@@ -7,6 +7,7 @@ import { app } from 'electron'
77import {
88 DEFAULT_DAILY_NOTES_DIRECTORY ,
99 AssetMeta ,
10+ type FolderIconId ,
1011 type PrimaryNotesLocation ,
1112 type VaultSettings ,
1213 FolderEntry ,
@@ -64,13 +65,45 @@ const SEARCH_EXECUTABLE_NAMES = {
6465 ripgrep : new Set ( [ 'rg' , 'rg.exe' ] ) ,
6566 fzf : new Set ( [ 'fzf' , 'fzf.exe' ] )
6667} as const
68+ const VALID_FOLDER_ICON_IDS = new Set < FolderIconId > ( [
69+ 'folder' ,
70+ 'bolt' ,
71+ 'tray' ,
72+ 'archive' ,
73+ 'trash' ,
74+ 'book' ,
75+ 'bookmark' ,
76+ 'calendar' ,
77+ 'briefcase' ,
78+ 'tag' ,
79+ 'document' ,
80+ 'sparkle' ,
81+ 'code' ,
82+ 'user' ,
83+ 'star' ,
84+ 'heart' ,
85+ 'link' ,
86+ 'lightbulb' ,
87+ 'flask' ,
88+ 'graduation' ,
89+ 'music' ,
90+ 'image' ,
91+ 'palette' ,
92+ 'terminal' ,
93+ 'wrench' ,
94+ 'globe' ,
95+ 'map' ,
96+ 'chart' ,
97+ 'home'
98+ ] )
6799
68100const DEFAULT_VAULT_SETTINGS : VaultSettings = {
69101 primaryNotesLocation : 'inbox' ,
70102 dailyNotes : {
71103 enabled : false ,
72104 directory : DEFAULT_DAILY_NOTES_DIRECTORY
73- }
105+ } ,
106+ folderIcons : { }
74107}
75108
76109interface VaultTextSearchCandidate {
@@ -286,7 +319,8 @@ function cloneVaultSettings(settings: VaultSettings): VaultSettings {
286319 dailyNotes : {
287320 enabled : settings . dailyNotes . enabled ,
288321 directory : settings . dailyNotes . directory
289- }
322+ } ,
323+ folderIcons : { ...settings . folderIcons }
290324 }
291325}
292326
@@ -310,12 +344,22 @@ function normalizeVaultSettings(
310344 dailyNotes : {
311345 enabled : DEFAULT_VAULT_SETTINGS . dailyNotes . enabled ,
312346 directory : DEFAULT_DAILY_NOTES_DIRECTORY
313- }
347+ } ,
348+ folderIcons : { }
314349 }
315350 }
316351 const candidate = value as {
317352 primaryNotesLocation ?: unknown
318353 dailyNotes ?: { enabled ?: unknown ; directory ?: unknown } | null
354+ folderIcons ?: Record < string , unknown > | null
355+ }
356+ const folderIcons : Record < string , FolderIconId > = { }
357+ if ( candidate . folderIcons && typeof candidate . folderIcons === 'object' ) {
358+ for ( const [ key , iconId ] of Object . entries ( candidate . folderIcons ) ) {
359+ if ( ! key || typeof iconId !== 'string' ) continue
360+ if ( ! VALID_FOLDER_ICON_IDS . has ( iconId as FolderIconId ) ) continue
361+ folderIcons [ key ] = iconId
362+ }
319363 }
320364 return {
321365 primaryNotesLocation : normalizePrimaryNotesLocation (
@@ -327,8 +371,72 @@ function normalizeVaultSettings(
327371 ? candidate . dailyNotes . enabled
328372 : DEFAULT_VAULT_SETTINGS . dailyNotes . enabled ,
329373 directory : normalizeDailyNotesDirectory ( candidate . dailyNotes ?. directory )
374+ } ,
375+ folderIcons
376+ }
377+ }
378+
379+ function folderIconKey ( folder : NoteFolder , subpath : string ) : string {
380+ return `${ folder } :${ subpath } `
381+ }
382+
383+ function rewriteFolderIconsForRename (
384+ folderIcons : Record < string , FolderIconId > ,
385+ folder : NoteFolder ,
386+ oldSubpath : string ,
387+ newSubpath : string
388+ ) : Record < string , FolderIconId > {
389+ const next : Record < string , FolderIconId > = { }
390+ const exactKey = folderIconKey ( folder , oldSubpath )
391+ const prefix = `${ exactKey } /`
392+ for ( const [ key , value ] of Object . entries ( folderIcons ) ) {
393+ if ( key === exactKey ) {
394+ next [ folderIconKey ( folder , newSubpath ) ] = value
395+ continue
396+ }
397+ if ( key . startsWith ( prefix ) ) {
398+ next [ folderIconKey ( folder , newSubpath ) + key . slice ( exactKey . length ) ] = value
399+ continue
400+ }
401+ next [ key ] = value
402+ }
403+ return next
404+ }
405+
406+ function removeFolderIcons (
407+ folderIcons : Record < string , FolderIconId > ,
408+ folder : NoteFolder ,
409+ subpath : string
410+ ) : Record < string , FolderIconId > {
411+ const next : Record < string , FolderIconId > = { }
412+ const exactKey = folderIconKey ( folder , subpath )
413+ const prefix = `${ exactKey } /`
414+ for ( const [ key , value ] of Object . entries ( folderIcons ) ) {
415+ if ( key === exactKey || key . startsWith ( prefix ) ) continue
416+ next [ key ] = value
417+ }
418+ return next
419+ }
420+
421+ function duplicateFolderIcons (
422+ folderIcons : Record < string , FolderIconId > ,
423+ folder : NoteFolder ,
424+ sourceSubpath : string ,
425+ targetSubpath : string
426+ ) : Record < string , FolderIconId > {
427+ const next : Record < string , FolderIconId > = { ...folderIcons }
428+ const exactKey = folderIconKey ( folder , sourceSubpath )
429+ const prefix = `${ exactKey } /`
430+ for ( const [ key , value ] of Object . entries ( folderIcons ) ) {
431+ if ( key === exactKey ) {
432+ next [ folderIconKey ( folder , targetSubpath ) ] = value
433+ continue
434+ }
435+ if ( key . startsWith ( prefix ) ) {
436+ next [ folderIconKey ( folder , targetSubpath ) + key . slice ( exactKey . length ) ] = value
330437 }
331438 }
439+ return next
332440}
333441
334442async function inferPrimaryNotesLocation ( root : string ) : Promise < PrimaryNotesLocation > {
@@ -1343,6 +1451,17 @@ export async function renameFolder(
13431451 } else {
13441452 await fs . rename ( oldAbs , newAbs )
13451453 }
1454+ const settings = await getVaultSettings ( root )
1455+ const nextSettings : VaultSettings = {
1456+ ...settings ,
1457+ folderIcons : rewriteFolderIconsForRename (
1458+ settings . folderIcons ,
1459+ topFolder ,
1460+ oldClean ,
1461+ newClean
1462+ )
1463+ }
1464+ await setVaultSettings ( root , nextSettings )
13461465 return newClean
13471466}
13481467
@@ -1359,6 +1478,12 @@ export async function deleteFolder(
13591478 if ( ! clean ) throw new Error ( 'Cannot delete the top-level folder' )
13601479 const abs = resolveSafe ( await folderRoot ( root , topFolder ) , clean )
13611480 await fs . rm ( abs , { recursive : true , force : true } )
1481+ const settings = await getVaultSettings ( root )
1482+ const nextSettings : VaultSettings = {
1483+ ...settings ,
1484+ folderIcons : removeFolderIcons ( settings . folderIcons , topFolder , clean )
1485+ }
1486+ await setVaultSettings ( root , nextSettings )
13621487}
13631488
13641489/**
@@ -1390,7 +1515,14 @@ export async function duplicateFolder(
13901515 }
13911516 const newAbs = path . join ( parentAbs , copyName )
13921517 await fs . cp ( oldAbs , newAbs , { recursive : true } )
1393- return path . relative ( topRoot , newAbs ) . split ( path . sep ) . join ( '/' )
1518+ const newSubpath = path . relative ( topRoot , newAbs ) . split ( path . sep ) . join ( '/' )
1519+ const settings = await getVaultSettings ( root )
1520+ const nextSettings : VaultSettings = {
1521+ ...settings ,
1522+ folderIcons : duplicateFolderIcons ( settings . folderIcons , topFolder , clean , newSubpath )
1523+ }
1524+ await setVaultSettings ( root , nextSettings )
1525+ return newSubpath
13941526}
13951527
13961528/** Build the absolute on-disk path for a vault folder / subfolder. */
0 commit comments