#74 - Integration with Bookmarks core plugin and support for indirect drag & drop arrangement
- feature code complete - not reviewed - not tested - no unit tests coverage
This commit is contained in:
parent
08ffd7db9a
commit
56348006ce
|
@ -9,6 +9,7 @@ export enum CustomSortGroupType {
|
||||||
ExactHeadAndTail, // Like W...n or Un...ed, which is shorter variant of typing the entire title
|
ExactHeadAndTail, // Like W...n or Un...ed, which is shorter variant of typing the entire title
|
||||||
HasMetadataField, // Notes (or folder's notes) containing a specific metadata field
|
HasMetadataField, // Notes (or folder's notes) containing a specific metadata field
|
||||||
StarredOnly,
|
StarredOnly,
|
||||||
|
BookmarkedOnly,
|
||||||
HasIcon
|
HasIcon
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,6 +31,8 @@ export enum CustomSortOrder {
|
||||||
byMetadataFieldAlphabeticalReverse,
|
byMetadataFieldAlphabeticalReverse,
|
||||||
byMetadataFieldTrueAlphabeticalReverse,
|
byMetadataFieldTrueAlphabeticalReverse,
|
||||||
standardObsidian, // Let the folder sorting be in hands of Obsidian, whatever user selected in the UI
|
standardObsidian, // Let the folder sorting be in hands of Obsidian, whatever user selected in the UI
|
||||||
|
byBookmarkOrder,
|
||||||
|
byBookmarkOrderReverse,
|
||||||
default = alphabetical
|
default = alphabetical
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ import {
|
||||||
determineFolderDatesIfNeeded,
|
determineFolderDatesIfNeeded,
|
||||||
determineSortingGroup,
|
determineSortingGroup,
|
||||||
FolderItemForSorting,
|
FolderItemForSorting,
|
||||||
matchGroupRegex,
|
matchGroupRegex, sorterByBookmarkOrder, sorterByMetadataField,
|
||||||
SorterFn,
|
SorterFn,
|
||||||
Sorters
|
Sorters
|
||||||
} from './custom-sort';
|
} from './custom-sort';
|
||||||
|
@ -16,6 +16,7 @@ import {
|
||||||
ObsidianIconFolder_PluginInstance,
|
ObsidianIconFolder_PluginInstance,
|
||||||
ObsidianIconFolderPlugin_Data
|
ObsidianIconFolderPlugin_Data
|
||||||
} from "../utils/ObsidianIconFolderPluginSignature";
|
} from "../utils/ObsidianIconFolderPluginSignature";
|
||||||
|
import {determineBookmarkOrder} from "../utils/BookmarksCorePluginSignature";
|
||||||
|
|
||||||
const mockTFile = (basename: string, ext: string, size?: number, ctime?: number, mtime?: number): TFile => {
|
const mockTFile = (basename: string, ext: string, size?: number, ctime?: number, mtime?: number): TFile => {
|
||||||
return {
|
return {
|
||||||
|
@ -2216,7 +2217,7 @@ describe('CustomSortOrder.byMetadataFieldAlphabeticalReverse', () => {
|
||||||
const itemB: Partial<FolderItemForSorting> = {
|
const itemB: Partial<FolderItemForSorting> = {
|
||||||
sortString: 'n123'
|
sortString: 'n123'
|
||||||
}
|
}
|
||||||
const sorter: SorterFn = Sorters[CustomSortOrder.byMetadataFieldAlphabeticalReverse]
|
const sorter: SorterFn = Sorters[CustomSortOrder.byMetadataFieldAlphabetical]
|
||||||
|
|
||||||
// when
|
// when
|
||||||
const result1: number = sorter(itemA as FolderItemForSorting, itemB as FolderItemForSorting)
|
const result1: number = sorter(itemA as FolderItemForSorting, itemB as FolderItemForSorting)
|
||||||
|
@ -2226,6 +2227,25 @@ describe('CustomSortOrder.byMetadataFieldAlphabeticalReverse', () => {
|
||||||
expect(result1).toBe(SORT_FIRST_GOES_EARLIER)
|
expect(result1).toBe(SORT_FIRST_GOES_EARLIER)
|
||||||
expect(result2).toBe(SORT_FIRST_GOES_LATER)
|
expect(result2).toBe(SORT_FIRST_GOES_LATER)
|
||||||
})
|
})
|
||||||
|
it('should put the item with metadata later if the second one has no metadata (reverse order)', () => {
|
||||||
|
// given
|
||||||
|
const itemA: Partial<FolderItemForSorting> = {
|
||||||
|
metadataFieldValue: '15',
|
||||||
|
sortString: 'n123'
|
||||||
|
}
|
||||||
|
const itemB: Partial<FolderItemForSorting> = {
|
||||||
|
sortString: 'n123'
|
||||||
|
}
|
||||||
|
const sorter: SorterFn = Sorters[CustomSortOrder.byMetadataFieldAlphabeticalReverse]
|
||||||
|
|
||||||
|
// when
|
||||||
|
const result1: number = sorter(itemA as FolderItemForSorting, itemB as FolderItemForSorting)
|
||||||
|
const result2: number = sorter(itemB as FolderItemForSorting, itemA as FolderItemForSorting)
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(result1).toBe(SORT_FIRST_GOES_LATER)
|
||||||
|
expect(result2).toBe(SORT_FIRST_GOES_EARLIER)
|
||||||
|
})
|
||||||
it('should correctly fallback to alphabetical reverse if no metadata on both items', () => {
|
it('should correctly fallback to alphabetical reverse if no metadata on both items', () => {
|
||||||
// given
|
// given
|
||||||
const itemA: Partial<FolderItemForSorting> = {
|
const itemA: Partial<FolderItemForSorting> = {
|
||||||
|
@ -2247,3 +2267,74 @@ describe('CustomSortOrder.byMetadataFieldAlphabeticalReverse', () => {
|
||||||
expect(result3).toBe(SORT_ITEMS_ARE_EQUAL)
|
expect(result3).toBe(SORT_ITEMS_ARE_EQUAL)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('sorterByMetadataField', () => {
|
||||||
|
it.each([
|
||||||
|
[true,'abc','def',-1, 'a', 'a'],
|
||||||
|
[true,'xyz','klm',1, 'b', 'b'],
|
||||||
|
[true,'mmm','mmm',0, 'c', 'c'],
|
||||||
|
[true,'mmm','mmm',-1, 'd', 'e'],
|
||||||
|
[true,'mmm','mmm',1, 'e', 'd'],
|
||||||
|
[true,'abc',undefined,-1, 'a','a'],
|
||||||
|
[true,undefined,'klm',1, 'b','b'],
|
||||||
|
[true,undefined,undefined,0, 'a','a'],
|
||||||
|
[true,undefined,undefined,-1, 'a','b'],
|
||||||
|
[true,undefined,undefined,1, 'd','c'],
|
||||||
|
[false,'abc','def',1, 'a', 'a'],
|
||||||
|
[false,'xyz','klm',-1, 'b', 'b'],
|
||||||
|
[false,'mmm','mmm',0, 'c', 'c'],
|
||||||
|
[false,'mmm','mmm',1, 'd', 'e'],
|
||||||
|
[false,'mmm','mmm',-1, 'e', 'd'],
|
||||||
|
[false,'abc',undefined,1, 'a','a'],
|
||||||
|
[false,undefined,'klm',-1, 'b','b'],
|
||||||
|
[false,undefined,undefined,0, 'a','a'],
|
||||||
|
[false,undefined,undefined,1, 'a','b'],
|
||||||
|
[false,undefined,undefined,-1, 'd','c'],
|
||||||
|
|
||||||
|
])('straight order %s, comparing %s and %s should return %s for sortStrings %s and %s',
|
||||||
|
(straight: boolean, metadataA: string|undefined, metadataB: string|undefined, order: number, sortStringA: string, sortStringB) => {
|
||||||
|
const sorterFn = sorterByMetadataField(!straight, false)
|
||||||
|
const itemA: Partial<FolderItemForSorting> = {metadataFieldValue: metadataA, sortString: sortStringA}
|
||||||
|
const itemB: Partial<FolderItemForSorting> = {metadataFieldValue: metadataB, sortString: sortStringB}
|
||||||
|
const result = sorterFn(itemA as FolderItemForSorting, itemB as FolderItemForSorting)
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(result).toBe(order)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('sorterByBookmarkOrder', () => {
|
||||||
|
it.each([
|
||||||
|
[true,10,20,-1, 'a', 'a'],
|
||||||
|
[true,20,10,1, 'b', 'b'],
|
||||||
|
[true,30,30,0, 'c', 'c'], // not possible in reality - each bookmark order is unique by definition - covered for clarity
|
||||||
|
[true,1,1,0, 'd', 'e'], // ----//----
|
||||||
|
[true,2,2,0, 'e', 'd'], // ----//----
|
||||||
|
[true,3,undefined,-1, 'a','a'],
|
||||||
|
[true,undefined,4,1, 'b','b'],
|
||||||
|
[true,undefined,undefined,0, 'a','a'],
|
||||||
|
[true,undefined,undefined,-1, 'a','b'],
|
||||||
|
[true,undefined,undefined,1, 'd','c'],
|
||||||
|
[false,10,20,1, 'a', 'a'],
|
||||||
|
[false,20,10,-1, 'b', 'b'],
|
||||||
|
[false,30,30,0, 'c', 'c'], // not possible in reality - each bookmark order is unique by definition - covered for clarity
|
||||||
|
[false,1,1,0, 'd', 'e'], // ------//-----
|
||||||
|
[false,2,2,0, 'e', 'd'], // ------//-----
|
||||||
|
[false,3,undefined,1, 'a','a'],
|
||||||
|
[false,undefined,4,-1, 'b','b'],
|
||||||
|
[false,undefined,undefined,0, 'a','a'],
|
||||||
|
[false,undefined,undefined,1, 'a','b'],
|
||||||
|
[false,undefined,undefined,-1, 'd','c'],
|
||||||
|
|
||||||
|
])('straight order %s, comparing %s and %s should return %s for sortStrings %s and %s',
|
||||||
|
(straight: boolean, bookmarkA: number|undefined, bookmarkB: number|undefined, order: number, sortStringA: string, sortStringB) => {
|
||||||
|
const sorterFn = sorterByBookmarkOrder(!straight, false)
|
||||||
|
const itemA: Partial<FolderItemForSorting> = {bookmarkedIdx: bookmarkA, sortString: sortStringA}
|
||||||
|
const itemB: Partial<FolderItemForSorting> = {bookmarkedIdx: bookmarkB, sortString: sortStringB}
|
||||||
|
const result = sorterFn(itemA as FolderItemForSorting, itemB as FolderItemForSorting)
|
||||||
|
const normalizedResult = result < 0 ? -1 : ((result > 0) ? 1 : result)
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(normalizedResult).toBe(order)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
|
@ -1,26 +1,9 @@
|
||||||
import {
|
import {FrontMatterCache, requireApiVersion, TAbstractFile, TFile, TFolder} from 'obsidian';
|
||||||
App,
|
import {determineStarredStatusOf, getStarredPlugin, Starred_PluginInstance} from '../utils/StarredPluginSignature';
|
||||||
CommunityPlugin,
|
|
||||||
FrontMatterCache,
|
|
||||||
InstalledPlugin,
|
|
||||||
requireApiVersion,
|
|
||||||
TAbstractFile,
|
|
||||||
TFile,
|
|
||||||
TFolder
|
|
||||||
} from 'obsidian';
|
|
||||||
import {
|
|
||||||
determineStarredStatusOf,
|
|
||||||
getStarredPlugin,
|
|
||||||
Starred_PluginInstance,
|
|
||||||
StarredPlugin_findStarredFile_methodName
|
|
||||||
} from '../utils/StarredPluginSignature';
|
|
||||||
import {
|
import {
|
||||||
determineIconOf,
|
determineIconOf,
|
||||||
getIconFolderPlugin,
|
getIconFolderPlugin,
|
||||||
FolderIconObject,
|
ObsidianIconFolder_PluginInstance
|
||||||
ObsidianIconFolder_PluginInstance,
|
|
||||||
ObsidianIconFolderPlugin_Data,
|
|
||||||
ObsidianIconFolderPlugin_getData_methodName
|
|
||||||
} from '../utils/ObsidianIconFolderPluginSignature'
|
} from '../utils/ObsidianIconFolderPluginSignature'
|
||||||
import {
|
import {
|
||||||
CustomSortGroup,
|
CustomSortGroup,
|
||||||
|
@ -32,6 +15,11 @@ import {
|
||||||
RegExpSpec
|
RegExpSpec
|
||||||
} from "./custom-sort-types";
|
} from "./custom-sort-types";
|
||||||
import {isDefined} from "../utils/utils";
|
import {isDefined} from "../utils/utils";
|
||||||
|
import {
|
||||||
|
Bookmarks_PluginInstance,
|
||||||
|
determineBookmarkOrder,
|
||||||
|
getBookmarksPlugin
|
||||||
|
} from "../utils/BookmarksCorePluginSignature";
|
||||||
|
|
||||||
let CollatorCompare = new Intl.Collator(undefined, {
|
let CollatorCompare = new Intl.Collator(undefined, {
|
||||||
usage: "sort",
|
usage: "sort",
|
||||||
|
@ -45,6 +33,21 @@ let CollatorTrueAlphabeticalCompare = new Intl.Collator(undefined, {
|
||||||
numeric: false,
|
numeric: false,
|
||||||
}).compare;
|
}).compare;
|
||||||
|
|
||||||
|
|
||||||
|
export const SORTSPEC_FOR_AUTOMATIC_BOOKMARKS_INTEGRATION: CustomSortSpec = {
|
||||||
|
defaultOrder: CustomSortOrder.byBookmarkOrder,
|
||||||
|
groups: [
|
||||||
|
{
|
||||||
|
order: CustomSortOrder.byBookmarkOrder,
|
||||||
|
type: CustomSortGroupType.Outsiders
|
||||||
|
}
|
||||||
|
],
|
||||||
|
outsidersGroupIdx: 0,
|
||||||
|
targetFoldersPaths: [
|
||||||
|
"Spec applied automatically to folder not having explicit spec when automatic integration with bookmarks is enabled"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
export interface FolderItemForSorting {
|
export interface FolderItemForSorting {
|
||||||
path: string
|
path: string
|
||||||
groupIdx?: number // the index itself represents order for groups
|
groupIdx?: number // the index itself represents order for groups
|
||||||
|
@ -55,6 +58,7 @@ export interface FolderItemForSorting {
|
||||||
mtime: number // for a file mtime is obvious, for a folder = date of most recently modified child file
|
mtime: number // for a file mtime is obvious, for a folder = date of most recently modified child file
|
||||||
isFolder: boolean
|
isFolder: boolean
|
||||||
folder?: TFolder
|
folder?: TFolder
|
||||||
|
bookmarkedIdx?: number // derived from Bookmarks core plugin position
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SorterFn = (a: FolderItemForSorting, b: FolderItemForSorting) => number
|
export type SorterFn = (a: FolderItemForSorting, b: FolderItemForSorting) => number
|
||||||
|
@ -65,7 +69,7 @@ const TrueAlphabetical: boolean = true
|
||||||
const ReverseOrder: boolean = true
|
const ReverseOrder: boolean = true
|
||||||
const StraightOrder: boolean = false
|
const StraightOrder: boolean = false
|
||||||
|
|
||||||
const sorterByMetadataField:(reverseOrder?: boolean, trueAlphabetical?: boolean) => SorterFn = (reverseOrder: boolean, trueAlphabetical?: boolean) => {
|
export const sorterByMetadataField:(reverseOrder?: boolean, trueAlphabetical?: boolean) => SorterFn = (reverseOrder: boolean, trueAlphabetical?: boolean) => {
|
||||||
const collatorCompareFn: CollatorCompareFn = trueAlphabetical ? CollatorTrueAlphabeticalCompare : CollatorCompare
|
const collatorCompareFn: CollatorCompareFn = trueAlphabetical ? CollatorTrueAlphabeticalCompare : CollatorCompare
|
||||||
return (a: FolderItemForSorting, b: FolderItemForSorting) => {
|
return (a: FolderItemForSorting, b: FolderItemForSorting) => {
|
||||||
if (reverseOrder) {
|
if (reverseOrder) {
|
||||||
|
@ -81,13 +85,31 @@ const sorterByMetadataField:(reverseOrder?: boolean, trueAlphabetical?: boolean)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Item with metadata goes before the w/o metadata
|
// Item with metadata goes before the w/o metadata
|
||||||
if (a.metadataFieldValue) return reverseOrder ? 1 : -1
|
if (a.metadataFieldValue) return -1
|
||||||
if (b.metadataFieldValue) return reverseOrder ? -1 : 1
|
if (b.metadataFieldValue) return 1
|
||||||
// Fallback -> requested sort by metadata, yet none of two items contain it, use alphabetical by name
|
// Fallback -> requested sort by metadata, yet none of two items contain it, use alphabetical by name
|
||||||
return collatorCompareFn(a.sortString, b.sortString)
|
return collatorCompareFn(a.sortString, b.sortString)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const sorterByBookmarkOrder:(reverseOrder?: boolean, trueAlphabetical?: boolean) => SorterFn = (reverseOrder: boolean, trueAlphabetical?: boolean) => {
|
||||||
|
const collatorCompareFn: CollatorCompareFn = trueAlphabetical ? CollatorTrueAlphabeticalCompare : CollatorCompare
|
||||||
|
return (a: FolderItemForSorting, b: FolderItemForSorting) => {
|
||||||
|
if (reverseOrder) {
|
||||||
|
[a, b] = [b, a]
|
||||||
|
}
|
||||||
|
if (a.bookmarkedIdx && b.bookmarkedIdx) {
|
||||||
|
// By design the bookmark idx is unique per each item, so no need for secondary sorting if they are equal
|
||||||
|
return a.bookmarkedIdx - b.bookmarkedIdx
|
||||||
|
}
|
||||||
|
// Item with bookmark order goes before the w/o bookmark info
|
||||||
|
if (a.bookmarkedIdx) return -1
|
||||||
|
if (b.bookmarkedIdx) return 1
|
||||||
|
// Fallback -> requested sort by bookmark order, yet none of two items contain it, use alphabetical by name
|
||||||
|
return collatorCompareFn(a.sortString, b.sortString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export let Sorters: { [key in CustomSortOrder]: SorterFn } = {
|
export let Sorters: { [key in CustomSortOrder]: SorterFn } = {
|
||||||
[CustomSortOrder.alphabetical]: (a: FolderItemForSorting, b: FolderItemForSorting) => CollatorCompare(a.sortString, b.sortString),
|
[CustomSortOrder.alphabetical]: (a: FolderItemForSorting, b: FolderItemForSorting) => CollatorCompare(a.sortString, b.sortString),
|
||||||
[CustomSortOrder.trueAlphabetical]: (a: FolderItemForSorting, b: FolderItemForSorting) => CollatorTrueAlphabeticalCompare(a.sortString, b.sortString),
|
[CustomSortOrder.trueAlphabetical]: (a: FolderItemForSorting, b: FolderItemForSorting) => CollatorTrueAlphabeticalCompare(a.sortString, b.sortString),
|
||||||
|
@ -105,6 +127,8 @@ export let Sorters: { [key in CustomSortOrder]: SorterFn } = {
|
||||||
[CustomSortOrder.byMetadataFieldTrueAlphabetical]: sorterByMetadataField(StraightOrder, TrueAlphabetical),
|
[CustomSortOrder.byMetadataFieldTrueAlphabetical]: sorterByMetadataField(StraightOrder, TrueAlphabetical),
|
||||||
[CustomSortOrder.byMetadataFieldAlphabeticalReverse]: sorterByMetadataField(ReverseOrder),
|
[CustomSortOrder.byMetadataFieldAlphabeticalReverse]: sorterByMetadataField(ReverseOrder),
|
||||||
[CustomSortOrder.byMetadataFieldTrueAlphabeticalReverse]: sorterByMetadataField(ReverseOrder, TrueAlphabetical),
|
[CustomSortOrder.byMetadataFieldTrueAlphabeticalReverse]: sorterByMetadataField(ReverseOrder, TrueAlphabetical),
|
||||||
|
[CustomSortOrder.byBookmarkOrder]: sorterByBookmarkOrder(StraightOrder),
|
||||||
|
[CustomSortOrder.byBookmarkOrderReverse]: sorterByBookmarkOrder(ReverseOrder),
|
||||||
|
|
||||||
// This is a fallback entry which should not be used - the plugin code should refrain from custom sorting at all
|
// This is a fallback entry which should not be used - the plugin code should refrain from custom sorting at all
|
||||||
[CustomSortOrder.standardObsidian]: (a: FolderItemForSorting, b: FolderItemForSorting) => CollatorCompare(a.sortString, b.sortString),
|
[CustomSortOrder.standardObsidian]: (a: FolderItemForSorting, b: FolderItemForSorting) => CollatorCompare(a.sortString, b.sortString),
|
||||||
|
@ -164,6 +188,7 @@ export const matchGroupRegex = (theRegex: RegExpSpec, nameForMatching: string):
|
||||||
|
|
||||||
export interface Context {
|
export interface Context {
|
||||||
starredPluginInstance?: Starred_PluginInstance
|
starredPluginInstance?: Starred_PluginInstance
|
||||||
|
bookmarksPluginInstance?: Bookmarks_PluginInstance
|
||||||
iconFolderPluginInstance?: ObsidianIconFolder_PluginInstance
|
iconFolderPluginInstance?: ObsidianIconFolder_PluginInstance
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,6 +196,7 @@ export const determineSortingGroup = function (entry: TFile | TFolder, spec: Cus
|
||||||
let groupIdx: number
|
let groupIdx: number
|
||||||
let determined: boolean = false
|
let determined: boolean = false
|
||||||
let matchedGroup: string | null | undefined
|
let matchedGroup: string | null | undefined
|
||||||
|
let bookmarkedIdx: number | undefined
|
||||||
let metadataValueToSortBy: string | undefined
|
let metadataValueToSortBy: string | undefined
|
||||||
const aFolder: boolean = isFolder(entry)
|
const aFolder: boolean = isFolder(entry)
|
||||||
const aFile: boolean = !aFolder
|
const aFile: boolean = !aFolder
|
||||||
|
@ -264,12 +290,20 @@ export const determineSortingGroup = function (entry: TFile | TFolder, spec: Cus
|
||||||
break
|
break
|
||||||
case CustomSortGroupType.StarredOnly:
|
case CustomSortGroupType.StarredOnly:
|
||||||
if (ctx?.starredPluginInstance) {
|
if (ctx?.starredPluginInstance) {
|
||||||
let starred: boolean = determineStarredStatusOf(entry, aFile, ctx.starredPluginInstance)
|
const starred: boolean = determineStarredStatusOf(entry, aFile, ctx.starredPluginInstance)
|
||||||
if (starred) {
|
if (starred) {
|
||||||
determined = true
|
determined = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
case CustomSortGroupType.BookmarkedOnly:
|
||||||
|
if (ctx?.bookmarksPluginInstance) {
|
||||||
|
const bookmarkOrder: number | undefined = determineBookmarkOrder(entry.path, ctx.bookmarksPluginInstance)
|
||||||
|
if (bookmarkOrder) { // safe ==> orders intentionally start from 1
|
||||||
|
determined = true
|
||||||
|
bookmarkedIdx = bookmarkOrder
|
||||||
|
}
|
||||||
|
}
|
||||||
case CustomSortGroupType.HasIcon:
|
case CustomSortGroupType.HasIcon:
|
||||||
if(ctx?.iconFolderPluginInstance) {
|
if(ctx?.iconFolderPluginInstance) {
|
||||||
let iconName: string | undefined = determineIconOf(entry, ctx.iconFolderPluginInstance)
|
let iconName: string | undefined = determineIconOf(entry, ctx.iconFolderPluginInstance)
|
||||||
|
@ -363,7 +397,8 @@ export const determineSortingGroup = function (entry: TFile | TFolder, spec: Cus
|
||||||
folder: aFolder ? (entry as TFolder) : undefined,
|
folder: aFolder ? (entry as TFolder) : undefined,
|
||||||
path: entry.path,
|
path: entry.path,
|
||||||
ctime: aFile ? entryAsTFile.stat.ctime : DEFAULT_FOLDER_CTIME,
|
ctime: aFile ? entryAsTFile.stat.ctime : DEFAULT_FOLDER_CTIME,
|
||||||
mtime: aFile ? entryAsTFile.stat.mtime : DEFAULT_FOLDER_MTIME
|
mtime: aFile ? entryAsTFile.stat.mtime : DEFAULT_FOLDER_MTIME,
|
||||||
|
bookmarkedIdx: bookmarkedIdx
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -380,6 +415,17 @@ export const sortOrderNeedsFolderDates = (order: CustomSortOrder | undefined, se
|
||||||
|| SortOrderRequiringFolderDate.has(secondary ?? CustomSortOrder.standardObsidian)
|
|| SortOrderRequiringFolderDate.has(secondary ?? CustomSortOrder.standardObsidian)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const SortOrderRequiringBookmarksOrder = new Set<CustomSortOrder>([
|
||||||
|
CustomSortOrder.byBookmarkOrder,
|
||||||
|
CustomSortOrder.byBookmarkOrderReverse
|
||||||
|
])
|
||||||
|
|
||||||
|
export const sortOrderNeedsBookmarksOrder = (order: CustomSortOrder | undefined, secondary?: CustomSortOrder): boolean => {
|
||||||
|
// The CustomSortOrder.standardObsidian used as default because it doesn't require bookmarks order
|
||||||
|
return SortOrderRequiringBookmarksOrder.has(order ?? CustomSortOrder.standardObsidian)
|
||||||
|
|| SortOrderRequiringBookmarksOrder.has(secondary ?? CustomSortOrder.standardObsidian)
|
||||||
|
}
|
||||||
|
|
||||||
// Syntax sugar for readability
|
// Syntax sugar for readability
|
||||||
export type ModifiedTime = number
|
export type ModifiedTime = number
|
||||||
export type CreatedTime = number
|
export type CreatedTime = number
|
||||||
|
@ -422,10 +468,32 @@ export const determineFolderDatesIfNeeded = (folderItems: Array<FolderItemForSor
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Order by bookmarks order can be applied independently of grouping by bookmarked status
|
||||||
|
// This function determines the bookmarked order if the sorting criteria (of group or entire folder) requires it
|
||||||
|
export const determineBookmarksOrderIfNeeded = (folderItems: Array<FolderItemForSorting>, sortingSpec: CustomSortSpec, plugin: Bookmarks_PluginInstance) => {
|
||||||
|
if (!plugin) return
|
||||||
|
|
||||||
|
folderItems.forEach((item) => {
|
||||||
|
const folderDefaultSortRequiresBookmarksOrder: boolean = !!(sortingSpec.defaultOrder && sortOrderNeedsBookmarksOrder(sortingSpec.defaultOrder))
|
||||||
|
let groupSortRequiresBookmarksOrder: boolean = false
|
||||||
|
if (!folderDefaultSortRequiresBookmarksOrder) {
|
||||||
|
const groupIdx: number | undefined = item.groupIdx
|
||||||
|
if (groupIdx !== undefined) {
|
||||||
|
const groupOrder: CustomSortOrder | undefined = sortingSpec.groups[groupIdx].order
|
||||||
|
groupSortRequiresBookmarksOrder = sortOrderNeedsBookmarksOrder(groupOrder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (folderDefaultSortRequiresBookmarksOrder || groupSortRequiresBookmarksOrder) {
|
||||||
|
item.bookmarkedIdx = determineBookmarkOrder(item.path, plugin)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export const folderSort = function (sortingSpec: CustomSortSpec, order: string[]) {
|
export const folderSort = function (sortingSpec: CustomSortSpec, order: string[]) {
|
||||||
let fileExplorer = this.fileExplorer
|
let fileExplorer = this.fileExplorer
|
||||||
sortingSpec._mCache = sortingSpec.plugin?.app.metadataCache
|
sortingSpec._mCache = sortingSpec.plugin?.app.metadataCache
|
||||||
const starredPluginInstance: Starred_PluginInstance | undefined = getStarredPlugin(sortingSpec?.plugin?.app)
|
const starredPluginInstance: Starred_PluginInstance | undefined = getStarredPlugin(sortingSpec?.plugin?.app)
|
||||||
|
const bookmarksPluginInstance: Bookmarks_PluginInstance | undefined = getBookmarksPlugin(sortingSpec?.plugin?.app)
|
||||||
const iconFolderPluginInstance: ObsidianIconFolder_PluginInstance | undefined = getIconFolderPlugin(sortingSpec?.plugin?.app)
|
const iconFolderPluginInstance: ObsidianIconFolder_PluginInstance | undefined = getIconFolderPlugin(sortingSpec?.plugin?.app)
|
||||||
|
|
||||||
const folderItems: Array<FolderItemForSorting> = (sortingSpec.itemsToHide ?
|
const folderItems: Array<FolderItemForSorting> = (sortingSpec.itemsToHide ?
|
||||||
|
@ -437,6 +505,7 @@ export const folderSort = function (sortingSpec: CustomSortSpec, order: string[]
|
||||||
.map((entry: TFile | TFolder) => {
|
.map((entry: TFile | TFolder) => {
|
||||||
const itemForSorting: FolderItemForSorting = determineSortingGroup(entry, sortingSpec, {
|
const itemForSorting: FolderItemForSorting = determineSortingGroup(entry, sortingSpec, {
|
||||||
starredPluginInstance: starredPluginInstance,
|
starredPluginInstance: starredPluginInstance,
|
||||||
|
bookmarksPluginInstance: bookmarksPluginInstance,
|
||||||
iconFolderPluginInstance: iconFolderPluginInstance
|
iconFolderPluginInstance: iconFolderPluginInstance
|
||||||
})
|
})
|
||||||
return itemForSorting
|
return itemForSorting
|
||||||
|
@ -445,6 +514,10 @@ export const folderSort = function (sortingSpec: CustomSortSpec, order: string[]
|
||||||
// Finally, for advanced sorting by modified date, for some folders the modified date has to be determined
|
// Finally, for advanced sorting by modified date, for some folders the modified date has to be determined
|
||||||
determineFolderDatesIfNeeded(folderItems, sortingSpec)
|
determineFolderDatesIfNeeded(folderItems, sortingSpec)
|
||||||
|
|
||||||
|
if (bookmarksPluginInstance) {
|
||||||
|
determineBookmarksOrderIfNeeded(folderItems, sortingSpec, bookmarksPluginInstance)
|
||||||
|
}
|
||||||
|
|
||||||
folderItems.sort(function (itA: FolderItemForSorting, itB: FolderItemForSorting) {
|
folderItems.sort(function (itA: FolderItemForSorting, itB: FolderItemForSorting) {
|
||||||
return compareTwoItems(itA, itB, sortingSpec);
|
return compareTwoItems(itA, itB, sortingSpec);
|
||||||
});
|
});
|
||||||
|
|
|
@ -37,6 +37,13 @@ starred:
|
||||||
/:files starred:
|
/:files starred:
|
||||||
/folders starred:
|
/folders starred:
|
||||||
|
|
||||||
|
:::: folder of bookmarks
|
||||||
|
< by-bookmarks-order
|
||||||
|
/: bookmarked:
|
||||||
|
< by-bookmarks-order
|
||||||
|
/ Abc
|
||||||
|
> by-bookmarks-order
|
||||||
|
|
||||||
:::: Conceptual model
|
:::: Conceptual model
|
||||||
/: Entities
|
/: Entities
|
||||||
%
|
%
|
||||||
|
@ -95,6 +102,13 @@ target-folder: tricky folder 2
|
||||||
/:files starred:
|
/:files starred:
|
||||||
/folders starred:
|
/folders starred:
|
||||||
|
|
||||||
|
target-folder: folder of bookmarks
|
||||||
|
order-asc: by-bookmarks-order
|
||||||
|
/:files bookmarked:
|
||||||
|
order-asc: by-bookmarks-order
|
||||||
|
/folders Abc
|
||||||
|
order-desc: by-bookmarks-order
|
||||||
|
|
||||||
:::: Conceptual model
|
:::: Conceptual model
|
||||||
/:files Entities
|
/:files Entities
|
||||||
%
|
%
|
||||||
|
@ -205,6 +219,30 @@ const expectedSortSpecsExampleA: { [key: string]: CustomSortSpec } = {
|
||||||
'tricky folder 2'
|
'tricky folder 2'
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"folder of bookmarks": {
|
||||||
|
defaultOrder: CustomSortOrder.byBookmarkOrder,
|
||||||
|
groups: [
|
||||||
|
{
|
||||||
|
filesOnly: true,
|
||||||
|
order: CustomSortOrder.byBookmarkOrder,
|
||||||
|
type: CustomSortGroupType.BookmarkedOnly
|
||||||
|
},
|
||||||
|
{
|
||||||
|
exactText: "Abc",
|
||||||
|
foldersOnly: true,
|
||||||
|
order: CustomSortOrder.byBookmarkOrderReverse,
|
||||||
|
type: CustomSortGroupType.ExactName
|
||||||
|
},
|
||||||
|
{
|
||||||
|
order: CustomSortOrder.byBookmarkOrder,
|
||||||
|
type: CustomSortGroupType.Outsiders
|
||||||
|
}
|
||||||
|
],
|
||||||
|
outsidersGroupIdx: 2,
|
||||||
|
targetFoldersPaths: [
|
||||||
|
"folder of bookmarks"
|
||||||
|
]
|
||||||
|
},
|
||||||
"Conceptual model": {
|
"Conceptual model": {
|
||||||
groups: [{
|
groups: [{
|
||||||
exactText: "Entities",
|
exactText: "Entities",
|
||||||
|
|
|
@ -114,6 +114,7 @@ const OrderLiterals: { [key: string]: CustomSortOrderAscDescPair } = {
|
||||||
'modified': {asc: CustomSortOrder.byModifiedTime, desc: CustomSortOrder.byModifiedTimeReverse},
|
'modified': {asc: CustomSortOrder.byModifiedTime, desc: CustomSortOrder.byModifiedTimeReverse},
|
||||||
'advanced modified': {asc: CustomSortOrder.byModifiedTimeAdvanced, desc: CustomSortOrder.byModifiedTimeReverseAdvanced},
|
'advanced modified': {asc: CustomSortOrder.byModifiedTimeAdvanced, desc: CustomSortOrder.byModifiedTimeReverseAdvanced},
|
||||||
'advanced created': {asc: CustomSortOrder.byCreatedTimeAdvanced, desc: CustomSortOrder.byCreatedTimeReverseAdvanced},
|
'advanced created': {asc: CustomSortOrder.byCreatedTimeAdvanced, desc: CustomSortOrder.byCreatedTimeReverseAdvanced},
|
||||||
|
'by-bookmarks-order': {asc: CustomSortOrder.byBookmarkOrder, desc: CustomSortOrder.byBookmarkOrderReverse},
|
||||||
|
|
||||||
// Advanced, for edge cases of secondary sorting, when if regexp match is the same, override the alphabetical sorting by full name
|
// Advanced, for edge cases of secondary sorting, when if regexp match is the same, override the alphabetical sorting by full name
|
||||||
'a-z, created': {
|
'a-z, created': {
|
||||||
|
@ -207,6 +208,8 @@ const HideItemVerboseLexeme: string = '/--hide:'
|
||||||
|
|
||||||
const MetadataFieldIndicatorLexeme: string = 'with-metadata:'
|
const MetadataFieldIndicatorLexeme: string = 'with-metadata:'
|
||||||
|
|
||||||
|
const BookmarkedItemIndicatorLexeme: string = 'bookmarked:'
|
||||||
|
|
||||||
const StarredItemsIndicatorLexeme: string = 'starred:'
|
const StarredItemsIndicatorLexeme: string = 'starred:'
|
||||||
|
|
||||||
const IconIndicatorLexeme: string = 'with-icon:'
|
const IconIndicatorLexeme: string = 'with-icon:'
|
||||||
|
@ -1514,6 +1517,13 @@ export class SortingSpecProcessor {
|
||||||
foldersOnly: spec.foldersOnly,
|
foldersOnly: spec.foldersOnly,
|
||||||
matchFilenameWithExt: spec.matchFilenameWithExt
|
matchFilenameWithExt: spec.matchFilenameWithExt
|
||||||
}
|
}
|
||||||
|
} else if (theOnly.startsWith(BookmarkedItemIndicatorLexeme)) {
|
||||||
|
return {
|
||||||
|
type: CustomSortGroupType.BookmarkedOnly,
|
||||||
|
filesOnly: spec.filesOnly,
|
||||||
|
foldersOnly: spec.foldersOnly,
|
||||||
|
matchFilenameWithExt: spec.matchFilenameWithExt
|
||||||
|
}
|
||||||
} else if (theOnly.startsWith(IconIndicatorLexeme)) {
|
} else if (theOnly.startsWith(IconIndicatorLexeme)) {
|
||||||
const iconName: string | undefined = extractIdentifier(theOnly.substring(IconIndicatorLexeme.length))
|
const iconName: string | undefined = extractIdentifier(theOnly.substring(IconIndicatorLexeme.length))
|
||||||
return {
|
return {
|
||||||
|
|
22
src/main.ts
22
src/main.ts
|
@ -16,7 +16,7 @@ import {
|
||||||
Vault
|
Vault
|
||||||
} from 'obsidian';
|
} from 'obsidian';
|
||||||
import {around} from 'monkey-around';
|
import {around} from 'monkey-around';
|
||||||
import {folderSort} from './custom-sort/custom-sort';
|
import {folderSort, SORTSPEC_FOR_AUTOMATIC_BOOKMARKS_INTEGRATION} from './custom-sort/custom-sort';
|
||||||
import {SortingSpecProcessor, SortSpecsCollection} from './custom-sort/sorting-spec-processor';
|
import {SortingSpecProcessor, SortSpecsCollection} from './custom-sort/sorting-spec-processor';
|
||||||
import {CustomSortOrder, CustomSortSpec} from './custom-sort/custom-sort-types';
|
import {CustomSortOrder, CustomSortSpec} from './custom-sort/custom-sort-types';
|
||||||
|
|
||||||
|
@ -36,6 +36,7 @@ interface CustomSortPluginSettings {
|
||||||
statusBarEntryEnabled: boolean
|
statusBarEntryEnabled: boolean
|
||||||
notificationsEnabled: boolean
|
notificationsEnabled: boolean
|
||||||
mobileNotificationsEnabled: boolean
|
mobileNotificationsEnabled: boolean
|
||||||
|
enableAutomaticBookmarksOrderIntegration: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_SETTINGS: CustomSortPluginSettings = {
|
const DEFAULT_SETTINGS: CustomSortPluginSettings = {
|
||||||
|
@ -43,7 +44,8 @@ const DEFAULT_SETTINGS: CustomSortPluginSettings = {
|
||||||
suspended: true, // if false by default, it would be hard to handle the auto-parse after plugin install
|
suspended: true, // if false by default, it would be hard to handle the auto-parse after plugin install
|
||||||
statusBarEntryEnabled: true,
|
statusBarEntryEnabled: true,
|
||||||
notificationsEnabled: true,
|
notificationsEnabled: true,
|
||||||
mobileNotificationsEnabled: false
|
mobileNotificationsEnabled: false,
|
||||||
|
enableAutomaticBookmarksOrderIntegration: false
|
||||||
}
|
}
|
||||||
|
|
||||||
const SORTSPEC_FILE_NAME: string = 'sortspec.md'
|
const SORTSPEC_FILE_NAME: string = 'sortspec.md'
|
||||||
|
@ -347,6 +349,9 @@ export default class CustomSortPlugin extends Plugin {
|
||||||
sortSpec = null // A folder is explicitly excluded from custom sorting plugin
|
sortSpec = null // A folder is explicitly excluded from custom sorting plugin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!sortSpec && plugin.settings.enableAutomaticBookmarksOrderIntegration) {
|
||||||
|
sortSpec = SORTSPEC_FOR_AUTOMATIC_BOOKMARKS_INTEGRATION
|
||||||
|
}
|
||||||
if (sortSpec) {
|
if (sortSpec) {
|
||||||
sortSpec.plugin = plugin
|
sortSpec.plugin = plugin
|
||||||
return folderSort.call(this, sortSpec, ...args);
|
return folderSort.call(this, sortSpec, ...args);
|
||||||
|
@ -476,5 +481,18 @@ class CustomSortSettingTab extends PluginSettingTab {
|
||||||
this.plugin.settings.mobileNotificationsEnabled = value;
|
this.plugin.settings.mobileNotificationsEnabled = value;
|
||||||
await this.plugin.saveSettings();
|
await this.plugin.saveSettings();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
new Setting(containerEl)
|
||||||
|
.setName('Enable automatic integration with core Bookmarks plugin')
|
||||||
|
// TODO: add a nice description here
|
||||||
|
.setDesc('Details TBD. TODO: add a nice description here')
|
||||||
|
.addToggle(toggle => toggle
|
||||||
|
.setValue(this.plugin.settings.enableAutomaticBookmarksOrderIntegration)
|
||||||
|
.onChange(async (value) => {
|
||||||
|
this.plugin.settings.enableAutomaticBookmarksOrderIntegration = value;
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
}));
|
||||||
|
|
||||||
|
// TODO: expose additional configuration setting to specify group path in Bookmarks, if auto-integration with bookmarks is enabled
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,145 @@
|
||||||
|
import {App, InstalledPlugin, PluginInstance} from "obsidian";
|
||||||
|
|
||||||
|
const BookmarksPlugin_getBookmarks_methodName = 'getBookmarks'
|
||||||
|
|
||||||
|
type Path = string
|
||||||
|
|
||||||
|
// Only relevant types of bookmarked items considered here
|
||||||
|
// The full set of types also includes 'search', canvas, graph, maybe more to come
|
||||||
|
type BookmarkedItem = BookmarkedFile | BookmarkedFolder | BookmarkedGroup
|
||||||
|
|
||||||
|
// Either a file, a folder or header/block inside a file
|
||||||
|
interface BookmarkWithPath {
|
||||||
|
path: Path
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BookmarkedFile {
|
||||||
|
type: 'file'
|
||||||
|
path: Path
|
||||||
|
subpath?: string // Anchor within the file
|
||||||
|
title?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BookmarkedFolder {
|
||||||
|
type: 'folder'
|
||||||
|
path: Path
|
||||||
|
title?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BookmarkedGroup {
|
||||||
|
type: 'group'
|
||||||
|
items: Array<BookmarkedItem>
|
||||||
|
title?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type BookmarkedItemPath = string
|
||||||
|
|
||||||
|
export interface OrderedBookmarkedItem {
|
||||||
|
file: boolean
|
||||||
|
folder: boolean
|
||||||
|
path: BookmarkedItemPath
|
||||||
|
order: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OrderedBookmarks {
|
||||||
|
[key: BookmarkedItemPath]: OrderedBookmarkedItem
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Bookmarks_PluginInstance extends PluginInstance {
|
||||||
|
[BookmarksPlugin_getBookmarks_methodName]: () => Array<BookmarkedItem> | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
let bookmarksCache: OrderedBookmarks | undefined = undefined
|
||||||
|
let bookmarksCacheTimestamp: number | undefined = undefined
|
||||||
|
|
||||||
|
const CacheExpirationMilis = 1000 // One second seems to be reasonable
|
||||||
|
|
||||||
|
export const invalidateExpiredBookmarksCache = (force?: boolean): void => {
|
||||||
|
if (bookmarksCache) {
|
||||||
|
let flush: boolean = true
|
||||||
|
if (!force && !!bookmarksCacheTimestamp) {
|
||||||
|
if (Date.now() - CacheExpirationMilis <= bookmarksCacheTimestamp) {
|
||||||
|
flush = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (flush) {
|
||||||
|
bookmarksCache = undefined
|
||||||
|
bookmarksCacheTimestamp = undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BookmarksCorePluginId: string = 'bookmarks'
|
||||||
|
|
||||||
|
export const getBookmarksPlugin = (app?: App): Bookmarks_PluginInstance | undefined => {
|
||||||
|
invalidateExpiredBookmarksCache()
|
||||||
|
const bookmarksPlugin: InstalledPlugin | undefined = app?.internalPlugins?.getPluginById(BookmarksCorePluginId)
|
||||||
|
console.log(bookmarksPlugin)
|
||||||
|
const bookmarks = (bookmarksPlugin?.instance as any) ?.['getBookmarks']()
|
||||||
|
console.log(bookmarks)
|
||||||
|
if (bookmarksPlugin && bookmarksPlugin.enabled && bookmarksPlugin.instance) {
|
||||||
|
const bookmarksPluginInstance: Bookmarks_PluginInstance = bookmarksPlugin.instance as Bookmarks_PluginInstance
|
||||||
|
// defensive programming, in case Obsidian changes its internal APIs
|
||||||
|
if (typeof bookmarksPluginInstance?.[BookmarksPlugin_getBookmarks_methodName] === 'function') {
|
||||||
|
return bookmarksPluginInstance
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type TraverseCallback = (item: BookmarkedItem) => boolean | void
|
||||||
|
|
||||||
|
const traverseBookmarksCollection = (items: Array<BookmarkedItem>, callback: TraverseCallback) => {
|
||||||
|
const recursiveTraversal = (collection: Array<BookmarkedItem>) => {
|
||||||
|
for (let idx = 0, collectionRef = collection; idx < collectionRef.length; idx++) {
|
||||||
|
const item = collectionRef[idx];
|
||||||
|
if (callback(item)) return;
|
||||||
|
if ('group' === item.type) recursiveTraversal(item.items);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
recursiveTraversal(items);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: extend this function to take a scope as parameter: a path to Bookmarks group to start from
|
||||||
|
// Initially consuming all bookmarks is ok - finally the starting point (group) should be configurable
|
||||||
|
const getOrderedBookmarks = (plugin: Bookmarks_PluginInstance): OrderedBookmarks | undefined => {
|
||||||
|
const bookmarks: Array<BookmarkedItem> | undefined = plugin?.[BookmarksPlugin_getBookmarks_methodName]()
|
||||||
|
if (bookmarks) {
|
||||||
|
const orderedBookmarks: OrderedBookmarks = {}
|
||||||
|
let order: number = 0
|
||||||
|
const consumeItem = (item: BookmarkedItem) => {
|
||||||
|
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
|
||||||
|
// Consume only the first occurrence of a path in bookmarks, even if many duplicates can exist
|
||||||
|
const alreadyConsumed = orderedBookmarks[path]
|
||||||
|
if (!alreadyConsumed) {
|
||||||
|
orderedBookmarks[path] = {
|
||||||
|
path: path,
|
||||||
|
order: order++,
|
||||||
|
file: isFile,
|
||||||
|
folder: isFile
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
traverseBookmarksCollection(bookmarks, consumeItem)
|
||||||
|
return orderedBookmarks
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Result:
|
||||||
|
// undefined ==> item not found in bookmarks
|
||||||
|
// > 0 ==> item found in bookmarks at returned position
|
||||||
|
// Intentionally not returning 0 to allow simple syntax of processing the result
|
||||||
|
export const determineBookmarkOrder = (path: string, plugin: Bookmarks_PluginInstance): number | undefined => {
|
||||||
|
if (!bookmarksCache) {
|
||||||
|
bookmarksCache = getOrderedBookmarks(plugin)
|
||||||
|
bookmarksCacheTimestamp = Date.now()
|
||||||
|
}
|
||||||
|
|
||||||
|
const bookmarkedItemPosition: number | undefined = bookmarksCache?.[path]?.order
|
||||||
|
|
||||||
|
return (bookmarkedItemPosition !== undefined && bookmarkedItemPosition >= 0) ? (bookmarkedItemPosition + 1) : undefined
|
||||||
|
}
|
Loading…
Reference in New Issue