Skip to content

Commit 866747a

Browse files
committed
Add periodic bookmarks sync with configurable interval
- Implement syncSiblings() to reorder bookmarks to match file explorer order and remove orphaned entries - Add configurable sync interval setting (0 = disabled, up to 3600s) - Start/stop sync timer on setting change and plugin lifecycle - Sync bookmarks immediately on new file/folder creation
1 parent 7713f79 commit 866747a

3 files changed

Lines changed: 129 additions & 3 deletions

File tree

src/main.ts

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ export default class CustomSortPlugin
103103

104104
uninstallerOfFileExplorerPatch: MonkeyAroundUninstaller|undefined = undefined
105105

106+
bookmarksSyncTimerId: ReturnType<typeof setInterval> | undefined = undefined
107+
106108
showNotice(message: string, timeout?: number) {
107109
if (this.settings.notificationsEnabled || (Platform.isMobile && this.settings.mobileNotificationsEnabled)) {
108110
new Notice(message, timeout)
@@ -374,6 +376,8 @@ export default class CustomSortPlugin
374376
this.registerPluginUnloadHandler()
375377

376378
this.initialize();
379+
380+
this.restartBookmarksSyncTimer();
377381
}
378382

379383
registerEventHandlers() {
@@ -582,6 +586,16 @@ export default class CustomSortPlugin
582586
bookmarksPlugin.saveDataAndUpdateBookmarkViews(true)
583587
}
584588
})
589+
590+
this.app.vault.on("create", (file: TAbstractFile) => {
591+
if (plugin.settings.bookmarksSyncIntervalSeconds <= 0) return
592+
const bookmarksPlugin = getBookmarksPlugin(plugin.app, plugin.settings.bookmarksGroupToConsumeAsOrderingReference)
593+
if (bookmarksPlugin && file.parent) {
594+
const orderedChildren: Array<TAbstractFile> = plugin.orderedFolderItemsForBookmarking(file.parent, bookmarksPlugin)
595+
bookmarksPlugin.syncSiblings(orderedChildren)
596+
bookmarksPlugin.saveDataAndUpdateBookmarkViews(true)
597+
}
598+
})
585599
}
586600

587601
uninstallFileExplorerPatchIfInstalled() {
@@ -697,9 +711,9 @@ export default class CustomSortPlugin
697711
}
698712

699713
syncBookmarksRecursive(folder: TFolder, bookmarksPlugin: BookmarksPluginInterface): void {
700-
// Sync the current folder: bookmark all children that are not yet bookmarked (appended at end)
714+
// Sync the current folder: reorder bookmarks to match file explorer order and remove orphans
701715
const orderedChildren: Array<TAbstractFile> = this.orderedFolderItemsForBookmarking(folder, bookmarksPlugin)
702-
bookmarksPlugin.bookmarkSiblings(orderedChildren)
716+
bookmarksPlugin.syncSiblings(orderedChildren)
703717

704718
// Recurse into subfolders
705719
for (const child of folder.children) {
@@ -709,7 +723,36 @@ export default class CustomSortPlugin
709723
}
710724
}
711725

726+
runBookmarksSyncForVault(): void {
727+
if (this.settings.suspended) return
728+
const bookmarksPlugin = getBookmarksPlugin(this.app, this.settings.bookmarksGroupToConsumeAsOrderingReference)
729+
if (!bookmarksPlugin) return
730+
const rootFolder: TFolder = this.app.vault.getRoot()
731+
this.syncBookmarksRecursive(rootFolder, bookmarksPlugin)
732+
bookmarksPlugin.saveDataAndUpdateBookmarkViews(true)
733+
}
734+
735+
restartBookmarksSyncTimer(): void {
736+
// Clear existing timer
737+
if (this.bookmarksSyncTimerId !== undefined) {
738+
clearInterval(this.bookmarksSyncTimerId)
739+
this.bookmarksSyncTimerId = undefined
740+
}
741+
742+
// Start new timer if interval > 0
743+
const intervalSeconds = this.settings.bookmarksSyncIntervalSeconds
744+
if (intervalSeconds > 0) {
745+
this.bookmarksSyncTimerId = setInterval(() => {
746+
this.runBookmarksSyncForVault()
747+
}, intervalSeconds * 1000)
748+
}
749+
}
750+
712751
onunload() {
752+
if (this.bookmarksSyncTimerId !== undefined) {
753+
clearInterval(this.bookmarksSyncTimerId)
754+
this.bookmarksSyncTimerId = undefined
755+
}
713756
}
714757

715758
onUserEnable() {

src/settings.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export interface CustomSortPluginSettings {
1414
bookmarksContextMenus: boolean
1515
bookmarksGroupToConsumeAsOrderingReference: string
1616
delayForInitialApplication: number // miliseconds
17+
bookmarksSyncIntervalSeconds: number // 0 = disabled
1718
}
1819

1920
const MILIS = 1000
@@ -22,6 +23,10 @@ const DELAY_MIN_SECONDS = 0
2223
const DELAY_MAX_SECONDS = 30
2324
const DEFAULT_DELAY = DEFAULT_DELAY_SECONDS * MILIS
2425

26+
const SYNC_INTERVAL_MIN_SECONDS = 0
27+
const SYNC_INTERVAL_MAX_SECONDS = 3600
28+
const DEFAULT_SYNC_INTERVAL_SECONDS = 0
29+
2530
export const DEFAULT_SETTINGS: CustomSortPluginSettings = {
2631
additionalSortspecFile: '',
2732
indexNoteNameForFolderNotes: '',
@@ -33,7 +38,8 @@ export const DEFAULT_SETTINGS: CustomSortPluginSettings = {
3338
automaticBookmarksIntegration: false,
3439
bookmarksContextMenus: false,
3540
bookmarksGroupToConsumeAsOrderingReference: 'sortspec',
36-
delayForInitialApplication: DEFAULT_DELAY
41+
delayForInitialApplication: DEFAULT_DELAY,
42+
bookmarksSyncIntervalSeconds: DEFAULT_SYNC_INTERVAL_SECONDS
3743
}
3844

3945
// On API 1.2.x+ enable the bookmarks integration by default
@@ -246,5 +252,29 @@ export class CustomSortSettingTab extends PluginSettingTab {
246252
}
247253
await this.plugin.saveSettings();
248254
}))
255+
256+
const bookmarksSyncIntervalDescr: DocumentFragment = sanitizeHTMLToDom(
257+
'Interval in seconds for automatic synchronization of bookmarks with the file explorer order.'
258+
+ '<br>'
259+
+ 'Set to <b>0</b> to disable automatic sync (manual sync via context menu is still available).'
260+
+ '<br>'
261+
+ 'When enabled, the plugin will periodically ensure that bookmarks reflect the current file/folder structure and sorting order.'
262+
+ '<br>'
263+
+ `Min: ${SYNC_INTERVAL_MIN_SECONDS} sec., max: ${SYNC_INTERVAL_MAX_SECONDS} sec.`
264+
)
265+
266+
new Setting(containerEl)
267+
.setName('Automatic bookmarks sync interval')
268+
.setDesc(bookmarksSyncIntervalDescr)
269+
.addText(text => text
270+
.setPlaceholder('0')
271+
.setValue(`${this.plugin.settings.bookmarksSyncIntervalSeconds}`)
272+
.onChange(async (value) => {
273+
let interval = parseInt(value)
274+
interval = (Number.isNaN(interval) || !Number.isFinite(interval)) ? DEFAULT_SYNC_INTERVAL_SECONDS : (interval < SYNC_INTERVAL_MIN_SECONDS ? SYNC_INTERVAL_MIN_SECONDS : (interval > SYNC_INTERVAL_MAX_SECONDS ? SYNC_INTERVAL_MAX_SECONDS : interval))
275+
this.plugin.settings.bookmarksSyncIntervalSeconds = interval
276+
await this.plugin.saveSettings()
277+
this.plugin.restartBookmarksSyncTimer()
278+
}))
249279
}
250280
}

src/utils/BookmarksCorePluginSignature.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ export interface BookmarksPluginInterface {
8282
saveDataAndUpdateBookmarkViews(updateBookmarkViews: boolean): void
8383
bookmarkSiblings(siblings: Array<TAbstractFile>, inTheTop?: boolean): void
8484
unbookmarkSiblings(siblings: Array<TAbstractFile>): void
85+
syncSiblings(orderedSiblings: Array<TAbstractFile>): void
8586
updateSortingBookmarksAfterItemRenamed(renamedItem: TAbstractFile, oldPath: string): void
8687
updateSortingBookmarksAfterItemDeleted(deletedItem: TAbstractFile): void
8788
isBookmarkedForSorting(item: TAbstractFile): boolean
@@ -230,6 +231,58 @@ class BookmarksPluginWrapper implements BookmarksPluginInterface {
230231
}
231232
}
232233

234+
// Sync bookmarks to match the given ordered siblings exactly:
235+
// - Reorder existing bookmark entries to match the order of orderedSiblings
236+
// - Add missing items at their correct position
237+
// - Remove orphaned entries (items in bookmarks that are not in orderedSiblings)
238+
syncSiblings = (orderedSiblings: Array<TAbstractFile>) => {
239+
if (orderedSiblings.length === 0) return
240+
241+
const bookmarksContainer: BookmarkedParentFolder|undefined = findGroupForItemPathInBookmarks(
242+
orderedSiblings[0].path,
243+
CreateIfMissing,
244+
this.plugin!,
245+
this.groupNameForSorting
246+
)
247+
248+
if (!bookmarksContainer) return
249+
250+
const reordered: Array<BookmarkedItem> = []
251+
const matchedItems: Set<BookmarkedItem> = new Set()
252+
253+
for (const aSibling of orderedSiblings) {
254+
const siblingName = lastPathComponent(aSibling.path)
255+
256+
// Find existing bookmark entry for this sibling
257+
const existing = bookmarksContainer.items.find((it) =>
258+
((it.type === 'folder' || it.type === 'file') && it.path === aSibling.path) ||
259+
(it.type === 'group' && groupNameForPath(it.title||'') === siblingName)
260+
)
261+
262+
if (existing) {
263+
// If it was a group transparent for sorting, make it visible again
264+
if (existing.type === 'group' && isGroupTransparentForSorting(existing.title)) {
265+
existing.title = groupNameForPath(existing.title||'')
266+
}
267+
reordered.push(existing)
268+
matchedItems.add(existing)
269+
} else {
270+
// Create new entry at the correct position
271+
const newEntry: BookmarkedItem = (aSibling instanceof TFolder)
272+
? createBookmarkGroupEntry(siblingName)
273+
: createBookmarkFileEntry(aSibling.path)
274+
reordered.push(newEntry)
275+
}
276+
}
277+
278+
// Remove orphaned entries: items in bookmarks that have no corresponding file/folder
279+
// (they are simply not included in reordered)
280+
281+
// Replace the container items with the reordered list
282+
bookmarksContainer.items.length = 0
283+
bookmarksContainer.items.push(...reordered)
284+
}
285+
233286
updateSortingBookmarksAfterItemRenamed = (renamedItem: TAbstractFile, oldPath: string): void => {
234287
updateSortingBookmarksAfterItemRenamed(this.plugin!, renamedItem, oldPath, this.groupNameForSorting)
235288
}

0 commit comments

Comments
 (0)