#74 - Integration with Bookmarks core plugin and support for indirect drag & drop arrangement
Tons of updates: - feature complete - context menu for 'bookmark this' and 'bookmark+siblings' for sorting - order of bookmarked siblings reflects the current sorting in File Explorer, whatever it is (!!!) - unit tests probably missing for the additions - list of TODO: updated in main.ts and other files inline
This commit is contained in:
parent
71ab76652c
commit
99afdebba8
|
@ -16,7 +16,6 @@ import {
|
|||
ObsidianIconFolder_PluginInstance,
|
||||
ObsidianIconFolderPlugin_Data
|
||||
} from "../utils/ObsidianIconFolderPluginSignature";
|
||||
import {determineBookmarkOrder} from "../utils/BookmarksCorePluginSignature";
|
||||
|
||||
const mockTFile = (basename: string, ext: string, size?: number, ctime?: number, mtime?: number): TFile => {
|
||||
return {
|
||||
|
|
|
@ -57,6 +57,8 @@ export interface FolderItemForSorting {
|
|||
}
|
||||
|
||||
export type SorterFn = (a: FolderItemForSorting, b: FolderItemForSorting) => number
|
||||
export type PlainSorterFn = (a: TAbstractFile, b: TAbstractFile) => number
|
||||
export type PlainFileOnlySorterFn = (a: TFile, b: TFile) => number
|
||||
export type CollatorCompareFn = (a: string, b: string) => number
|
||||
|
||||
// Syntax sugar
|
||||
|
@ -129,13 +131,32 @@ let Sorters: { [key in CustomSortOrder]: SorterFn } = {
|
|||
[CustomSortOrder.standardObsidian]: (a: FolderItemForSorting, b: FolderItemForSorting) => CollatorCompare(a.sortString, b.sortString),
|
||||
};
|
||||
|
||||
// OS - Obsidian Sort
|
||||
const OS_alphabetical = 'alphabetical'
|
||||
const OS_alphabeticalReverse = 'alphabeticalReverse'
|
||||
const OS_byModifiedTime = 'byModifiedTime'
|
||||
const OS_byModifiedTimeReverse = 'byModifiedTimeReverse'
|
||||
const OS_byCreatedTime = 'byCreatedTime'
|
||||
const OS_byCreatedTimeReverse = 'byCreatedTimeReverse'
|
||||
|
||||
export const ObsidianStandardDefaultSortingName = OS_alphabetical
|
||||
|
||||
const StandardObsidianToCustomSort: {[key: string]: CustomSortOrder} = {
|
||||
"alphabetical": CustomSortOrder.alphabetical,
|
||||
"alphabeticalReverse": CustomSortOrder.alphabeticalReverse,
|
||||
"byModifiedTime": CustomSortOrder.byModifiedTimeReverse, // In Obsidian labeled as 'Modified time (new to old)'
|
||||
"byModifiedTimeReverse": CustomSortOrder.byModifiedTime, // In Obsidian labeled as 'Modified time (old to new)'
|
||||
"byCreatedTime": CustomSortOrder.byCreatedTimeReverse, // In Obsidian labeled as 'Created time (new to old)'
|
||||
"byCreatedTimeReverse": CustomSortOrder.byCreatedTime // In Obsidian labeled as 'Created time (old to new)'
|
||||
[OS_alphabetical]: CustomSortOrder.alphabetical,
|
||||
[OS_alphabeticalReverse]: CustomSortOrder.alphabeticalReverse,
|
||||
[OS_byModifiedTime]: CustomSortOrder.byModifiedTimeReverse, // In Obsidian labeled as 'Modified time (new to old)'
|
||||
[OS_byModifiedTimeReverse]: CustomSortOrder.byModifiedTime, // In Obsidian labeled as 'Modified time (old to new)'
|
||||
[OS_byCreatedTime]: CustomSortOrder.byCreatedTimeReverse, // In Obsidian labeled as 'Created time (new to old)'
|
||||
[OS_byCreatedTimeReverse]: CustomSortOrder.byCreatedTime // In Obsidian labeled as 'Created time (old to new)'
|
||||
}
|
||||
|
||||
const StandardObsidianToPlainSortFn: {[key: string]: PlainFileOnlySorterFn} = {
|
||||
[OS_alphabetical]: (a: TFile, b: TFile) => CollatorCompare(a.basename, b.basename),
|
||||
[OS_alphabeticalReverse]: (a: TFile, b: TFile) => -StandardObsidianToPlainSortFn[OS_alphabetical](a,b),
|
||||
[OS_byModifiedTime]: (a: TFile, b: TFile) => b.stat.mtime - a.stat.mtime,
|
||||
[OS_byModifiedTimeReverse]: (a: TFile, b: TFile) => -StandardObsidianToPlainSortFn[OS_byModifiedTime](a,b),
|
||||
[OS_byCreatedTime]: (a: TFile, b: TFile) => b.stat.ctime - a.stat.ctime,
|
||||
[OS_byCreatedTimeReverse]: (a: TFile, b: TFile) => -StandardObsidianToPlainSortFn[OS_byCreatedTime](a,b)
|
||||
}
|
||||
|
||||
// Standard Obsidian comparator keeps folders in the top sorted alphabetically
|
||||
|
@ -150,6 +171,20 @@ const StandardObsidianComparator = (order: CustomSortOrder): SorterFn => {
|
|||
}
|
||||
}
|
||||
|
||||
// Equivalent of StandardObsidianComparator working directly on TAbstractFile items
|
||||
export const StandardPlainObsidianComparator = (order: string): PlainSorterFn => {
|
||||
const fileSorterFn = StandardObsidianToPlainSortFn[order] || StandardObsidianToCustomSort[OS_alphabetical]
|
||||
return (a: TAbstractFile, b: TAbstractFile): number => {
|
||||
const aIsFolder: boolean = a instanceof TFolder
|
||||
const bIsFolder: boolean = b instanceof TFolder
|
||||
return aIsFolder || bIsFolder
|
||||
?
|
||||
(aIsFolder && !bIsFolder ? -1 : (bIsFolder && !aIsFolder ? 1 : CollatorCompare(a.name,b.name)))
|
||||
:
|
||||
fileSorterFn(a as TFile, b as TFile);
|
||||
}
|
||||
}
|
||||
|
||||
export const getSorterFnFor = (sorting: CustomSortOrder, currentUIselectedSorting?: string): SorterFn => {
|
||||
if (sorting === CustomSortOrder.standardObsidian) {
|
||||
sorting = StandardObsidianToCustomSort[currentUIselectedSorting ?? 'alphabetical'] ?? CustomSortOrder.alphabetical
|
||||
|
@ -545,3 +580,36 @@ export const folderSort = function (sortingSpec: CustomSortSpec, ctx: Processing
|
|||
this.children = items;
|
||||
}
|
||||
};
|
||||
|
||||
// Returns a sorted copy of the input array, intentionally to keep it intact
|
||||
export const sortFolderItemsForBookmarking = function (items: Array<TAbstractFile>, sortingSpec: CustomSortSpec|null|undefined, ctx: ProcessingContext, uiSortOrder: string): Array<TAbstractFile> {
|
||||
if (sortingSpec) {
|
||||
const folderItemsByPath: { [key: string]: TAbstractFile } = {}
|
||||
|
||||
const folderItems: Array<FolderItemForSorting> = items.map((entry: TFile | TFolder) => {
|
||||
folderItemsByPath[entry.path] = entry
|
||||
const itemForSorting: FolderItemForSorting = determineSortingGroup(entry, sortingSpec, ctx)
|
||||
return itemForSorting
|
||||
})
|
||||
|
||||
// Finally, for advanced sorting by modified date, for some folders the modified date has to be determined
|
||||
determineFolderDatesIfNeeded(folderItems, sortingSpec)
|
||||
|
||||
if (ctx.bookmarksPlugin?.instance) {
|
||||
determineBookmarksOrderIfNeeded(folderItems, sortingSpec, ctx.bookmarksPlugin.instance, ctx.bookmarksPlugin.groupNameForSorting)
|
||||
}
|
||||
|
||||
const comparator: SorterFn = getComparator(sortingSpec, uiSortOrder)
|
||||
|
||||
folderItems.sort(comparator)
|
||||
|
||||
const sortedItems: Array<TAbstractFile> = folderItems.map((entry) => folderItemsByPath[entry.path])
|
||||
|
||||
return sortedItems
|
||||
} else { // No custom sorting or the custom sort disabled - apply standard Obsidian sorting (internally 1:1 recreated implementation)
|
||||
const folderItems: Array<TAbstractFile> = items.map((entry: TFile | TFolder) => entry)
|
||||
const plainSorterFn: PlainSorterFn = StandardPlainObsidianComparator(uiSortOrder)
|
||||
folderItems.sort(plainSorterFn)
|
||||
return folderItems
|
||||
}
|
||||
};
|
||||
|
|
129
src/main.ts
129
src/main.ts
|
@ -16,9 +16,14 @@ import {
|
|||
Vault, WorkspaceLeaf
|
||||
} from 'obsidian';
|
||||
import {around} from 'monkey-around';
|
||||
import {folderSort, ProcessingContext} from './custom-sort/custom-sort';
|
||||
import {
|
||||
folderSort,
|
||||
ObsidianStandardDefaultSortingName,
|
||||
ProcessingContext,
|
||||
sortFolderItemsForBookmarking
|
||||
} from './custom-sort/custom-sort';
|
||||
import {SortingSpecProcessor, SortSpecsCollection} from './custom-sort/sorting-spec-processor';
|
||||
import {CustomSortOrder, CustomSortSpec} from './custom-sort/custom-sort-types';
|
||||
import {CustomSortSpec} from './custom-sort/custom-sort-types';
|
||||
|
||||
import {
|
||||
addIcons,
|
||||
|
@ -30,8 +35,14 @@ import {
|
|||
ICON_SORT_SUSPENDED_SYNTAX_ERROR
|
||||
} from "./custom-sort/icons";
|
||||
import {getStarredPlugin} from "./utils/StarredPluginSignature";
|
||||
import {getBookmarksPlugin} from "./utils/BookmarksCorePluginSignature";
|
||||
import {
|
||||
getBookmarksPlugin,
|
||||
bookmarkFolderItem,
|
||||
saveDataAndUpdateBookmarkViews,
|
||||
bookmarkSiblings
|
||||
} from "./utils/BookmarksCorePluginSignature";
|
||||
import {getIconFolderPlugin} from "./utils/ObsidianIconFolderPluginSignature";
|
||||
import {lastPathComponent} from "./utils/utils";
|
||||
|
||||
interface CustomSortPluginSettings {
|
||||
additionalSortspecFile: string
|
||||
|
@ -317,14 +328,25 @@ export default class CustomSortPlugin extends Plugin {
|
|||
item.setTitle('Custom sort: bookmark for sorting.');
|
||||
item.setIcon('hashtag');
|
||||
item.onClick(() => {
|
||||
console.log(`custom-sort: bookmark this clicked ${source}`)
|
||||
const bookmarksPlugin = getBookmarksPlugin(plugin.app)
|
||||
console.log(`custom-sort: bookmark this clicked ${source} and the leaf is`)
|
||||
if (bookmarksPlugin) {
|
||||
bookmarkFolderItem(file, bookmarksPlugin, plugin.settings.bookmarksGroupToConsumeAsOrderingReference)
|
||||
saveDataAndUpdateBookmarkViews(bookmarksPlugin, plugin.app)
|
||||
}
|
||||
});
|
||||
};
|
||||
const bookmarkAllMenuItem = (item: MenuItem) => {
|
||||
item.setTitle('Custom sort: bookmark all siblings for sorting.');
|
||||
item.setTitle('Custom sort: bookmark+siblings for sorting.');
|
||||
item.setIcon('hashtag');
|
||||
item.onClick(() => {
|
||||
console.log(`custom-sort: bookmark all siblings clicked ${source}`)
|
||||
const bookmarksPlugin = getBookmarksPlugin(plugin.app)
|
||||
if (bookmarksPlugin) {
|
||||
const orderedChildren: Array<TAbstractFile> = plugin.orderedFolderItemsForBookmarking(file.parent)
|
||||
bookmarkSiblings(orderedChildren, bookmarksPlugin, plugin.settings.bookmarksGroupToConsumeAsOrderingReference)
|
||||
saveDataAndUpdateBookmarkViews(bookmarksPlugin, plugin.app)
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -358,6 +380,32 @@ export default class CustomSortPlugin extends Plugin {
|
|||
})
|
||||
}
|
||||
|
||||
determineSortSpecForFolder(folderPath: string, folderName?: string): CustomSortSpec|null|undefined {
|
||||
folderName = folderName ?? lastPathComponent(folderPath)
|
||||
let sortSpec: CustomSortSpec | null | undefined = this.sortSpecCache?.sortSpecByPath?.[folderPath]
|
||||
sortSpec = sortSpec ?? this.sortSpecCache?.sortSpecByName?.[folderName]
|
||||
|
||||
if (!sortSpec && this.sortSpecCache?.sortSpecByWildcard) {
|
||||
// when no sorting spec found directly by folder path, check for wildcard-based match
|
||||
sortSpec = this.sortSpecCache?.sortSpecByWildcard.folderMatch(folderPath, folderName)
|
||||
}
|
||||
return sortSpec
|
||||
}
|
||||
|
||||
createProcessingContextForSorting(): ProcessingContext {
|
||||
const ctx: ProcessingContext = {
|
||||
_mCache: this.app.metadataCache,
|
||||
starredPluginInstance: getStarredPlugin(this.app),
|
||||
bookmarksPlugin: {
|
||||
instance: this.settings.automaticBookmarksIntegration ? getBookmarksPlugin(this.app) : undefined,
|
||||
groupNameForSorting: this.settings.bookmarksGroupToConsumeAsOrderingReference
|
||||
},
|
||||
iconFolderPluginInstance: getIconFolderPlugin(this.app),
|
||||
plugin: this
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
// For the idea of monkey-patching credits go to https://github.com/nothingislost/obsidian-bartender
|
||||
patchFileExplorerFolder(patchableFileExplorer?: FileExplorerView): boolean {
|
||||
let plugin = this;
|
||||
|
@ -371,10 +419,6 @@ export default class CustomSortPlugin extends Plugin {
|
|||
const uninstallerOfFolderSortFunctionWrapper: MonkeyAroundUninstaller = around(Folder.prototype, {
|
||||
sort(old: any) {
|
||||
return function (...args: any[]) {
|
||||
console.log(this)
|
||||
console.log(this.fileExplorer.sortOrder)
|
||||
|
||||
|
||||
// quick check for plugin status
|
||||
if (plugin.settings.suspended) {
|
||||
return old.call(this, ...args);
|
||||
|
@ -385,38 +429,12 @@ export default class CustomSortPlugin extends Plugin {
|
|||
setIcon(plugin.ribbonIconEl, ICON_SORT_ENABLED_ACTIVE)
|
||||
}
|
||||
|
||||
// if custom sort is not specified, use the UI-selected
|
||||
const folder: TFolder = this.file
|
||||
let sortSpec: CustomSortSpec | null | undefined = plugin.sortSpecCache?.sortSpecByPath?.[folder.path]
|
||||
sortSpec = sortSpec ?? plugin.sortSpecCache?.sortSpecByName?.[folder.name]
|
||||
|
||||
if (!sortSpec && plugin.sortSpecCache?.sortSpecByWildcard) {
|
||||
// when no sorting spec found directly by folder path, check for wildcard-based match
|
||||
sortSpec = plugin.sortSpecCache?.sortSpecByWildcard.folderMatch(folder.path, folder.name)
|
||||
/* SM??? if (sortSpec?.defaultOrder === CustomSortOrder.standardObsidian) {
|
||||
explicitlyStandardSort = true
|
||||
sortSpec = null // A folder is explicitly excluded from custom sorting plugin
|
||||
}*/
|
||||
}
|
||||
|
||||
// TODO: ensure that explicitly configured standard sort excludes the auto-applied on-the-fly
|
||||
let sortSpec: CustomSortSpec | null | undefined = plugin.determineSortSpecForFolder(folder.path, folder.name)
|
||||
|
||||
if (sortSpec) {
|
||||
console.log(`Sortspec for folder ${folder.path}`)
|
||||
console.log(sortSpec)
|
||||
const ctx: ProcessingContext = {
|
||||
_mCache: plugin.app.metadataCache,
|
||||
starredPluginInstance: getStarredPlugin(plugin.app),
|
||||
bookmarksPlugin: {
|
||||
instance: plugin.settings.automaticBookmarksIntegration ? getBookmarksPlugin(this.app) : undefined,
|
||||
groupNameForSorting: plugin.settings.bookmarksGroupToConsumeAsOrderingReference
|
||||
},
|
||||
iconFolderPluginInstance: getIconFolderPlugin(this.app),
|
||||
plugin: plugin
|
||||
}
|
||||
return folderSort.call(this, sortSpec, ctx);
|
||||
return folderSort.call(this, sortSpec, plugin.createProcessingContextForSorting());
|
||||
} else {
|
||||
console.log(`NO Sortspec for folder ${folder.path}`)
|
||||
return old.call(this, ...args);
|
||||
}
|
||||
};
|
||||
|
@ -429,6 +447,16 @@ export default class CustomSortPlugin extends Plugin {
|
|||
}
|
||||
}
|
||||
|
||||
orderedFolderItemsForBookmarking(folder: TFolder): Array<TAbstractFile> {
|
||||
let sortSpec: CustomSortSpec | null | undefined = undefined
|
||||
if (!this.settings.suspended) {
|
||||
sortSpec = this.determineSortSpecForFolder(folder.path, folder.name)
|
||||
}
|
||||
let uiSortOrder: string = this.getFileExplorer()?.sortOrder || ObsidianStandardDefaultSortingName
|
||||
|
||||
return sortFolderItemsForBookmarking(folder.children, sortSpec, this.createProcessingContextForSorting(), uiSortOrder)
|
||||
}
|
||||
|
||||
// Credits go to https://github.com/nothingislost/obsidian-bartender
|
||||
getFileExplorer(): FileExplorerView | undefined {
|
||||
let fileExplorer: FileExplorerView | undefined = this.app.workspace.getLeavesOfType("file-explorer")?.first()
|
||||
|
@ -437,7 +465,6 @@ export default class CustomSortPlugin extends Plugin {
|
|||
}
|
||||
|
||||
onunload() {
|
||||
|
||||
}
|
||||
|
||||
updateStatusBar() {
|
||||
|
@ -455,6 +482,10 @@ export default class CustomSortPlugin extends Plugin {
|
|||
}
|
||||
}
|
||||
|
||||
const pathToFlatString = (path: string): string => {
|
||||
return path.replace('/','_').replace('\\', '_')
|
||||
}
|
||||
|
||||
class CustomSortSettingTab extends PluginSettingTab {
|
||||
plugin: CustomSortPlugin;
|
||||
|
||||
|
@ -577,33 +608,23 @@ class CustomSortSettingTab extends PluginSettingTab {
|
|||
.setPlaceholder('e.g. Group for sorting')
|
||||
.setValue(this.plugin.settings.bookmarksGroupToConsumeAsOrderingReference)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.bookmarksGroupToConsumeAsOrderingReference = value.trim();
|
||||
this.plugin.settings.bookmarksGroupToConsumeAsOrderingReference = value.trim() ? pathToFlatString(normalizePath(value)) : '';
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// TODO: clear bookmarks cache upon each tap on ribbon or on the command of 'sorting-on'
|
||||
|
||||
// TODO: clear bookmarks cache upon each context menu - before and after (maybe after is not needed, implicitlty empty after first clearing)
|
||||
|
||||
// TODO: if a folder doesn't have any bookmarked items, it should remain under control of standard obsidian sorting
|
||||
// TODO: clear bookmarks cache upon each context menu - before and after (maybe after is not needed, implicitly empty after first clearing)
|
||||
|
||||
// TODO: in discussion sections add (and pin) announcement "DRAG & DROP ORDERING AVAILABLE VIA THE BOOKMARKS CORE PLUGIN INTEGRATION"
|
||||
|
||||
// TODO: in community, add update message with announcement of drag & drop support via Bookmarks plugin
|
||||
|
||||
// TODO: if folder has explicit sorting: standard, don't apply bookmarks
|
||||
// TODO: context menu only if bookmarks plugin enabled and new setting (yet to be exposed) doesn't disable it
|
||||
|
||||
// TODO: fix error
|
||||
// bookmarks integration - for root folder and for other folders
|
||||
// (check for the case:
|
||||
// target-folder: /*
|
||||
// sorting: standard
|
||||
// TODO: defensive programming with ?. and equivalents to protect against crash if Obsidian API changes
|
||||
// Better the plugin to fail an operation than crash with errors
|
||||
|
||||
// TODO: unbookmarked items in partially bookmarked -> can it apply the system sort ???
|
||||
|
||||
// TODO: unblock syntax 'sorting: standard' also for groups --> since I have access to currently configured sorting :-)
|
||||
|
||||
// TODO: bug? On auto-bookmark integration strange behavior
|
||||
// TODO: remove console.log (many places added)
|
||||
|
|
|
@ -53,5 +53,7 @@ declare module 'obsidian' {
|
|||
createFolderDom(folder: TFolder): FileExplorerFolder;
|
||||
|
||||
requestSort(): void;
|
||||
|
||||
sortOrder: string
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
import {App, InstalledPlugin, PluginInstance} from "obsidian";
|
||||
import {App, InstalledPlugin, Plugin, PluginInstance, TAbstractFile, TFolder} from "obsidian";
|
||||
import {lastPathComponent} from "./utils";
|
||||
|
||||
const BookmarksPlugin_getBookmarks_methodName = 'getBookmarks'
|
||||
|
||||
const BookmarksPlugin_items_collectionName = 'items'
|
||||
|
||||
type Path = string
|
||||
|
||||
// Only relevant types of bookmarked items considered here
|
||||
|
@ -16,20 +19,23 @@ interface BookmarkWithPath {
|
|||
interface BookmarkedFile {
|
||||
type: 'file'
|
||||
path: Path
|
||||
subpath?: string // Anchor within the file
|
||||
subpath?: string // Anchor within the file (heading and/or block ref)
|
||||
title?: string
|
||||
ctime: number
|
||||
}
|
||||
|
||||
interface BookmarkedFolder {
|
||||
type: 'folder'
|
||||
path: Path
|
||||
title?: string
|
||||
ctime: number
|
||||
}
|
||||
|
||||
interface BookmarkedGroup {
|
||||
type: 'group'
|
||||
items: Array<BookmarkedItem>
|
||||
title?: string
|
||||
ctime: number
|
||||
}
|
||||
|
||||
export type BookmarkedItemPath = string
|
||||
|
@ -37,6 +43,7 @@ export type BookmarkedItemPath = string
|
|||
export interface OrderedBookmarkedItem {
|
||||
file: boolean
|
||||
folder: boolean
|
||||
group: boolean
|
||||
path: BookmarkedItemPath
|
||||
order: number
|
||||
}
|
||||
|
@ -47,6 +54,8 @@ interface OrderedBookmarks {
|
|||
|
||||
export interface Bookmarks_PluginInstance extends PluginInstance {
|
||||
[BookmarksPlugin_getBookmarks_methodName]: () => Array<BookmarkedItem> | undefined
|
||||
[BookmarksPlugin_items_collectionName]: Array<BookmarkedItem>
|
||||
saveData(): void
|
||||
}
|
||||
|
||||
let bookmarksCache: OrderedBookmarks | undefined = undefined
|
||||
|
@ -86,34 +95,39 @@ export const getBookmarksPlugin = (app?: App): Bookmarks_PluginInstance | undefi
|
|||
}
|
||||
}
|
||||
|
||||
type TraverseCallback = (item: BookmarkedItem, groupPath: string) => boolean | void
|
||||
type TraverseCallback = (item: BookmarkedItem, parentsGroupsPath: string) => boolean | void
|
||||
|
||||
const traverseBookmarksCollection = (items: Array<BookmarkedItem>, callback: TraverseCallback) => {
|
||||
const recursiveTraversal = (collection: Array<BookmarkedItem>, groupPath: string) => {
|
||||
const recursiveTraversal = (collection: Array<BookmarkedItem>, groupsPath: string) => {
|
||||
for (let idx = 0, collectionRef = collection; idx < collectionRef.length; idx++) {
|
||||
const item = collectionRef[idx];
|
||||
if (callback(item, groupPath)) return;
|
||||
if ('group' === item.type) recursiveTraversal(item.items, `${groupPath}${groupPath ? '/' : ''}${item.title}`);
|
||||
if (callback(item, groupsPath)) return;
|
||||
if ('group' === item.type) recursiveTraversal(item.items, `${groupsPath}${groupsPath?'/':''}${item.title}`);
|
||||
}
|
||||
};
|
||||
recursiveTraversal(items, '');
|
||||
}
|
||||
|
||||
const getOrderedBookmarks = (plugin: Bookmarks_PluginInstance, bookmarksGroup?: string): OrderedBookmarks | undefined => {
|
||||
const bookmarks: Array<BookmarkedItem> | undefined = plugin?.[BookmarksPlugin_getBookmarks_methodName]()
|
||||
const getOrderedBookmarks = (plugin: Bookmarks_PluginInstance, bookmarksGroupName?: string): OrderedBookmarks | undefined => {
|
||||
console.log(`Populating bookmarks cache with group scope ${bookmarksGroupName}`)
|
||||
let bookmarks: Array<BookmarkedItem> | undefined = plugin?.[BookmarksPlugin_getBookmarks_methodName]()
|
||||
if (bookmarks) {
|
||||
if (bookmarksGroupName) {
|
||||
const bookmarksGroup: BookmarkedGroup|undefined = bookmarks.find(
|
||||
(item) => item.type === 'group' && item.title === bookmarksGroupName) as BookmarkedGroup
|
||||
bookmarks = bookmarksGroup ? bookmarksGroup.items : undefined
|
||||
}
|
||||
if (bookmarks) {
|
||||
const orderedBookmarks: OrderedBookmarks = {}
|
||||
let order: number = 0
|
||||
const groupNamePrefix: string = bookmarksGroup ? `${bookmarksGroup}/` : ''
|
||||
const consumeItem = (item: BookmarkedItem, groupPath: string) => {
|
||||
if (groupNamePrefix && !groupPath.startsWith(groupNamePrefix)) {
|
||||
return
|
||||
}
|
||||
const consumeItem = (item: BookmarkedItem, parentGroupsPath: string) => {
|
||||
const isFile: boolean = item.type === 'file'
|
||||
const isAnchor: boolean = isFile && !!(item as BookmarkedFile).subpath
|
||||
const isFolder: boolean = item.type === 'folder'
|
||||
if ((isFile && !isAnchor) || isFolder) {
|
||||
const path = (item as BookmarkWithPath).path
|
||||
const isGroup: boolean = item.type === 'group'
|
||||
if ((isFile && !isAnchor) || isFolder || isGroup) {
|
||||
const pathOfGroup: string = `${parentGroupsPath}${parentGroupsPath?'/':''}${item.title}`
|
||||
const path = isGroup ? pathOfGroup : (item as BookmarkWithPath).path
|
||||
// Consume only the first occurrence of a path in bookmarks, even if many duplicates can exist
|
||||
const alreadyConsumed = orderedBookmarks[path]
|
||||
if (!alreadyConsumed) {
|
||||
|
@ -121,7 +135,8 @@ const getOrderedBookmarks = (plugin: Bookmarks_PluginInstance, bookmarksGroup?:
|
|||
path: path,
|
||||
order: order++,
|
||||
file: isFile,
|
||||
folder: isFile
|
||||
folder: isFile,
|
||||
group: isGroup
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -129,6 +144,7 @@ const getOrderedBookmarks = (plugin: Bookmarks_PluginInstance, bookmarksGroup?:
|
|||
traverseBookmarksCollection(bookmarks, consumeItem)
|
||||
return orderedBookmarks
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Result:
|
||||
|
@ -145,3 +161,57 @@ export const determineBookmarkOrder = (path: string, plugin: Bookmarks_PluginIns
|
|||
|
||||
return (bookmarkedItemPosition !== undefined && bookmarkedItemPosition >= 0) ? (bookmarkedItemPosition + 1) : undefined
|
||||
}
|
||||
|
||||
// EXPERIMENTAL - operates on internal structures of core Bookmarks plugin
|
||||
|
||||
const createBookmarkFileEntry = (path: string): BookmarkedFile => {
|
||||
return { type: "file", ctime: Date.now(), path: path }
|
||||
}
|
||||
|
||||
const createBookmarkGroupEntry = (title: string): BookmarkedGroup => {
|
||||
return { type: "group", ctime: Date.now(), items: [], title: title }
|
||||
}
|
||||
|
||||
export const bookmarkFolderItem = (item: TAbstractFile, plugin: Bookmarks_PluginInstance, bookmarksGroup?: string) => {
|
||||
bookmarkSiblings([item], plugin, bookmarksGroup)
|
||||
}
|
||||
|
||||
export const bookmarkSiblings = (siblings: Array<TAbstractFile>, plugin: Bookmarks_PluginInstance, bookmarksGroup?: string) => {
|
||||
let items = plugin[BookmarksPlugin_items_collectionName]
|
||||
|
||||
if (siblings.length === 0) return // for sanity
|
||||
|
||||
const parentPathComponents: Array<string> = siblings[0].path.split('/')!
|
||||
parentPathComponents.pop()
|
||||
|
||||
if (bookmarksGroup) {
|
||||
parentPathComponents.unshift(bookmarksGroup)
|
||||
}
|
||||
|
||||
parentPathComponents.forEach((pathSegment) => {
|
||||
let group: BookmarkedGroup|undefined = items.find((it) => it.type === 'group' && it.title === pathSegment) as BookmarkedGroup
|
||||
if (!group) {
|
||||
group = createBookmarkGroupEntry(pathSegment)
|
||||
items.push(group)
|
||||
}
|
||||
items = group.items
|
||||
})
|
||||
|
||||
siblings.forEach((aSibling) => {
|
||||
const siblingName = lastPathComponent(aSibling.path)
|
||||
if (!items.find((it) =>
|
||||
((it.type === 'folder' || it.type === 'file') && it.path === aSibling.path) ||
|
||||
(it.type === 'group' && it.title === siblingName))) {
|
||||
const newEntry: BookmarkedItem = (aSibling instanceof TFolder) ? createBookmarkGroupEntry(siblingName) : createBookmarkFileEntry(aSibling.path)
|
||||
items.push(newEntry)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export const saveDataAndUpdateBookmarkViews = (plugin: Bookmarks_PluginInstance, app: App) => {
|
||||
plugin.saveData()
|
||||
const bookmarksLeafs = app.workspace.getLeavesOfType('bookmarks')
|
||||
bookmarksLeafs?.forEach((leaf) => {
|
||||
(leaf.view as any)?.update?.()
|
||||
})
|
||||
}
|
||||
|
|
|
@ -6,3 +6,8 @@ export function isDefined(o: any): boolean {
|
|||
export function last<T>(o: Array<T>): T | undefined {
|
||||
return o?.length > 0 ? o[o.length - 1] : undefined
|
||||
}
|
||||
|
||||
export function lastPathComponent(path: string): string {
|
||||
const pathComponents = (path ?? '').split('/')
|
||||
return pathComponents.pop()!
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue