#74 - Integration with Bookmarks core plugin and support for indirect drag & drop arrangement
Tons of updates: - full integration with standard sorting at folder level and at sorting group level - refined support for implicit sorting for bookmarks plugin integration - documentation update (partial, sketchy)
This commit is contained in:
parent
cc73b4d3f1
commit
71ab76652c
|
@ -181,6 +181,48 @@ sorting-spec: |
|
|||
The artificial separator `---+---` defines a sorting group, which will not match any folders or files
|
||||
and is used here to logically separate the series of combined groups into to logical sets
|
||||
|
||||
## Bookmarks plugin integration
|
||||
|
||||
Integration with the __Bookmarks core plugin__ allows for ordering of items via drag & drop in Bookmarks view and reflecting the same order in File Explorer automatically
|
||||
|
||||
TODO: the simple scenario presented on movie
|
||||
|
||||
A separate group of bookmarks is designated by default, to separate from ...
|
||||
|
||||
If at least one item in folder is bookmarked and the auto-enabled, the order of the items becomes managed by the custom sort plugin
|
||||
|
||||
Auto-integration works without any need for sorting configuration files. Under the hood it is equivalent to applying the following global sorting specification:
|
||||
```yaml
|
||||
---
|
||||
sorting-spec: |
|
||||
target-folder: /*
|
||||
bookmarked:
|
||||
< by-bookmarks-order
|
||||
sorting: standard
|
||||
---
|
||||
```
|
||||
|
||||
Auto-integration doesn't apply to folders, for which explicit sorting specification is defined in YAML.
|
||||
In that case, if you want to employ the grouping and/or ordering by bookmarks order, you need to use explicit syntax:
|
||||
|
||||
```yaml
|
||||
---
|
||||
sorting-spec: |
|
||||
target-folder: My folder
|
||||
bookmarked:
|
||||
< by-bookmarks-order
|
||||
---
|
||||
```
|
||||
|
||||
TODO: more instructions plus movie of advanced integration, where bookmarks reflect the folders structure
|
||||
|
||||
Also hints for updating
|
||||
|
||||
A folder is excluded from auto-integration if:
|
||||
- has custom sorting spec
|
||||
- you can (if needed) enable the auto-integration for part of the items
|
||||
- has explicitly applied 'sorting: standard'
|
||||
|
||||
## Matching starred items
|
||||
|
||||
The Obsidian core plugin `Starred` allows the user to 'star' files\
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import {MetadataCache, Plugin} from "obsidian";
|
||||
|
||||
export enum CustomSortGroupType {
|
||||
Outsiders, // Not belonging to any of other groups
|
||||
MatchAll, // like a wildard *, used in connection with foldersOnly or filesOnly. The difference between the MatchAll and Outsiders is
|
||||
|
@ -30,7 +28,7 @@ export enum CustomSortOrder {
|
|||
byMetadataFieldTrueAlphabetical,
|
||||
byMetadataFieldAlphabeticalReverse,
|
||||
byMetadataFieldTrueAlphabeticalReverse,
|
||||
standardObsidian, // Let the folder sorting be in hands of Obsidian, whatever user selected in the UI
|
||||
standardObsidian, // whatever user selected in the UI
|
||||
byBookmarkOrder,
|
||||
byBookmarkOrderReverse,
|
||||
default = alphabetical
|
||||
|
@ -80,10 +78,7 @@ export interface CustomSortSpec {
|
|||
outsidersFoldersGroupIdx?: number
|
||||
itemsToHide?: Set<string>
|
||||
priorityOrder?: Array<number> // Indexes of groups in evaluation order
|
||||
|
||||
// For internal transient use
|
||||
plugin?: Plugin // to hand over the access to App instance to the sorting engine
|
||||
_mCache?: MetadataCache
|
||||
implicit?: boolean // spec applied automatically (e.g. auto integration with a plugin)
|
||||
}
|
||||
|
||||
export const DEFAULT_METADATA_FIELD_FOR_SORTING: string = 'sort-index-value'
|
||||
|
|
|
@ -7,7 +7,7 @@ import {
|
|||
FolderItemForSorting,
|
||||
matchGroupRegex, sorterByBookmarkOrder, sorterByMetadataField,
|
||||
SorterFn,
|
||||
Sorters
|
||||
getSorterFnFor, ProcessingContext
|
||||
} from './custom-sort';
|
||||
import {CustomSortGroupType, CustomSortOrder, CustomSortSpec, RegExpSpec} from './custom-sort-types';
|
||||
import {CompoundDashNumberNormalizerFn, CompoundDotRomanNumberNormalizerFn} from "./sorting-spec-processor";
|
||||
|
@ -716,7 +716,9 @@ describe('determineSortingGroup', () => {
|
|||
type: CustomSortGroupType.HasMetadataField,
|
||||
withMetadataFieldName: "metadataField1",
|
||||
exactPrefix: 'Ref'
|
||||
}],
|
||||
}]
|
||||
}
|
||||
const ctx: Partial<ProcessingContext> = {
|
||||
_mCache: {
|
||||
getCache: function (path: string): CachedMetadata | undefined {
|
||||
return {
|
||||
|
@ -732,7 +734,7 @@ describe('determineSortingGroup', () => {
|
|||
}
|
||||
|
||||
// when
|
||||
const result = determineSortingGroup(file, sortSpec)
|
||||
const result = determineSortingGroup(file, sortSpec, ctx as ProcessingContext)
|
||||
|
||||
// then
|
||||
expect(result).toEqual({
|
||||
|
@ -753,7 +755,9 @@ describe('determineSortingGroup', () => {
|
|||
type: CustomSortGroupType.HasMetadataField,
|
||||
withMetadataFieldName: "metadataField1",
|
||||
exactPrefix: 'Ref'
|
||||
}],
|
||||
}]
|
||||
}
|
||||
const ctx: Partial<ProcessingContext> = {
|
||||
_mCache: {
|
||||
getCache: function (path: string): CachedMetadata | undefined {
|
||||
return {
|
||||
|
@ -769,7 +773,7 @@ describe('determineSortingGroup', () => {
|
|||
}
|
||||
|
||||
// when
|
||||
const result = determineSortingGroup(file, sortSpec)
|
||||
const result = determineSortingGroup(file, sortSpec, ctx as ProcessingContext)
|
||||
|
||||
// then
|
||||
expect(result).toEqual({
|
||||
|
@ -790,7 +794,9 @@ describe('determineSortingGroup', () => {
|
|||
type: CustomSortGroupType.HasMetadataField,
|
||||
withMetadataFieldName: "metadataField1",
|
||||
exactPrefix: 'Ref'
|
||||
}],
|
||||
}]
|
||||
}
|
||||
const ctx: Partial<ProcessingContext> = {
|
||||
_mCache: {
|
||||
getCache: function (path: string): CachedMetadata | undefined {
|
||||
return {
|
||||
|
@ -806,7 +812,7 @@ describe('determineSortingGroup', () => {
|
|||
}
|
||||
|
||||
// when
|
||||
const result = determineSortingGroup(file, sortSpec)
|
||||
const result = determineSortingGroup(file, sortSpec, ctx as ProcessingContext)
|
||||
|
||||
// then
|
||||
expect(result).toEqual({
|
||||
|
@ -827,7 +833,9 @@ describe('determineSortingGroup', () => {
|
|||
type: CustomSortGroupType.HasMetadataField,
|
||||
withMetadataFieldName: "metadataField1",
|
||||
exactPrefix: 'Ref'
|
||||
}],
|
||||
}]
|
||||
}
|
||||
const ctx: Partial<ProcessingContext> = {
|
||||
_mCache: {
|
||||
getCache: function (path: string): CachedMetadata | undefined {
|
||||
return {
|
||||
|
@ -843,7 +851,7 @@ describe('determineSortingGroup', () => {
|
|||
}
|
||||
|
||||
// when
|
||||
const result = determineSortingGroup(folder, sortSpec)
|
||||
const result = determineSortingGroup(folder, sortSpec, ctx as ProcessingContext)
|
||||
|
||||
// then
|
||||
expect(result).toEqual({
|
||||
|
@ -876,7 +884,7 @@ describe('determineSortingGroup', () => {
|
|||
// when
|
||||
const result = determineSortingGroup(file, sortSpec, {
|
||||
starredPluginInstance: starredPluginInstance as Starred_PluginInstance
|
||||
})
|
||||
} as ProcessingContext)
|
||||
|
||||
// then
|
||||
expect(result).toEqual({
|
||||
|
@ -907,7 +915,7 @@ describe('determineSortingGroup', () => {
|
|||
// when
|
||||
const result = determineSortingGroup(file, sortSpec, {
|
||||
starredPluginInstance: starredPluginInstance as Starred_PluginInstance
|
||||
})
|
||||
} as ProcessingContext)
|
||||
|
||||
// then
|
||||
expect(result).toEqual({
|
||||
|
@ -938,7 +946,7 @@ describe('determineSortingGroup', () => {
|
|||
// when
|
||||
const result = determineSortingGroup(folder, sortSpec, {
|
||||
starredPluginInstance: starredPluginInstance as Starred_PluginInstance
|
||||
})
|
||||
} as ProcessingContext)
|
||||
|
||||
// then
|
||||
expect(result).toEqual({
|
||||
|
@ -977,7 +985,7 @@ describe('determineSortingGroup', () => {
|
|||
// when
|
||||
const result = determineSortingGroup(folder, sortSpec, {
|
||||
starredPluginInstance: starredPluginInstance as Starred_PluginInstance
|
||||
})
|
||||
} as ProcessingContext)
|
||||
|
||||
// then
|
||||
expect(result).toEqual({
|
||||
|
@ -1016,7 +1024,7 @@ describe('determineSortingGroup', () => {
|
|||
// when
|
||||
const result = determineSortingGroup(folder, sortSpec, {
|
||||
starredPluginInstance: starredPluginInstance as Starred_PluginInstance
|
||||
})
|
||||
} as ProcessingContext)
|
||||
|
||||
// then
|
||||
expect(result).toEqual({
|
||||
|
@ -1058,7 +1066,7 @@ describe('determineSortingGroup', () => {
|
|||
// when
|
||||
const result = determineSortingGroup(file, sortSpec, {
|
||||
iconFolderPluginInstance: obsidianIconFolderPluginInstance as ObsidianIconFolder_PluginInstance
|
||||
})
|
||||
} as ProcessingContext)
|
||||
|
||||
// then
|
||||
expect(result).toEqual({
|
||||
|
@ -1093,7 +1101,7 @@ describe('determineSortingGroup', () => {
|
|||
// when
|
||||
const result = determineSortingGroup(file, sortSpec, {
|
||||
iconFolderPluginInstance: obsidianIconFolderPluginInstance as ObsidianIconFolder_PluginInstance
|
||||
})
|
||||
} as ProcessingContext)
|
||||
|
||||
// then
|
||||
expect(result).toEqual({
|
||||
|
@ -1127,7 +1135,7 @@ describe('determineSortingGroup', () => {
|
|||
// when
|
||||
const result = determineSortingGroup(file, sortSpec, {
|
||||
iconFolderPluginInstance: obsidianIconFolderPluginInstance as ObsidianIconFolder_PluginInstance
|
||||
})
|
||||
} as ProcessingContext)
|
||||
|
||||
// then
|
||||
expect(result).toEqual({
|
||||
|
@ -1162,7 +1170,7 @@ describe('determineSortingGroup', () => {
|
|||
// when
|
||||
const result = determineSortingGroup(file, sortSpec, {
|
||||
iconFolderPluginInstance: obsidianIconFolderPluginInstance as ObsidianIconFolder_PluginInstance
|
||||
})
|
||||
} as ProcessingContext)
|
||||
|
||||
// then
|
||||
expect(result).toEqual({
|
||||
|
@ -1193,7 +1201,7 @@ describe('determineSortingGroup', () => {
|
|||
// when
|
||||
const result = determineSortingGroup(folder, sortSpec, {
|
||||
iconFolderPluginInstance: obsidianIconFolderPluginInstance as ObsidianIconFolder_PluginInstance
|
||||
})
|
||||
} as ProcessingContext)
|
||||
|
||||
// then
|
||||
expect(result).toEqual({
|
||||
|
@ -1235,7 +1243,7 @@ describe('determineSortingGroup', () => {
|
|||
// when
|
||||
const result = determineSortingGroup(folder, sortSpec, {
|
||||
iconFolderPluginInstance: obsidianIconFolderPluginInstance as ObsidianIconFolder_PluginInstance
|
||||
})
|
||||
} as ProcessingContext)
|
||||
|
||||
// then
|
||||
expect(result).toEqual({
|
||||
|
@ -1280,7 +1288,7 @@ describe('determineSortingGroup', () => {
|
|||
// when
|
||||
const result = determineSortingGroup(folder, sortSpec, {
|
||||
iconFolderPluginInstance: obsidianIconFolderPluginInstance as ObsidianIconFolder_PluginInstance
|
||||
})
|
||||
} as ProcessingContext)
|
||||
|
||||
// then
|
||||
expect(result).toEqual({
|
||||
|
@ -1323,7 +1331,7 @@ describe('determineSortingGroup', () => {
|
|||
// when
|
||||
const result = determineSortingGroup(folder, sortSpec, {
|
||||
iconFolderPluginInstance: obsidianIconFolderPluginInstance as ObsidianIconFolder_PluginInstance
|
||||
})
|
||||
} as ProcessingContext)
|
||||
|
||||
// then
|
||||
expect(result).toEqual({
|
||||
|
@ -1369,7 +1377,7 @@ describe('determineSortingGroup', () => {
|
|||
// when
|
||||
const result = determineSortingGroup(folder, sortSpec, {
|
||||
iconFolderPluginInstance: obsidianIconFolderPluginInstance as ObsidianIconFolder_PluginInstance
|
||||
})
|
||||
} as ProcessingContext)
|
||||
|
||||
// then
|
||||
expect(result).toEqual({
|
||||
|
@ -1412,7 +1420,7 @@ describe('determineSortingGroup', () => {
|
|||
// when
|
||||
const result = determineSortingGroup(folder, sortSpec, {
|
||||
iconFolderPluginInstance: obsidianIconFolderPluginInstance as ObsidianIconFolder_PluginInstance
|
||||
})
|
||||
} as ProcessingContext)
|
||||
|
||||
// then
|
||||
expect(result).toEqual({
|
||||
|
@ -1458,7 +1466,7 @@ describe('determineSortingGroup', () => {
|
|||
// when
|
||||
const result = determineSortingGroup(folder, sortSpec, {
|
||||
iconFolderPluginInstance: obsidianIconFolderPluginInstance as ObsidianIconFolder_PluginInstance
|
||||
})
|
||||
} as ProcessingContext)
|
||||
|
||||
// then
|
||||
expect(result).toEqual({
|
||||
|
@ -1491,7 +1499,9 @@ describe('determineSortingGroup', () => {
|
|||
byMetadataField: 'metadata-field-for-sorting',
|
||||
exactPrefix: 'Ref',
|
||||
order: CustomSortOrder.byMetadataFieldAlphabetical
|
||||
}],
|
||||
}]
|
||||
}
|
||||
const ctx: Partial<ProcessingContext> = {
|
||||
_mCache: {
|
||||
getCache: function (path: string): CachedMetadata | undefined {
|
||||
return {
|
||||
|
@ -1507,7 +1517,7 @@ describe('determineSortingGroup', () => {
|
|||
}
|
||||
|
||||
// when
|
||||
const result = determineSortingGroup(file, sortSpec)
|
||||
const result = determineSortingGroup(file, sortSpec, ctx as ProcessingContext)
|
||||
|
||||
// then
|
||||
expect(result).toEqual({
|
||||
|
@ -1530,7 +1540,9 @@ describe('determineSortingGroup', () => {
|
|||
byMetadataField: 'metadata-field-for-sorting',
|
||||
exactPrefix: 'Ref',
|
||||
order: CustomSortOrder.byMetadataFieldAlphabeticalReverse
|
||||
}],
|
||||
}]
|
||||
}
|
||||
const ctx: Partial<ProcessingContext> = {
|
||||
_mCache: {
|
||||
getCache: function (path: string): CachedMetadata | undefined {
|
||||
return {
|
||||
|
@ -1546,7 +1558,7 @@ describe('determineSortingGroup', () => {
|
|||
}
|
||||
|
||||
// when
|
||||
const result = determineSortingGroup(file, sortSpec)
|
||||
const result = determineSortingGroup(file, sortSpec, ctx as ProcessingContext)
|
||||
|
||||
// then
|
||||
expect(result).toEqual({
|
||||
|
@ -1569,7 +1581,9 @@ describe('determineSortingGroup', () => {
|
|||
byMetadataField: 'metadata-field-for-sorting',
|
||||
exactPrefix: 'Ref',
|
||||
order: CustomSortOrder.byMetadataFieldTrueAlphabetical
|
||||
}],
|
||||
}]
|
||||
}
|
||||
const ctx: Partial<ProcessingContext> = {
|
||||
_mCache: {
|
||||
getCache: function (path: string): CachedMetadata | undefined {
|
||||
return {
|
||||
|
@ -1585,7 +1599,7 @@ describe('determineSortingGroup', () => {
|
|||
}
|
||||
|
||||
// when
|
||||
const result = determineSortingGroup(file, sortSpec)
|
||||
const result = determineSortingGroup(file, sortSpec, ctx as ProcessingContext)
|
||||
|
||||
// then
|
||||
expect(result).toEqual({
|
||||
|
@ -1608,7 +1622,9 @@ describe('determineSortingGroup', () => {
|
|||
byMetadataField: 'metadata-field-for-sorting',
|
||||
exactPrefix: 'Ref',
|
||||
order: CustomSortOrder.byMetadataFieldTrueAlphabeticalReverse
|
||||
}],
|
||||
}]
|
||||
}
|
||||
const ctx: Partial<ProcessingContext> = {
|
||||
_mCache: {
|
||||
getCache: function (path: string): CachedMetadata | undefined {
|
||||
return {
|
||||
|
@ -1624,7 +1640,7 @@ describe('determineSortingGroup', () => {
|
|||
}
|
||||
|
||||
// when
|
||||
const result = determineSortingGroup(file, sortSpec)
|
||||
const result = determineSortingGroup(file, sortSpec, ctx as ProcessingContext)
|
||||
|
||||
// then
|
||||
expect(result).toEqual({
|
||||
|
@ -1647,7 +1663,9 @@ describe('determineSortingGroup', () => {
|
|||
exactPrefix: 'Ref',
|
||||
byMetadataField: 'metadata-field-for-sorting',
|
||||
order: CustomSortOrder.byMetadataFieldAlphabeticalReverse
|
||||
}],
|
||||
}]
|
||||
}
|
||||
const ctx: Partial<ProcessingContext> = {
|
||||
_mCache: {
|
||||
getCache: function (path: string): CachedMetadata | undefined {
|
||||
return {
|
||||
|
@ -1663,7 +1681,7 @@ describe('determineSortingGroup', () => {
|
|||
}
|
||||
|
||||
// when
|
||||
const result = determineSortingGroup(folder, sortSpec)
|
||||
const result = determineSortingGroup(folder, sortSpec, ctx as ProcessingContext)
|
||||
|
||||
// then
|
||||
expect(result).toEqual({
|
||||
|
@ -1687,6 +1705,10 @@ describe('determineSortingGroup', () => {
|
|||
exactPrefix: 'Ref',
|
||||
order: CustomSortOrder.byMetadataFieldAlphabetical
|
||||
}],
|
||||
defaultOrder: CustomSortOrder.byMetadataFieldAlphabeticalReverse,
|
||||
byMetadataField: 'metadata-field-for-sorting-specified-on-target-folder'
|
||||
}
|
||||
const ctx: Partial<ProcessingContext> = {
|
||||
_mCache: {
|
||||
getCache: function (path: string): CachedMetadata | undefined {
|
||||
return {
|
||||
|
@ -1698,13 +1720,11 @@ describe('determineSortingGroup', () => {
|
|||
}
|
||||
}[path]
|
||||
}
|
||||
} as MetadataCache,
|
||||
defaultOrder: CustomSortOrder.byMetadataFieldAlphabeticalReverse,
|
||||
byMetadataField: 'metadata-field-for-sorting-specified-on-target-folder',
|
||||
} as MetadataCache
|
||||
}
|
||||
|
||||
// when
|
||||
const result = determineSortingGroup(file, sortSpec)
|
||||
const result = determineSortingGroup(file, sortSpec, ctx as ProcessingContext)
|
||||
|
||||
// then
|
||||
expect(result).toEqual({
|
||||
|
@ -1726,7 +1746,9 @@ describe('determineSortingGroup', () => {
|
|||
type: CustomSortGroupType.HasMetadataField,
|
||||
order: CustomSortOrder.byMetadataFieldAlphabetical,
|
||||
withMetadataFieldName: 'field-used-with-with-metadata-syntax'
|
||||
}],
|
||||
}]
|
||||
}
|
||||
const ctx: Partial<ProcessingContext> = {
|
||||
_mCache: {
|
||||
getCache: function (path: string): CachedMetadata | undefined {
|
||||
return {
|
||||
|
@ -1742,7 +1764,7 @@ describe('determineSortingGroup', () => {
|
|||
}
|
||||
|
||||
// when
|
||||
const result = determineSortingGroup(file, sortSpec)
|
||||
const result = determineSortingGroup(file, sortSpec, ctx as ProcessingContext)
|
||||
|
||||
// then
|
||||
expect(result).toEqual({
|
||||
|
@ -1764,7 +1786,9 @@ describe('determineSortingGroup', () => {
|
|||
type: CustomSortGroupType.ExactPrefix,
|
||||
exactPrefix: 'Ref',
|
||||
order: CustomSortOrder.byMetadataFieldAlphabetical
|
||||
}],
|
||||
}]
|
||||
}
|
||||
const ctx: Partial<ProcessingContext> = {
|
||||
_mCache: {
|
||||
getCache: function (path: string): CachedMetadata | undefined {
|
||||
return {
|
||||
|
@ -1780,7 +1804,7 @@ describe('determineSortingGroup', () => {
|
|||
}
|
||||
|
||||
// when
|
||||
const result = determineSortingGroup(file, sortSpec)
|
||||
const result = determineSortingGroup(file, sortSpec, ctx as ProcessingContext)
|
||||
|
||||
// then
|
||||
expect(result).toEqual({
|
||||
|
@ -2094,7 +2118,7 @@ describe('CustomSortOrder.byMetadataFieldAlphabetical', () => {
|
|||
const itemB: Partial<FolderItemForSorting> = {
|
||||
metadataFieldValue: 'B'
|
||||
}
|
||||
const sorter: SorterFn = Sorters[CustomSortOrder.byMetadataFieldAlphabetical]
|
||||
const sorter: SorterFn = getSorterFnFor(CustomSortOrder.byMetadataFieldAlphabetical)
|
||||
|
||||
// when
|
||||
const result1: number = sorter(itemA as FolderItemForSorting, itemB as FolderItemForSorting)
|
||||
|
@ -2114,7 +2138,7 @@ describe('CustomSortOrder.byMetadataFieldAlphabetical', () => {
|
|||
metadataFieldValue: 'Aaa',
|
||||
sortString: 'a123'
|
||||
}
|
||||
const sorter: SorterFn = Sorters[CustomSortOrder.byMetadataFieldAlphabetical]
|
||||
const sorter: SorterFn = getSorterFnFor(CustomSortOrder.byMetadataFieldAlphabetical)
|
||||
|
||||
// when
|
||||
const result1: number = sorter(itemA as FolderItemForSorting, itemB as FolderItemForSorting)
|
||||
|
@ -2135,7 +2159,7 @@ describe('CustomSortOrder.byMetadataFieldAlphabetical', () => {
|
|||
const itemB: Partial<FolderItemForSorting> = {
|
||||
sortString: 'n123'
|
||||
}
|
||||
const sorter: SorterFn = Sorters[CustomSortOrder.byMetadataFieldAlphabetical]
|
||||
const sorter: SorterFn = getSorterFnFor(CustomSortOrder.byMetadataFieldAlphabetical)
|
||||
|
||||
// when
|
||||
const result1: number = sorter(itemA as FolderItemForSorting, itemB as FolderItemForSorting)
|
||||
|
@ -2153,7 +2177,7 @@ describe('CustomSortOrder.byMetadataFieldAlphabetical', () => {
|
|||
const itemB: Partial<FolderItemForSorting> = {
|
||||
sortString: 'ccc '
|
||||
}
|
||||
const sorter: SorterFn = Sorters[CustomSortOrder.byMetadataFieldAlphabetical]
|
||||
const sorter: SorterFn = getSorterFnFor(CustomSortOrder.byMetadataFieldAlphabetical)
|
||||
|
||||
// when
|
||||
const result1: number = sorter(itemA as FolderItemForSorting, itemB as FolderItemForSorting)
|
||||
|
@ -2176,7 +2200,7 @@ describe('CustomSortOrder.byMetadataFieldAlphabeticalReverse', () => {
|
|||
const itemB: Partial<FolderItemForSorting> = {
|
||||
metadataFieldValue: 'B'
|
||||
}
|
||||
const sorter: SorterFn = Sorters[CustomSortOrder.byMetadataFieldAlphabeticalReverse]
|
||||
const sorter: SorterFn = getSorterFnFor(CustomSortOrder.byMetadataFieldAlphabeticalReverse)
|
||||
|
||||
// when
|
||||
const result1: number = sorter(itemA as FolderItemForSorting, itemB as FolderItemForSorting)
|
||||
|
@ -2196,7 +2220,7 @@ describe('CustomSortOrder.byMetadataFieldAlphabeticalReverse', () => {
|
|||
metadataFieldValue: 'Aaa',
|
||||
sortString: 'a123'
|
||||
}
|
||||
const sorter: SorterFn = Sorters[CustomSortOrder.byMetadataFieldAlphabeticalReverse]
|
||||
const sorter: SorterFn = getSorterFnFor(CustomSortOrder.byMetadataFieldAlphabeticalReverse)
|
||||
|
||||
// when
|
||||
const result1: number = sorter(itemA as FolderItemForSorting, itemB as FolderItemForSorting)
|
||||
|
@ -2217,7 +2241,7 @@ describe('CustomSortOrder.byMetadataFieldAlphabeticalReverse', () => {
|
|||
const itemB: Partial<FolderItemForSorting> = {
|
||||
sortString: 'n123'
|
||||
}
|
||||
const sorter: SorterFn = Sorters[CustomSortOrder.byMetadataFieldAlphabetical]
|
||||
const sorter: SorterFn = getSorterFnFor(CustomSortOrder.byMetadataFieldAlphabetical)
|
||||
|
||||
// when
|
||||
const result1: number = sorter(itemA as FolderItemForSorting, itemB as FolderItemForSorting)
|
||||
|
@ -2236,7 +2260,7 @@ describe('CustomSortOrder.byMetadataFieldAlphabeticalReverse', () => {
|
|||
const itemB: Partial<FolderItemForSorting> = {
|
||||
sortString: 'n123'
|
||||
}
|
||||
const sorter: SorterFn = Sorters[CustomSortOrder.byMetadataFieldAlphabeticalReverse]
|
||||
const sorter: SorterFn = getSorterFnFor(CustomSortOrder.byMetadataFieldAlphabeticalReverse)
|
||||
|
||||
// when
|
||||
const result1: number = sorter(itemA as FolderItemForSorting, itemB as FolderItemForSorting)
|
||||
|
@ -2254,7 +2278,7 @@ describe('CustomSortOrder.byMetadataFieldAlphabeticalReverse', () => {
|
|||
const itemB: Partial<FolderItemForSorting> = {
|
||||
sortString: 'ccc '
|
||||
}
|
||||
const sorter: SorterFn = Sorters[CustomSortOrder.byMetadataFieldAlphabeticalReverse]
|
||||
const sorter: SorterFn = getSorterFnFor(CustomSortOrder.byMetadataFieldAlphabeticalReverse)
|
||||
|
||||
// when
|
||||
const result1: number = sorter(itemA as FolderItemForSorting, itemB as FolderItemForSorting)
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import {FrontMatterCache, requireApiVersion, TAbstractFile, TFile, TFolder} from 'obsidian';
|
||||
import {determineStarredStatusOf, getStarredPlugin, Starred_PluginInstance} from '../utils/StarredPluginSignature';
|
||||
import {FrontMatterCache, MetadataCache, Plugin, requireApiVersion, TAbstractFile, TFile, TFolder} from 'obsidian';
|
||||
import {determineStarredStatusOf, Starred_PluginInstance} from '../utils/StarredPluginSignature';
|
||||
import {
|
||||
determineIconOf,
|
||||
getIconFolderPlugin,
|
||||
ObsidianIconFolder_PluginInstance
|
||||
} from '../utils/ObsidianIconFolderPluginSignature'
|
||||
import {
|
||||
|
@ -17,10 +16,21 @@ import {
|
|||
import {isDefined} from "../utils/utils";
|
||||
import {
|
||||
Bookmarks_PluginInstance,
|
||||
determineBookmarkOrder,
|
||||
getBookmarksPlugin
|
||||
determineBookmarkOrder
|
||||
} from "../utils/BookmarksCorePluginSignature";
|
||||
|
||||
export interface ProcessingContext {
|
||||
// For internal transient use
|
||||
plugin?: Plugin // to hand over the access to App instance to the sorting engine
|
||||
_mCache?: MetadataCache
|
||||
starredPluginInstance?: Starred_PluginInstance
|
||||
bookmarksPlugin: {
|
||||
instance?: Bookmarks_PluginInstance,
|
||||
groupNameForSorting?: string
|
||||
}
|
||||
iconFolderPluginInstance?: ObsidianIconFolder_PluginInstance
|
||||
}
|
||||
|
||||
let CollatorCompare = new Intl.Collator(undefined, {
|
||||
usage: "sort",
|
||||
sensitivity: "base",
|
||||
|
@ -95,7 +105,7 @@ export const sorterByBookmarkOrder:(reverseOrder?: boolean, trueAlphabetical?: b
|
|||
}
|
||||
}
|
||||
|
||||
export let Sorters: { [key in CustomSortOrder]: SorterFn } = {
|
||||
let Sorters: { [key in CustomSortOrder]: SorterFn } = {
|
||||
[CustomSortOrder.alphabetical]: (a: FolderItemForSorting, b: FolderItemForSorting) => CollatorCompare(a.sortString, b.sortString),
|
||||
[CustomSortOrder.trueAlphabetical]: (a: FolderItemForSorting, b: FolderItemForSorting) => CollatorTrueAlphabeticalCompare(a.sortString, b.sortString),
|
||||
[CustomSortOrder.alphabeticalReverse]: (a: FolderItemForSorting, b: FolderItemForSorting) => CollatorCompare(b.sortString, a.sortString),
|
||||
|
@ -115,19 +125,50 @@ export let Sorters: { [key in CustomSortOrder]: SorterFn } = {
|
|||
[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 getSorterFor() function below should protect against it
|
||||
[CustomSortOrder.standardObsidian]: (a: FolderItemForSorting, b: FolderItemForSorting) => CollatorCompare(a.sortString, b.sortString),
|
||||
};
|
||||
|
||||
function compareTwoItems(itA: FolderItemForSorting, itB: FolderItemForSorting, sortSpec: CustomSortSpec) {
|
||||
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)'
|
||||
}
|
||||
|
||||
// Standard Obsidian comparator keeps folders in the top sorted alphabetically
|
||||
const StandardObsidianComparator = (order: CustomSortOrder): SorterFn => {
|
||||
const customSorterFn = Sorters[order]
|
||||
return (a: FolderItemForSorting, b: FolderItemForSorting): number => {
|
||||
return a.isFolder || b.isFolder
|
||||
?
|
||||
(a.isFolder && !b.isFolder ? -1 : (b.isFolder && !a.isFolder ? 1 : Sorters[CustomSortOrder.alphabetical](a,b)))
|
||||
:
|
||||
customSorterFn(a, b);
|
||||
}
|
||||
}
|
||||
|
||||
export const getSorterFnFor = (sorting: CustomSortOrder, currentUIselectedSorting?: string): SorterFn => {
|
||||
if (sorting === CustomSortOrder.standardObsidian) {
|
||||
sorting = StandardObsidianToCustomSort[currentUIselectedSorting ?? 'alphabetical'] ?? CustomSortOrder.alphabetical
|
||||
return StandardObsidianComparator(sorting)
|
||||
} else {
|
||||
return Sorters[sorting]
|
||||
}
|
||||
}
|
||||
|
||||
function getComparator(sortSpec: CustomSortSpec, currentUIselectedSorting?: string): SorterFn {
|
||||
const compareTwoItems = (itA: FolderItemForSorting, itB: FolderItemForSorting) => {
|
||||
if (itA.groupIdx != undefined && itB.groupIdx != undefined) {
|
||||
if (itA.groupIdx === itB.groupIdx) {
|
||||
const group: CustomSortGroup | undefined = sortSpec.groups[itA.groupIdx]
|
||||
const matchingGroupPresentOnBothSidesAndEqual: boolean = itA.matchGroup !== undefined && itA.matchGroup === itB.matchGroup
|
||||
if (matchingGroupPresentOnBothSidesAndEqual && group.secondaryOrder) {
|
||||
return Sorters[group.secondaryOrder ?? CustomSortOrder.default](itA, itB)
|
||||
return getSorterFnFor(group.secondaryOrder ?? CustomSortOrder.default, currentUIselectedSorting)(itA, itB)
|
||||
} else {
|
||||
return Sorters[group?.order ?? CustomSortOrder.default](itA, itB)
|
||||
return getSorterFnFor(group?.order ?? CustomSortOrder.default, currentUIselectedSorting)(itA, itB)
|
||||
}
|
||||
} else {
|
||||
return itA.groupIdx - itB.groupIdx;
|
||||
|
@ -136,9 +177,11 @@ function compareTwoItems(itA: FolderItemForSorting, itB: FolderItemForSorting, s
|
|||
// should never happen - groupIdx is not known for at least one of items to compare.
|
||||
// The logic of determining the index always sets some idx
|
||||
// Yet for sanity and to satisfy TS code analyzer a fallback to default behavior below
|
||||
return Sorters[CustomSortOrder.default](itA, itB)
|
||||
return getSorterFnFor(CustomSortOrder.default, currentUIselectedSorting)(itA, itB)
|
||||
}
|
||||
}
|
||||
return compareTwoItems
|
||||
}
|
||||
|
||||
const isFolder = (entry: TAbstractFile) => {
|
||||
// The plain obvious 'entry instanceof TFolder' doesn't work inside Jest unit tests, hence a workaround below
|
||||
|
@ -171,13 +214,7 @@ export const matchGroupRegex = (theRegex: RegExpSpec, nameForMatching: string):
|
|||
return [false, undefined, undefined]
|
||||
}
|
||||
|
||||
export interface Context {
|
||||
starredPluginInstance?: Starred_PluginInstance
|
||||
bookmarksPluginInstance?: Bookmarks_PluginInstance
|
||||
iconFolderPluginInstance?: ObsidianIconFolder_PluginInstance
|
||||
}
|
||||
|
||||
export const determineSortingGroup = function (entry: TFile | TFolder, spec: CustomSortSpec, ctx?: Context): FolderItemForSorting {
|
||||
export const determineSortingGroup = function (entry: TFile | TFolder, spec: CustomSortSpec, ctx?: ProcessingContext): FolderItemForSorting {
|
||||
let groupIdx: number
|
||||
let determined: boolean = false
|
||||
let matchedGroup: string | null | undefined
|
||||
|
@ -261,10 +298,10 @@ export const determineSortingGroup = function (entry: TFile | TFolder, spec: Cus
|
|||
break
|
||||
case CustomSortGroupType.HasMetadataField:
|
||||
if (group.withMetadataFieldName) {
|
||||
if (spec._mCache) {
|
||||
if (ctx?._mCache) {
|
||||
// For folders - scan metadata of 'folder note'
|
||||
const notePathToScan: string = aFile ? entry.path : `${entry.path}/${entry.name}.md`
|
||||
const frontMatterCache: FrontMatterCache | undefined = spec._mCache.getCache(notePathToScan)?.frontmatter
|
||||
const frontMatterCache: FrontMatterCache | undefined = ctx._mCache.getCache(notePathToScan)?.frontmatter
|
||||
const hasMetadata: boolean | undefined = frontMatterCache?.hasOwnProperty(group.withMetadataFieldName)
|
||||
|
||||
if (hasMetadata) {
|
||||
|
@ -282,8 +319,8 @@ export const determineSortingGroup = function (entry: TFile | TFolder, spec: Cus
|
|||
}
|
||||
break
|
||||
case CustomSortGroupType.BookmarkedOnly:
|
||||
if (ctx?.bookmarksPluginInstance) {
|
||||
const bookmarkOrder: number | undefined = determineBookmarkOrder(entry.path, ctx.bookmarksPluginInstance)
|
||||
if (ctx?.bookmarksPlugin?.instance) {
|
||||
const bookmarkOrder: number | undefined = determineBookmarkOrder(entry.path, ctx.bookmarksPlugin?.instance, ctx.bookmarksPlugin?.groupNameForSorting)
|
||||
if (bookmarkOrder) { // safe ==> orders intentionally start from 1
|
||||
determined = true
|
||||
bookmarkedIdx = bookmarkOrder
|
||||
|
@ -362,10 +399,10 @@ export const determineSortingGroup = function (entry: TFile | TFolder, spec: Cus
|
|||
metadataFieldName = DEFAULT_METADATA_FIELD_FOR_SORTING
|
||||
}
|
||||
if (metadataFieldName) {
|
||||
if (spec._mCache) {
|
||||
if (ctx?._mCache) {
|
||||
// For folders - scan metadata of 'folder note'
|
||||
const notePathToScan: string = aFile ? entry.path : `${entry.path}/${entry.name}.md`
|
||||
const frontMatterCache: FrontMatterCache | undefined = spec._mCache.getCache(notePathToScan)?.frontmatter
|
||||
const frontMatterCache: FrontMatterCache | undefined = ctx._mCache.getCache(notePathToScan)?.frontmatter
|
||||
metadataValueToSortBy = frontMatterCache?.[metadataFieldName]
|
||||
}
|
||||
}
|
||||
|
@ -455,7 +492,7 @@ 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) => {
|
||||
export const determineBookmarksOrderIfNeeded = (folderItems: Array<FolderItemForSorting>, sortingSpec: CustomSortSpec, plugin: Bookmarks_PluginInstance, bookmarksGroup?: string) => {
|
||||
if (!plugin) return
|
||||
|
||||
folderItems.forEach((item) => {
|
||||
|
@ -469,17 +506,13 @@ export const determineBookmarksOrderIfNeeded = (folderItems: Array<FolderItemFor
|
|||
}
|
||||
}
|
||||
if (folderDefaultSortRequiresBookmarksOrder || groupSortRequiresBookmarksOrder) {
|
||||
item.bookmarkedIdx = determineBookmarkOrder(item.path, plugin)
|
||||
item.bookmarkedIdx = determineBookmarkOrder(item.path, plugin, bookmarksGroup)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const folderSort = function (sortingSpec: CustomSortSpec, order: string[]) {
|
||||
export const folderSort = function (sortingSpec: CustomSortSpec, ctx: ProcessingContext) {
|
||||
let fileExplorer = this.fileExplorer
|
||||
sortingSpec._mCache = sortingSpec.plugin?.app.metadataCache
|
||||
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 folderItems: Array<FolderItemForSorting> = (sortingSpec.itemsToHide ?
|
||||
this.file.children.filter((entry: TFile | TFolder) => {
|
||||
|
@ -488,24 +521,20 @@ export const folderSort = function (sortingSpec: CustomSortSpec, order: string[]
|
|||
:
|
||||
this.file.children)
|
||||
.map((entry: TFile | TFolder) => {
|
||||
const itemForSorting: FolderItemForSorting = determineSortingGroup(entry, sortingSpec, {
|
||||
starredPluginInstance: starredPluginInstance,
|
||||
bookmarksPluginInstance: bookmarksPluginInstance,
|
||||
iconFolderPluginInstance: iconFolderPluginInstance
|
||||
})
|
||||
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 (bookmarksPluginInstance) {
|
||||
determineBookmarksOrderIfNeeded(folderItems, sortingSpec, bookmarksPluginInstance)
|
||||
if (ctx.bookmarksPlugin?.instance) {
|
||||
determineBookmarksOrderIfNeeded(folderItems, sortingSpec, ctx.bookmarksPlugin.instance, ctx.bookmarksPlugin.groupNameForSorting)
|
||||
}
|
||||
|
||||
folderItems.sort(function (itA: FolderItemForSorting, itB: FolderItemForSorting) {
|
||||
return compareTwoItems(itA, itB, sortingSpec);
|
||||
});
|
||||
const comparator: SorterFn = getComparator(sortingSpec, fileExplorer.sortOrder)
|
||||
|
||||
folderItems.sort(comparator)
|
||||
|
||||
const items = folderItems
|
||||
.map((item: FolderItemForSorting) => fileExplorer.fileItems[item.path])
|
||||
|
@ -515,8 +544,4 @@ export const folderSort = function (sortingSpec: CustomSortSpec, order: string[]
|
|||
} else {
|
||||
this.children = items;
|
||||
}
|
||||
|
||||
// release risky references
|
||||
sortingSpec._mCache = undefined
|
||||
sortingSpec.plugin = undefined
|
||||
};
|
||||
|
|
|
@ -2,8 +2,10 @@ import {FolderWildcardMatching} from './folder-matching-rules'
|
|||
|
||||
type SortingSpec = string
|
||||
|
||||
const checkIfImplicitSpec = (s: SortingSpec) => false
|
||||
|
||||
const createMockMatcherRichVersion = (): FolderWildcardMatching<SortingSpec> => {
|
||||
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching()
|
||||
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching(checkIfImplicitSpec)
|
||||
let p: string
|
||||
p = '/...'; matcher.addWildcardDefinition(p, `00 ${p}`)
|
||||
p = '/*'; matcher.addWildcardDefinition(p, `0 ${p}`)
|
||||
|
@ -19,25 +21,25 @@ const PRIO2 = 2
|
|||
const PRIO3 = 3
|
||||
|
||||
const createMockMatcherSimplestVersion = (): FolderWildcardMatching<SortingSpec> => {
|
||||
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching()
|
||||
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching(checkIfImplicitSpec)
|
||||
matcher.addWildcardDefinition('/Reviews/daily/*', '/Reviews/daily/*')
|
||||
return matcher
|
||||
}
|
||||
|
||||
const createMockMatcherRootOnlyVersion = (): FolderWildcardMatching<SortingSpec> => {
|
||||
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching()
|
||||
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching(checkIfImplicitSpec)
|
||||
matcher.addWildcardDefinition('/...', '/...')
|
||||
return matcher
|
||||
}
|
||||
|
||||
const createMockMatcherRootOnlyDeepVersion = (): FolderWildcardMatching<SortingSpec> => {
|
||||
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching()
|
||||
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching(checkIfImplicitSpec)
|
||||
matcher.addWildcardDefinition('/*', '/*')
|
||||
return matcher
|
||||
}
|
||||
|
||||
const createMockMatcherSimpleVersion = (): FolderWildcardMatching<SortingSpec> => {
|
||||
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching()
|
||||
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching(checkIfImplicitSpec)
|
||||
matcher.addWildcardDefinition('/Reviews/daily/*', '/Reviews/daily/*')
|
||||
matcher.addWildcardDefinition('/Reviews/daily/...', '/Reviews/daily/...')
|
||||
return matcher
|
||||
|
@ -108,21 +110,21 @@ describe('folderMatch', () => {
|
|||
expect(match3).toBe('/Reviews/daily/...')
|
||||
})
|
||||
it('should detect duplicate match children definitions for same path', () => {
|
||||
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching()
|
||||
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching(checkIfImplicitSpec)
|
||||
matcher.addWildcardDefinition('Archive/2020/...', 'First occurrence')
|
||||
const result = matcher.addWildcardDefinition('/Archive/2020/.../', 'Duplicate')
|
||||
|
||||
expect(result).toEqual({errorMsg: "Duplicate wildcard '...' specification for /Archive/2020/.../"})
|
||||
})
|
||||
it('should detect duplicate match all definitions for same path', () => {
|
||||
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching()
|
||||
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching(checkIfImplicitSpec)
|
||||
matcher.addWildcardDefinition('/Archive/2019/*', 'First occurrence')
|
||||
const result = matcher.addWildcardDefinition('Archive/2019/*', 'Duplicate')
|
||||
|
||||
expect(result).toEqual({errorMsg: "Duplicate wildcard '*' specification for Archive/2019/*"})
|
||||
})
|
||||
it('regexp-match by name works (order of regexp doesn\'t matter) case A', () => {
|
||||
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching()
|
||||
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching(checkIfImplicitSpec)
|
||||
matcher.addRegexpDefinition(/^daily$/, false, undefined, false, `r1`)
|
||||
matcher.addRegexpDefinition(/^daily$/, true, undefined, false, `r2`)
|
||||
matcher.addWildcardDefinition('/Reviews/*', `w1`)
|
||||
|
@ -134,7 +136,7 @@ describe('folderMatch', () => {
|
|||
expect(match2).toBe('r2')
|
||||
})
|
||||
it('regexp-match by name works (order of regexp doesn\'t matter) reversed case A', () => {
|
||||
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching()
|
||||
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching(checkIfImplicitSpec)
|
||||
matcher.addRegexpDefinition(/^daily$/, true, undefined, false, `r2`)
|
||||
matcher.addRegexpDefinition(/^daily$/, false, undefined, false, `r1`)
|
||||
matcher.addWildcardDefinition('/Reviews/*', `w1`)
|
||||
|
@ -146,7 +148,7 @@ describe('folderMatch', () => {
|
|||
expect(match2).toBe('r2')
|
||||
})
|
||||
it('regexp-match by path works (order of regexp doesn\'t matter) case A', () => {
|
||||
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching()
|
||||
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching(checkIfImplicitSpec)
|
||||
matcher.addRegexpDefinition(/^Reviews\/daily$/, false, undefined, false, `r1`)
|
||||
matcher.addRegexpDefinition(/^Reviews\/daily$/, true, undefined, false, `r2`)
|
||||
matcher.addWildcardDefinition('/Reviews/*', `w1`)
|
||||
|
@ -158,7 +160,7 @@ describe('folderMatch', () => {
|
|||
expect(match2).toBe('r1')
|
||||
})
|
||||
it('regexp-match by path works (order of regexp doesn\'t matter) reversed case A', () => {
|
||||
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching()
|
||||
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching(checkIfImplicitSpec)
|
||||
matcher.addRegexpDefinition(/^Reviews\/daily$/, true, undefined, false, `r2`)
|
||||
matcher.addRegexpDefinition(/^Reviews\/daily$/, false, undefined, false, `r1`)
|
||||
matcher.addWildcardDefinition('/Reviews/*', `w1`)
|
||||
|
@ -170,7 +172,7 @@ describe('folderMatch', () => {
|
|||
expect(match2).toBe('r1')
|
||||
})
|
||||
it('regexp-match by path and name for root level - order of regexp decides - case A', () => {
|
||||
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching()
|
||||
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching(checkIfImplicitSpec)
|
||||
matcher.addRegexpDefinition(/^daily$/, false, undefined, false, `r1`)
|
||||
matcher.addRegexpDefinition(/^daily$/, true, undefined, false, `r2`)
|
||||
matcher.addWildcardDefinition('/Reviews/*', `w1`)
|
||||
|
@ -179,7 +181,7 @@ describe('folderMatch', () => {
|
|||
expect(match).toBe('r2')
|
||||
})
|
||||
it('regexp-match by path and name for root level - order of regexp decides - reversed case A', () => {
|
||||
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching()
|
||||
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching(checkIfImplicitSpec)
|
||||
matcher.addRegexpDefinition(/^daily$/, true, undefined, false, `r2`)
|
||||
matcher.addRegexpDefinition(/^daily$/, false, undefined, false, `r1`)
|
||||
matcher.addWildcardDefinition('/Reviews/*', `w1`)
|
||||
|
@ -188,7 +190,7 @@ describe('folderMatch', () => {
|
|||
expect(match).toBe('r1')
|
||||
})
|
||||
it('regexp-match priorities - order of definitions irrelevant - unique priorities - case A', () => {
|
||||
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching()
|
||||
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching(checkIfImplicitSpec)
|
||||
matcher.addRegexpDefinition(/^freq\/daily$/, false, 3, false, `r1p3`)
|
||||
matcher.addRegexpDefinition(/^freq\/daily$/, false, 2, false, `r2p2`)
|
||||
matcher.addRegexpDefinition(/^freq\/daily$/, false, 1, false, `r3p1`)
|
||||
|
@ -198,7 +200,7 @@ describe('folderMatch', () => {
|
|||
expect(match).toBe('r1p3')
|
||||
})
|
||||
it('regexp-match priorities - order of definitions irrelevant - unique priorities - reversed case A', () => {
|
||||
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching()
|
||||
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching(checkIfImplicitSpec)
|
||||
matcher.addRegexpDefinition(/^freq\/daily$/, false, undefined, false, `r4pNone`)
|
||||
matcher.addRegexpDefinition(/^freq\/daily$/, false, 1, false, `r3p1`)
|
||||
matcher.addRegexpDefinition(/^freq\/daily$/, false, 2, false, `r2p2`)
|
||||
|
@ -208,7 +210,7 @@ describe('folderMatch', () => {
|
|||
expect(match).toBe('r1p3')
|
||||
})
|
||||
it('regexp-match priorities - order of definitions irrelevant - duplicate priorities - case A', () => {
|
||||
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching()
|
||||
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching(checkIfImplicitSpec)
|
||||
matcher.addRegexpDefinition(/^daily$/, true, 3, false, `r1p3a`)
|
||||
matcher.addRegexpDefinition(/^daily$/, true, 3, false, `r1p3b`)
|
||||
matcher.addRegexpDefinition(/^daily$/, true, 2, false, `r2p2a`)
|
||||
|
@ -221,7 +223,7 @@ describe('folderMatch', () => {
|
|||
expect(match).toBe('r1p3b')
|
||||
})
|
||||
it('regexp-match priorities - order of definitions irrelevant - unique priorities - reversed case A', () => {
|
||||
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching()
|
||||
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching(checkIfImplicitSpec)
|
||||
matcher.addRegexpDefinition(/^freq\/daily$/, false, undefined, false, `r4pNone`)
|
||||
matcher.addRegexpDefinition(/^freq\/daily$/, false, 1, false, `r3p1`)
|
||||
matcher.addRegexpDefinition(/^freq\/daily$/, false, 2, false, `r2p2`)
|
||||
|
@ -231,14 +233,14 @@ describe('folderMatch', () => {
|
|||
expect(match).toBe('r1p3')
|
||||
})
|
||||
it('regexp-match - edge case of matching the root folder - match by path', () => {
|
||||
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching()
|
||||
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching(checkIfImplicitSpec)
|
||||
matcher.addRegexpDefinition(/^\/$/, false, undefined, false, `r1`)
|
||||
// Path w/o leading / - this is how Obsidian supplies the path
|
||||
const match: SortingSpec | null = matcher.folderMatch('/', '')
|
||||
expect(match).toBe('r1')
|
||||
})
|
||||
it('regexp-match - edge case of matching the root folder - match by name not possible', () => {
|
||||
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching()
|
||||
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching(checkIfImplicitSpec)
|
||||
// Tricky regexp which can return zero length matches
|
||||
matcher.addRegexpDefinition(/.*/, true, undefined, false, `r1`)
|
||||
matcher.addWildcardDefinition('/*', `w1`)
|
||||
|
@ -247,7 +249,7 @@ describe('folderMatch', () => {
|
|||
expect(match).toBe('w1')
|
||||
})
|
||||
it('regexp-match - edge case of no match when only regexp rules present', () => {
|
||||
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching()
|
||||
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching(checkIfImplicitSpec)
|
||||
// Tricky regexp which can return zero length matches
|
||||
matcher.addRegexpDefinition(/abc/, true, undefined, false, `r1`)
|
||||
// Path w/o leading / - this is how Obsidian supplies the path
|
||||
|
|
|
@ -35,6 +35,8 @@ export interface AddingWildcardFailure {
|
|||
errorMsg: string
|
||||
}
|
||||
|
||||
export type CheckIfImplicitSpec<SortingSpec> = (s: SortingSpec) => boolean
|
||||
|
||||
export class FolderWildcardMatching<SortingSpec> {
|
||||
|
||||
// mimics the structure of folders, so for example tree.matchAll contains the matchAll flag for the root '/'
|
||||
|
@ -44,6 +46,9 @@ export class FolderWildcardMatching<SortingSpec> {
|
|||
|
||||
regexps: Array<FolderMatchingRegexp<SortingSpec>>
|
||||
|
||||
constructor(private checkIfImplicitSpec: CheckIfImplicitSpec<SortingSpec>) {
|
||||
}
|
||||
|
||||
// cache
|
||||
determinedWildcardRules: { [key: string]: DeterminedSortingSpec<SortingSpec> } = {}
|
||||
|
||||
|
@ -68,13 +73,13 @@ export class FolderWildcardMatching<SortingSpec> {
|
|||
}
|
||||
})
|
||||
if (lastComponent === MATCH_CHILDREN_PATH_TOKEN) {
|
||||
if (leafNode.matchChildren) {
|
||||
if (leafNode.matchChildren && !this.checkIfImplicitSpec(leafNode.matchChildren)) {
|
||||
return {errorMsg: `Duplicate wildcard '${lastComponent}' specification for ${wilcardDefinition}`}
|
||||
} else {
|
||||
leafNode.matchChildren = rule
|
||||
}
|
||||
} else { // Implicitly: MATCH_ALL_PATH_TOKEN
|
||||
if (leafNode.matchAll) {
|
||||
if (leafNode.matchAll && !this.checkIfImplicitSpec(leafNode.matchAll)) {
|
||||
return {errorMsg: `Duplicate wildcard '${lastComponent}' specification for ${wilcardDefinition}`}
|
||||
} else {
|
||||
leafNode.matchAll = rule
|
||||
|
|
|
@ -527,16 +527,23 @@ describe('SortingSpecProcessor', () => {
|
|||
const txtInputStandardObsidianSortAttr: string = `
|
||||
target-folder: AAA
|
||||
sorting: standard
|
||||
/ Some folder
|
||||
sorting: standard
|
||||
`
|
||||
|
||||
const expectedSortSpecForObsidianStandardSorting: { [key: string]: CustomSortSpec } = {
|
||||
"AAA": {
|
||||
defaultOrder: CustomSortOrder.standardObsidian,
|
||||
groups: [{
|
||||
exactText: 'Some folder',
|
||||
foldersOnly: true,
|
||||
order: CustomSortOrder.standardObsidian,
|
||||
type: CustomSortGroupType.ExactName
|
||||
}, {
|
||||
order: CustomSortOrder.standardObsidian,
|
||||
type: CustomSortGroupType.Outsiders
|
||||
}],
|
||||
outsidersGroupIdx: 0,
|
||||
outsidersGroupIdx: 1,
|
||||
targetFoldersPaths: ['AAA']
|
||||
}
|
||||
}
|
||||
|
@ -1687,11 +1694,13 @@ const txtInputErrorTooManyNumericSortSymbols: string = `
|
|||
% Chapter\\R+ ... page\\d+
|
||||
`
|
||||
|
||||
/* No longer applicable
|
||||
const txtInputErrorNestedStandardObsidianSortAttr: string = `
|
||||
target-folder: AAA
|
||||
/ Some folder
|
||||
sorting: standard
|
||||
`
|
||||
*/
|
||||
|
||||
const txtInputErrorPriorityEmptyFilePattern: string = `
|
||||
/!! /:
|
||||
|
@ -1797,6 +1806,7 @@ describe('SortingSpecProcessor error detection and reporting', () => {
|
|||
`${ERR_PREFIX} 9:TooManySortingSymbols Maximum one sorting symbol allowed per line ${ERR_SUFFIX_IN_LINE(2)}`)
|
||||
expect(errorsLogger).toHaveBeenNthCalledWith(2, ERR_LINE_TXT('% Chapter\\R+ ... page\\d+ '))
|
||||
})
|
||||
/* Problem no longer applicable
|
||||
it('should recognize error: nested standard obsidian sorting attribute', () => {
|
||||
const inputTxtArr: Array<string> = txtInputErrorNestedStandardObsidianSortAttr.split('\n')
|
||||
const result = processor.parseSortSpecFromText(inputTxtArr, 'mock-folder', 'custom-name-note.md')
|
||||
|
@ -1806,6 +1816,7 @@ describe('SortingSpecProcessor error detection and reporting', () => {
|
|||
`${ERR_PREFIX} 14:StandardObsidianSortAllowedOnlyAtFolderLevel The standard Obsidian sort order is only allowed at a folder level (not nested syntax) ${ERR_SUFFIX_IN_LINE(4)}`)
|
||||
expect(errorsLogger).toHaveBeenNthCalledWith(2, ERR_LINE_TXT(' sorting: standard'))
|
||||
})
|
||||
*/
|
||||
it('should recognize error: priority indicator alone', () => {
|
||||
const inputTxtArr: Array<string> = `
|
||||
/!
|
||||
|
|
|
@ -37,6 +37,7 @@ interface ProcessingContext {
|
|||
specs: Array<CustomSortSpec>
|
||||
currentSpec?: CustomSortSpec
|
||||
currentSpecGroup?: CustomSortGroup
|
||||
implicitSpec?: boolean
|
||||
|
||||
// Support for specific conditions (intentionally not generic approach)
|
||||
previousValidEntryWasTargetFolderAttr?: boolean // Entry in previous non-empty valid line
|
||||
|
@ -69,7 +70,7 @@ export enum ProblemCode {
|
|||
ItemToHideExactNameWithExtRequired,
|
||||
ItemToHideNoSupportForThreeDots,
|
||||
DuplicateWildcardSortSpecForSameFolder,
|
||||
StandardObsidianSortAllowedOnlyAtFolderLevel,
|
||||
ProblemNoLongerApplicable_StandardObsidianSortAllowedOnlyAtFolderLevel, // Placeholder kept to avoid refactoring of many unit tests (hardcoded error codes)
|
||||
PriorityNotAllowedOnOutsidersGroup,
|
||||
TooManyPriorityPrefixes,
|
||||
CombiningNotAllowedOnOutsidersGroup,
|
||||
|
@ -580,7 +581,7 @@ const ensureCollectionHasSortSpecByName = (collection?: SortSpecsCollection | nu
|
|||
const ensureCollectionHasSortSpecByWildcard = (collection?: SortSpecsCollection | null) => {
|
||||
collection = collection ?? {}
|
||||
if (!collection.sortSpecByWildcard) {
|
||||
collection.sortSpecByWildcard = new FolderWildcardMatching<CustomSortSpec>()
|
||||
collection.sortSpecByWildcard = new FolderWildcardMatching<CustomSortSpec>((spec: CustomSortSpec) => !!spec.implicit)
|
||||
}
|
||||
return collection
|
||||
}
|
||||
|
@ -605,35 +606,38 @@ const endsWithWildcardPatternSuffix = (path: string): boolean => {
|
|||
|
||||
enum WildcardPriority {
|
||||
NO_WILDCARD = 1,
|
||||
NO_WILDCARD_IMPLICIT,
|
||||
MATCH_CHILDREN,
|
||||
MATCH_ALL
|
||||
MATCH_CHILDREN_IMPLICIT,
|
||||
MATCH_ALL,
|
||||
MATCH_ALL_IMPLICIT
|
||||
}
|
||||
|
||||
const stripWildcardPatternSuffix = (path: string): {path: string, detectedWildcardPriority: number} => {
|
||||
const stripWildcardPatternSuffix = (path: string, ofImplicitSpec: boolean): {path: string, detectedWildcardPriority: number} => {
|
||||
if (path.endsWith(MATCH_ALL_SUFFIX)) {
|
||||
path = path.slice(0, -MATCH_ALL_SUFFIX.length)
|
||||
return {
|
||||
path: path.length > 0 ? path : '/',
|
||||
detectedWildcardPriority: WildcardPriority.MATCH_ALL
|
||||
detectedWildcardPriority: ofImplicitSpec ? WildcardPriority.MATCH_ALL_IMPLICIT : WildcardPriority.MATCH_ALL
|
||||
}
|
||||
}
|
||||
if (path.endsWith(MATCH_CHILDREN_1_SUFFIX)) {
|
||||
path = path.slice(0, -MATCH_CHILDREN_1_SUFFIX.length)
|
||||
return {
|
||||
path: path.length > 0 ? path : '/',
|
||||
detectedWildcardPriority: WildcardPriority.MATCH_CHILDREN,
|
||||
detectedWildcardPriority: ofImplicitSpec ? WildcardPriority.MATCH_CHILDREN_IMPLICIT : WildcardPriority.MATCH_CHILDREN
|
||||
}
|
||||
}
|
||||
if (path.endsWith(MATCH_CHILDREN_2_SUFFIX)) {
|
||||
path = path.slice(0, -MATCH_CHILDREN_2_SUFFIX.length)
|
||||
return {
|
||||
path: path.length > 0 ? path : '/',
|
||||
detectedWildcardPriority: WildcardPriority.MATCH_CHILDREN
|
||||
detectedWildcardPriority: ofImplicitSpec ? WildcardPriority.MATCH_CHILDREN_IMPLICIT : WildcardPriority.MATCH_CHILDREN
|
||||
}
|
||||
}
|
||||
return {
|
||||
path: path,
|
||||
detectedWildcardPriority: WildcardPriority.NO_WILDCARD
|
||||
detectedWildcardPriority: ofImplicitSpec ? WildcardPriority.NO_WILDCARD_IMPLICIT : WildcardPriority.NO_WILDCARD
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -730,12 +734,14 @@ export class SortingSpecProcessor {
|
|||
parseSortSpecFromText(text: Array<string>,
|
||||
folderPath: string,
|
||||
sortingSpecFileName: string,
|
||||
collection?: SortSpecsCollection | null
|
||||
collection?: SortSpecsCollection | null,
|
||||
implicitSpec?: boolean
|
||||
): SortSpecsCollection | null | undefined {
|
||||
// reset / init processing state after potential previous invocation
|
||||
this.ctx = {
|
||||
folderPath: folderPath, // location of the sorting spec file
|
||||
specs: []
|
||||
specs: [],
|
||||
implicitSpec: implicitSpec
|
||||
};
|
||||
this.currentEntryLine = null
|
||||
this.currentEntryLineIdx = null
|
||||
|
@ -842,7 +848,7 @@ export class SortingSpecProcessor {
|
|||
for (let idx = 0; idx < spec.targetFoldersPaths.length; idx++) {
|
||||
const originalPath = spec.targetFoldersPaths[idx]
|
||||
if (!originalPath.startsWith(MatchFolderNameLexeme) && !originalPath.startsWith(MatchFolderByRegexpLexeme)) {
|
||||
const {path, detectedWildcardPriority} = stripWildcardPatternSuffix(originalPath)
|
||||
const {path, detectedWildcardPriority} = stripWildcardPatternSuffix(originalPath, !!spec.implicit)
|
||||
let storeTheSpec: boolean = true
|
||||
const preexistingSortSpecPriority: WildcardPriority = this.pathMatchPriorityForPath[path]
|
||||
if (preexistingSortSpecPriority) {
|
||||
|
@ -974,10 +980,6 @@ export class SortingSpecProcessor {
|
|||
this.problem(ProblemCode.DuplicateOrderAttr, `Duplicate order specification for a sorting rule of folder ${folderPathsForProblemMsg}`)
|
||||
return false;
|
||||
}
|
||||
if ((attr.value as RecognizedOrderValue).order === CustomSortOrder.standardObsidian) {
|
||||
this.problem(ProblemCode.StandardObsidianSortAllowedOnlyAtFolderLevel, `The standard Obsidian sort order is only allowed at a folder level (not nested syntax)`)
|
||||
return false;
|
||||
}
|
||||
this.ctx.currentSpecGroup.order = (attr.value as RecognizedOrderValue).order
|
||||
this.ctx.currentSpecGroup.byMetadataField = (attr.value as RecognizedOrderValue).applyToMetadataField
|
||||
this.ctx.currentSpecGroup.secondaryOrder = (attr.value as RecognizedOrderValue).secondaryOrder
|
||||
|
@ -1453,7 +1455,8 @@ export class SortingSpecProcessor {
|
|||
private putNewSpecForNewTargetFolder(folderPath?: string): CustomSortSpec {
|
||||
const newSpec: CustomSortSpec = {
|
||||
targetFoldersPaths: [folderPath ?? this.ctx.folderPath],
|
||||
groups: []
|
||||
groups: [],
|
||||
implicit: this.ctx.implicitSpec
|
||||
}
|
||||
|
||||
this.ctx.specs.push(newSpec);
|
||||
|
|
147
src/main.ts
147
src/main.ts
|
@ -1,6 +1,6 @@
|
|||
import {
|
||||
App,
|
||||
FileExplorerView,
|
||||
FileExplorerView, Menu, MenuItem,
|
||||
MetadataCache,
|
||||
normalizePath,
|
||||
Notice,
|
||||
|
@ -13,10 +13,10 @@ import {
|
|||
TAbstractFile,
|
||||
TFile,
|
||||
TFolder,
|
||||
Vault
|
||||
Vault, WorkspaceLeaf
|
||||
} from 'obsidian';
|
||||
import {around} from 'monkey-around';
|
||||
import {folderSort} from './custom-sort/custom-sort';
|
||||
import {folderSort, ProcessingContext} from './custom-sort/custom-sort';
|
||||
import {SortingSpecProcessor, SortSpecsCollection} from './custom-sort/sorting-spec-processor';
|
||||
import {CustomSortOrder, CustomSortSpec} from './custom-sort/custom-sort-types';
|
||||
|
||||
|
@ -29,6 +29,9 @@ import {
|
|||
ICON_SORT_SUSPENDED_GENERAL_ERROR,
|
||||
ICON_SORT_SUSPENDED_SYNTAX_ERROR
|
||||
} from "./custom-sort/icons";
|
||||
import {getStarredPlugin} from "./utils/StarredPluginSignature";
|
||||
import {getBookmarksPlugin} from "./utils/BookmarksCorePluginSignature";
|
||||
import {getIconFolderPlugin} from "./utils/ObsidianIconFolderPluginSignature";
|
||||
|
||||
interface CustomSortPluginSettings {
|
||||
additionalSortspecFile: string
|
||||
|
@ -36,7 +39,8 @@ interface CustomSortPluginSettings {
|
|||
statusBarEntryEnabled: boolean
|
||||
notificationsEnabled: boolean
|
||||
mobileNotificationsEnabled: boolean
|
||||
enableAutomaticBookmarksOrderIntegration: boolean
|
||||
automaticBookmarksIntegration: boolean
|
||||
bookmarksGroupToConsumeAsOrderingReference: string
|
||||
}
|
||||
|
||||
const DEFAULT_SETTINGS: CustomSortPluginSettings = {
|
||||
|
@ -45,7 +49,8 @@ const DEFAULT_SETTINGS: CustomSortPluginSettings = {
|
|||
statusBarEntryEnabled: true,
|
||||
notificationsEnabled: true,
|
||||
mobileNotificationsEnabled: false,
|
||||
enableAutomaticBookmarksOrderIntegration: false
|
||||
automaticBookmarksIntegration: false,
|
||||
bookmarksGroupToConsumeAsOrderingReference: 'sortspec'
|
||||
}
|
||||
|
||||
const SORTSPEC_FILE_NAME: string = 'sortspec.md'
|
||||
|
@ -53,6 +58,13 @@ const SORTINGSPEC_YAML_KEY: string = 'sorting-spec'
|
|||
|
||||
const ERROR_NOTICE_TIMEOUT: number = 10000
|
||||
|
||||
const ImplicitSortspecForBookmarksIntegration: string = `
|
||||
target-folder: /*
|
||||
bookmarked:
|
||||
< by-bookmarks-order
|
||||
sorting: standard
|
||||
`
|
||||
|
||||
// the monkey-around package doesn't export the below type
|
||||
type MonkeyAroundUninstaller = () => void
|
||||
|
||||
|
@ -82,12 +94,13 @@ export default class CustomSortPlugin extends Plugin {
|
|||
this.sortSpecCache = null
|
||||
const processor: SortingSpecProcessor = new SortingSpecProcessor()
|
||||
|
||||
if (this.settings.enableAutomaticBookmarksOrderIntegration) {
|
||||
if (this.settings.automaticBookmarksIntegration) {
|
||||
this.sortSpecCache = processor.parseSortSpecFromText(
|
||||
'target-folder: /*\n< by-bookmarks-order'.split('\n'),
|
||||
ImplicitSortspecForBookmarksIntegration.split('\n'),
|
||||
'System internal path', // Dummy unused value, there are no errors in the internal spec
|
||||
'System internal file', // Dummy unused value, there are no errors in the internal spec
|
||||
this.sortSpecCache
|
||||
this.sortSpecCache,
|
||||
true // Implicit sorting spec generation
|
||||
)
|
||||
console.log('Auto injected sort spec')
|
||||
console.log(this.sortSpecCache)
|
||||
|
@ -296,6 +309,29 @@ export default class CustomSortPlugin extends Plugin {
|
|||
}
|
||||
})
|
||||
);
|
||||
|
||||
this.registerEvent(
|
||||
this.app.workspace.on("file-menu", (menu: Menu, file: TAbstractFile, source: string, leaf?: WorkspaceLeaf) => {
|
||||
const bookmarkThisMenuItem = (item: MenuItem) => {
|
||||
// TODO: if already bookmarked in the 'custom sort' group (or its descendants) don't show
|
||||
item.setTitle('Custom sort: bookmark for sorting.');
|
||||
item.setIcon('hashtag');
|
||||
item.onClick(() => {
|
||||
console.log(`custom-sort: bookmark this clicked ${source}`)
|
||||
});
|
||||
};
|
||||
const bookmarkAllMenuItem = (item: MenuItem) => {
|
||||
item.setTitle('Custom sort: bookmark all siblings for sorting.');
|
||||
item.setIcon('hashtag');
|
||||
item.onClick(() => {
|
||||
console.log(`custom-sort: bookmark all siblings clicked ${source}`)
|
||||
});
|
||||
};
|
||||
|
||||
menu.addItem(bookmarkThisMenuItem)
|
||||
menu.addItem(bookmarkAllMenuItem)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
registerCommands() {
|
||||
|
@ -335,6 +371,10 @@ 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);
|
||||
|
@ -349,21 +389,34 @@ export default class CustomSortPlugin extends Plugin {
|
|||
const folder: TFolder = this.file
|
||||
let sortSpec: CustomSortSpec | null | undefined = plugin.sortSpecCache?.sortSpecByPath?.[folder.path]
|
||||
sortSpec = sortSpec ?? plugin.sortSpecCache?.sortSpecByName?.[folder.name]
|
||||
if (sortSpec) {
|
||||
if (sortSpec.defaultOrder === CustomSortOrder.standardObsidian) {
|
||||
sortSpec = null // A folder is explicitly excluded from custom sorting plugin
|
||||
}
|
||||
} else if (plugin.sortSpecCache?.sortSpecByWildcard) {
|
||||
|
||||
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)
|
||||
if (sortSpec?.defaultOrder === CustomSortOrder.standardObsidian) {
|
||||
/* 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
|
||||
|
||||
if (sortSpec) {
|
||||
sortSpec.plugin = plugin
|
||||
return folderSort.call(this, sortSpec, ...args);
|
||||
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);
|
||||
} else {
|
||||
console.log(`NO Sortspec for folder ${folder.path}`)
|
||||
return old.call(this, ...args);
|
||||
}
|
||||
};
|
||||
|
@ -490,17 +543,67 @@ class CustomSortSettingTab extends PluginSettingTab {
|
|||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
const bookmarksIntegrationDescription: DocumentFragment = sanitizeHTMLToDom(
|
||||
'If enabled, order of files and folders in File Explorer will reflect the order '
|
||||
+ 'of bookmarked items in the bookmarks (core plugin) view.'
|
||||
+ '<br>'
|
||||
+ '<p>To separate regular bookmarks from the bookmarks created for sorting, you can put '
|
||||
+ 'the latter in a separate dedicated bookmarks group. The default name of the group is '
|
||||
+ "'" + DEFAULT_SETTINGS.bookmarksGroupToConsumeAsOrderingReference + "' "
|
||||
+ 'and you can change the group name in the configuration field below.'
|
||||
+ '<br>'
|
||||
+ 'If left empty, all the bookmarked items will be used to impose the order in File Explorer.</p>'
|
||||
+ '<p>More information on this functionality in the '
|
||||
+ '<a href="https://github.com/SebastianMC/obsidian-custom-sort/blob/master/docs/manual.md#bookmarks-plugin-integration">'
|
||||
+ 'manual</a> of this custom-sort plugin.'
|
||||
+ '</p>'
|
||||
)
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Enable automatic integration with core Bookmarks plugin')
|
||||
.setName('Automatic integration with core Bookmarks plugin (for indirect drag & drop ordering)')
|
||||
// TODO: add a nice description here
|
||||
.setDesc('Details TBD. TODO: add a nice description here')
|
||||
.setDesc(bookmarksIntegrationDescription)
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(this.plugin.settings.enableAutomaticBookmarksOrderIntegration)
|
||||
.setValue(this.plugin.settings.automaticBookmarksIntegration)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.enableAutomaticBookmarksOrderIntegration = value;
|
||||
this.plugin.settings.automaticBookmarksIntegration = value;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
// TODO: expose additional configuration setting to specify group path in Bookmarks, if auto-integration with bookmarks is enabled
|
||||
new Setting(containerEl)
|
||||
.setName('Name of the group in Bookmarks from which to read the order of items')
|
||||
.setDesc('See above.')
|
||||
.addText(text => text
|
||||
.setPlaceholder('e.g. Group for sorting')
|
||||
.setValue(this.plugin.settings.bookmarksGroupToConsumeAsOrderingReference)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.bookmarksGroupToConsumeAsOrderingReference = value.trim();
|
||||
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: 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: fix error
|
||||
// bookmarks integration - for root folder and for other folders
|
||||
// (check for the case:
|
||||
// target-folder: /*
|
||||
// sorting: standard
|
||||
|
||||
// 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
|
||||
|
|
|
@ -86,27 +86,29 @@ export const getBookmarksPlugin = (app?: App): Bookmarks_PluginInstance | undefi
|
|||
}
|
||||
}
|
||||
|
||||
type TraverseCallback = (item: BookmarkedItem) => boolean | void
|
||||
type TraverseCallback = (item: BookmarkedItem, groupPath: string) => boolean | void
|
||||
|
||||
const traverseBookmarksCollection = (items: Array<BookmarkedItem>, callback: TraverseCallback) => {
|
||||
const recursiveTraversal = (collection: Array<BookmarkedItem>) => {
|
||||
const recursiveTraversal = (collection: Array<BookmarkedItem>, groupPath: string) => {
|
||||
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);
|
||||
if (callback(item, groupPath)) return;
|
||||
if ('group' === item.type) recursiveTraversal(item.items, `${groupPath}${groupPath ? '/' : ''}${item.title}`);
|
||||
}
|
||||
};
|
||||
recursiveTraversal(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 getOrderedBookmarks = (plugin: Bookmarks_PluginInstance, bookmarksGroup?: string): 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 groupNamePrefix: string = bookmarksGroup ? `${bookmarksGroup}/` : ''
|
||||
const consumeItem = (item: BookmarkedItem, groupPath: string) => {
|
||||
if (groupNamePrefix && !groupPath.startsWith(groupNamePrefix)) {
|
||||
return
|
||||
}
|
||||
const isFile: boolean = item.type === 'file'
|
||||
const isAnchor: boolean = isFile && !!(item as BookmarkedFile).subpath
|
||||
const isFolder: boolean = item.type === 'folder'
|
||||
|
@ -133,9 +135,9 @@ const getOrderedBookmarks = (plugin: Bookmarks_PluginInstance): OrderedBookmarks
|
|||
// 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 => {
|
||||
export const determineBookmarkOrder = (path: string, plugin: Bookmarks_PluginInstance, bookmarksGroup?: string): number | undefined => {
|
||||
if (!bookmarksCache) {
|
||||
bookmarksCache = getOrderedBookmarks(plugin)
|
||||
bookmarksCache = getOrderedBookmarks(plugin, bookmarksGroup)
|
||||
bookmarksCacheTimestamp = Date.now()
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue