@@ -10,53 +10,86 @@ import Button from "../react/Button.jsx";
1010import FormGroup from "../react/FormGroup.js" ;
1111import { useTriliumEvent } from "../react/hooks.jsx" ;
1212import FBranch from "../../entities/fbranch.js" ;
13+ import type { ContextMenuCommandData } from "../../components/app_context.js" ;
14+ import "./branch_prefix.css" ;
15+
16+ // Virtual branches (e.g., from search results) start with this prefix
17+ const VIRTUAL_BRANCH_PREFIX = "virt-" ;
1318
1419export default function BranchPrefixDialog ( ) {
1520 const [ shown , setShown ] = useState ( false ) ;
16- const [ branch , setBranch ] = useState < FBranch > ( ) ;
21+ const [ branches , setBranches ] = useState < FBranch [ ] > ( [ ] ) ;
1722 const [ prefix , setPrefix ] = useState ( "" ) ;
1823 const branchInput = useRef < HTMLInputElement > ( null ) ;
1924
20- useTriliumEvent ( "editBranchPrefix" , async ( ) => {
21- const notePath = appContext . tabManager . getActiveContextNotePath ( ) ;
22- if ( ! notePath ) {
23- return ;
24- }
25+ useTriliumEvent ( "editBranchPrefix" , async ( data ?: ContextMenuCommandData ) => {
26+ let branchIds : string [ ] = [ ] ;
2527
26- const { noteId, parentNoteId } = tree . getNoteIdAndParentIdFromUrl ( notePath ) ;
28+ if ( data ?. selectedOrActiveBranchIds && data . selectedOrActiveBranchIds . length > 0 ) {
29+ // Multi-select mode from tree context menu
30+ branchIds = data . selectedOrActiveBranchIds . filter ( ( branchId ) => ! branchId . startsWith ( VIRTUAL_BRANCH_PREFIX ) ) ;
31+ } else {
32+ // Single branch mode from keyboard shortcut or when no selection
33+ const notePath = appContext . tabManager . getActiveContextNotePath ( ) ;
34+ if ( ! notePath ) {
35+ return ;
36+ }
2737
28- if ( ! noteId || ! parentNoteId ) {
29- return ;
38+ const { noteId, parentNoteId } = tree . getNoteIdAndParentIdFromUrl ( notePath ) ;
39+
40+ if ( ! noteId || ! parentNoteId ) {
41+ return ;
42+ }
43+
44+ const branchId = await froca . getBranchId ( parentNoteId , noteId ) ;
45+ if ( ! branchId ) {
46+ return ;
47+ }
48+ const parentNote = await froca . getNote ( parentNoteId ) ;
49+ if ( ! parentNote || parentNote . type === "search" ) {
50+ return ;
51+ }
52+
53+ branchIds = [ branchId ] ;
3054 }
3155
32- const newBranchId = await froca . getBranchId ( parentNoteId , noteId ) ;
33- if ( ! newBranchId ) {
56+ if ( branchIds . length === 0 ) {
3457 return ;
3558 }
36- const parentNote = await froca . getNote ( parentNoteId ) ;
37- if ( ! parentNote || parentNote . type === "search" ) {
59+
60+ const newBranches = branchIds
61+ . map ( id => froca . getBranch ( id ) )
62+ . filter ( ( branch ) : branch is FBranch => branch !== null ) ;
63+
64+ if ( newBranches . length === 0 ) {
3865 return ;
3966 }
4067
41- const newBranch = froca . getBranch ( newBranchId ) ;
42- setBranch ( newBranch ) ;
43- setPrefix ( newBranch ?. prefix ?? "" ) ;
68+ setBranches ( newBranches ) ;
69+ // Use the prefix of the first branch as the initial value
70+ setPrefix ( newBranches [ 0 ] ?. prefix ?? "" ) ;
4471 setShown ( true ) ;
4572 } ) ;
4673
4774 async function onSubmit ( ) {
48- if ( ! branch ) {
75+ if ( branches . length === 0 ) {
4976 return ;
5077 }
5178
52- savePrefix ( branch . branchId , prefix ) ;
79+ if ( branches . length === 1 ) {
80+ await savePrefix ( branches [ 0 ] . branchId , prefix ) ;
81+ } else {
82+ await savePrefixBatch ( branches . map ( b => b . branchId ) , prefix ) ;
83+ }
5384 setShown ( false ) ;
5485 }
5586
87+ const isSingleBranch = branches . length === 1 ;
88+
5689 return (
5790 < Modal
5891 className = "branch-prefix-dialog"
59- title = { t ( "branch_prefix.edit_branch_prefix" ) }
92+ title = { isSingleBranch ? t ( "branch_prefix.edit_branch_prefix" ) : t ( "branch_prefix.edit_branch_prefix_multiple" , { count : branches . length } ) }
6093 size = "lg"
6194 onShown = { ( ) => branchInput . current ?. focus ( ) }
6295 onHidden = { ( ) => setShown ( false ) }
@@ -69,9 +102,27 @@ export default function BranchPrefixDialog() {
69102 < div class = "input-group" >
70103 < input class = "branch-prefix-input form-control" value = { prefix } ref = { branchInput }
71104 onChange = { ( e ) => setPrefix ( ( e . target as HTMLInputElement ) . value ) } />
72- < div class = "branch-prefix-note-title input-group-text" > - { branch && branch . getNoteFromCache ( ) . title } </ div >
105+ { isSingleBranch && branches [ 0 ] && (
106+ < div class = "branch-prefix-note-title input-group-text" > - { branches [ 0 ] . getNoteFromCache ( ) . title } </ div >
107+ ) }
73108 </ div >
74109 </ FormGroup >
110+ { ! isSingleBranch && (
111+ < div className = "branch-prefix-notes-list" >
112+ < strong > { t ( "branch_prefix.affected_branches" , { count : branches . length } ) } </ strong >
113+ < ul >
114+ { branches . map ( ( branch ) => {
115+ const note = branch . getNoteFromCache ( ) ;
116+ return (
117+ < li key = { branch . branchId } >
118+ { branch . prefix && < span className = "branch-prefix-current" > { branch . prefix } - </ span > }
119+ { note . title }
120+ </ li >
121+ ) ;
122+ } ) }
123+ </ ul >
124+ </ div >
125+ ) }
75126 </ Modal >
76127 ) ;
77128}
@@ -80,3 +131,8 @@ async function savePrefix(branchId: string, prefix: string) {
80131 await server . put ( `branches/${ branchId } /set-prefix` , { prefix : prefix } ) ;
81132 toast . showMessage ( t ( "branch_prefix.branch_prefix_saved" ) ) ;
82133}
134+
135+ async function savePrefixBatch ( branchIds : string [ ] , prefix : string ) {
136+ await server . put ( "branches/set-prefix-batch" , { branchIds, prefix } ) ;
137+ toast . showMessage ( t ( "branch_prefix.branch_prefix_saved_multiple" , { count : branchIds . length } ) ) ;
138+ }
0 commit comments