#45 - Feature: explicit matching of 'starred' items
- new keyword added to support items starred with Obsidian core plugin 'Starred' - the keyword is `starred:` - detection and more user friendly handling of the general error condition when the File Explorer is not available - new ribbon status icon shape to indicate the general error plus detailed error logged to the console
This commit is contained in:
parent
9fbc98c163
commit
0ba423ce4b
|
@ -578,6 +578,12 @@ States of the ribbon icon:
|
|||
- Fix the problem in specification and click the ribbon icon to re-enable custom sorting.
|
||||
- If syntax error is not fixed, the notice baloon with show error details. Syntax error details are also visible in
|
||||
the developer console
|
||||
-  Plugin suspended. General error.
|
||||
- File Explorer not available or other type of general error
|
||||
- File Explorer is a core Obsidian plugin (named __Files__) and thus can be disabled in Obsidian settings
|
||||
- Some community plugins (like __MAKE.md__) also disable the File Explorer by default
|
||||
- See obsidinan developer console for detailed error message
|
||||
- To fix the problem, enable the File Explorer (in Obsidian or in the community plugin responsible for hididing it)
|
||||
-  Plugin enabled but the custom sorting was not applied.
|
||||
- This can happen when reinstalling the plugin and in similar cases
|
||||
- Click the ribbon icon twice to re-enable the custom sorting.
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 2.6 KiB |
|
@ -1,6 +1,6 @@
|
|||
> Document is partial, creation in progress
|
||||
> Please refer to [README.md](../README.md) for usage examples
|
||||
> Check [syntax-reference.md](./syntax-reference.md), maybe that file has already some content?
|
||||
> Please refer to [README.md](../README.md) for more usage examples
|
||||
> Check also [syntax-reference.md](./syntax-reference.md)
|
||||
|
||||
---
|
||||
Some sections added ad-hoc, to be integrated later
|
||||
|
@ -164,3 +164,81 @@ 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
|
||||
|
||||
## Matching starred items
|
||||
|
||||
The Obsidian core plugin `Starred` allows the user to 'star' files
|
||||
The keyword `starred:` allows matching such items. A folder is considered _starred_ if at least one immediate child file is starred
|
||||
|
||||
**Example:**
|
||||
|
||||
Consider the below sorting spec:
|
||||
```yaml
|
||||
---
|
||||
sorting-spec: |
|
||||
// Example sorting configuration showing
|
||||
// how to push the starred items to the top
|
||||
//
|
||||
// the line below applies the sorting specification
|
||||
// to all folders in the vault
|
||||
target-folder: /*
|
||||
// the sorting order specification for the target folder(s)
|
||||
> advanced created
|
||||
// the first group of items captures the files and folders which
|
||||
// are 'starred' in Obsidian core 'Starred' plugin.
|
||||
// Items in this group inherit the sorting order of target folder
|
||||
starred:
|
||||
// No further groups specified, which means all other items follow the
|
||||
// starred items, also in the order specified
|
||||
---
|
||||
```
|
||||
|
||||
The above sorting specification pushes the _starred_ items to the top
|
||||
To achieve the opposite effect and push the starred items to the bottom, use the below sorting spec:
|
||||
|
||||
```yaml
|
||||
---
|
||||
sorting-spec: |
|
||||
// Example sorting configuration showing
|
||||
// how to push the starred items to the bottom
|
||||
//
|
||||
// the line below applies the sorting specification
|
||||
// to all folders in the vault
|
||||
target-folder: /*
|
||||
// the sorting order specification for the target folder(s)
|
||||
> a-z
|
||||
// the first group of items captures all of the files and folders which don't match any other sorting rule
|
||||
// Items in this group inherit the sorting order of target folder
|
||||
/folders:files
|
||||
// the second group of items captures the files and folders which
|
||||
// are 'starred' in Obsidian core 'Starred' plugin.
|
||||
// Items in this group also inherit the sorting order of target folder
|
||||
starred:
|
||||
---
|
||||
```
|
||||
|
||||
For a broader view, the same effect (as in previous example) can be achieved using the priorities
|
||||
of sorting rules:
|
||||
|
||||
```yaml
|
||||
---
|
||||
sorting-spec: |
|
||||
// Example sorting configuration showing
|
||||
// how to push the starred items to the bottom
|
||||
//
|
||||
// the line below applies the sorting specification
|
||||
// to all folders in the vault
|
||||
target-folder: /*
|
||||
// the sorting order specification for the target folder(s)
|
||||
> a-z
|
||||
// the first group of items captures all of the files and folders
|
||||
// Items in this group inherit the sorting order of target folder
|
||||
...
|
||||
// the second group of items captures the files and folders which
|
||||
// are 'starred' in Obsidian core 'Starred' plugin.
|
||||
// Items in this group also inherit the sorting order of target folder
|
||||
// The priority '/!' indicator tells to evaluate this sorting rule before other rules
|
||||
// If it were not used, the prevoius rule '...' would eat all of the folders and items
|
||||
// and the starred items wouldn't be pushed to the bottom
|
||||
/! starred:
|
||||
---
|
||||
```
|
||||
|
|
|
@ -144,6 +144,7 @@ Some tokens have shorter equivalents, which can be used interchangeably:
|
|||
- `/:files` --> `/:` e.g. `/:files Chapter \.d+ ...` is equivalent to `/: Chapter \.d+ ...`
|
||||
- `/:files.` --> `/:.` e.g. `/:files. ... \-D+.md` is equivalent to `/:. ... \-D+.md`
|
||||
- `/folders` --> `/` e.g. `/folders Archive...` is equivalent to `/ Archive...`
|
||||
- `/folders:files` --> `%` e.g. `/folders:files Chapter...` is equivalent to `% Chapter...`
|
||||
|
||||
Additional shorter equivalents to allow single-liners like `sorting-spec: \< a-z`:
|
||||
- `order-asc:` --> `\<` e.g. `order-asc: modified` is equivalent to `\< modified`
|
||||
|
|
|
@ -7,7 +7,8 @@ export enum CustomSortGroupType {
|
|||
ExactPrefix, // ... while the Outsiders captures items which didn't match any of other defined groups
|
||||
ExactSuffix,
|
||||
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
|
||||
}
|
||||
|
||||
export enum CustomSortOrder {
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
} from './custom-sort';
|
||||
import {CustomSortGroupType, CustomSortOrder, CustomSortSpec, RegExpSpec} from './custom-sort-types';
|
||||
import {CompoundDashNumberNormalizerFn, CompoundDotRomanNumberNormalizerFn} from "./sorting-spec-processor";
|
||||
import {findStarredFile_pathParam, Starred_PluginInstance} from "../utils/StarredPluginSignature";
|
||||
|
||||
const mockTFile = (basename: string, ext: string, size?: number, ctime?: number, mtime?: number): TFile => {
|
||||
return {
|
||||
|
@ -51,7 +52,7 @@ const mockTFolderWithChildren = (name: string): TFolder => {
|
|||
const child4: TFile = mockTFile('Child file 2 created as newest, not modified at all', 'md', 100, TIMESTAMP_NEWEST, TIMESTAMP_NEWEST)
|
||||
const child5: TFile = mockTFile('Child file 3 created inbetween, modified inbetween', 'md', 100, TIMESTAMP_INBETWEEN, TIMESTAMP_INBETWEEN)
|
||||
|
||||
return mockTFolder('Mock parent folder', [child1, child2, child3, child4, child5])
|
||||
return mockTFolder(name, [child1, child2, child3, child4, child5])
|
||||
}
|
||||
|
||||
const MockedLoc: Pos = {
|
||||
|
@ -879,6 +880,193 @@ describe('determineSortingGroup', () => {
|
|||
} as FolderItemForSorting);
|
||||
})
|
||||
})
|
||||
describe('CustomSortGroupType.StarredOnly', () => {
|
||||
it('should not match not starred file', () => {
|
||||
// given
|
||||
const file: TFile = mockTFile('References', 'md', 111, MOCK_TIMESTAMP + 222, MOCK_TIMESTAMP + 333);
|
||||
const sortSpec: CustomSortSpec = {
|
||||
targetFoldersPaths: ['/'],
|
||||
groups: [{
|
||||
type: CustomSortGroupType.StarredOnly
|
||||
}]
|
||||
}
|
||||
const starredPluginInstance: Partial<Starred_PluginInstance> = {
|
||||
findStarredFile: jest.fn( function(filePath: findStarredFile_pathParam): TFile | null {
|
||||
return null
|
||||
})
|
||||
}
|
||||
|
||||
// when
|
||||
const result = determineSortingGroup(file, sortSpec, {
|
||||
starredPluginInstance: starredPluginInstance as Starred_PluginInstance
|
||||
})
|
||||
|
||||
// then
|
||||
expect(result).toEqual({
|
||||
groupIdx: 1, // The lastIdx+1, group not determined
|
||||
isFolder: false,
|
||||
sortString: "References.md",
|
||||
ctimeNewest: MOCK_TIMESTAMP + 222,
|
||||
ctimeOldest: MOCK_TIMESTAMP + 222,
|
||||
mtime: MOCK_TIMESTAMP + 333,
|
||||
path: 'Some parent folder/References.md'
|
||||
});
|
||||
expect(starredPluginInstance.findStarredFile).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
it('should match starred file', () => {
|
||||
// given
|
||||
const file: TFile = mockTFile('References', 'md', 111, MOCK_TIMESTAMP + 222, MOCK_TIMESTAMP + 333);
|
||||
const sortSpec: CustomSortSpec = {
|
||||
targetFoldersPaths: ['/'],
|
||||
groups: [{
|
||||
type: CustomSortGroupType.StarredOnly
|
||||
}]
|
||||
}
|
||||
const starredPluginInstance: Partial<Starred_PluginInstance> = {
|
||||
findStarredFile: jest.fn( function(filePath: findStarredFile_pathParam): TFile | null {
|
||||
return filePath.path === 'Some parent folder/References.md' ? file : null
|
||||
})
|
||||
}
|
||||
|
||||
// when
|
||||
const result = determineSortingGroup(file, sortSpec, {
|
||||
starredPluginInstance: starredPluginInstance as Starred_PluginInstance
|
||||
})
|
||||
|
||||
// then
|
||||
expect(result).toEqual({
|
||||
groupIdx: 0,
|
||||
isFolder: false,
|
||||
sortString: "References.md",
|
||||
ctimeNewest: MOCK_TIMESTAMP + 222,
|
||||
ctimeOldest: MOCK_TIMESTAMP + 222,
|
||||
mtime: MOCK_TIMESTAMP + 333,
|
||||
path: 'Some parent folder/References.md'
|
||||
});
|
||||
expect(starredPluginInstance.findStarredFile).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
it('should not match empty folder', () => {
|
||||
// given
|
||||
const folder: TFolder = mockTFolder('TestEmptyFolder');
|
||||
const sortSpec: CustomSortSpec = {
|
||||
targetFoldersPaths: ['/'],
|
||||
groups: [{
|
||||
type: CustomSortGroupType.StarredOnly
|
||||
}]
|
||||
}
|
||||
const starredPluginInstance: Partial<Starred_PluginInstance> = {
|
||||
findStarredFile: jest.fn( function(filePath: findStarredFile_pathParam): TFile | null {
|
||||
return filePath.path === 'Some parent folder/References.md' ? {} as TFile : null
|
||||
})
|
||||
}
|
||||
|
||||
// when
|
||||
const result = determineSortingGroup(folder, sortSpec, {
|
||||
starredPluginInstance: starredPluginInstance as Starred_PluginInstance
|
||||
})
|
||||
|
||||
// then
|
||||
expect(result).toEqual({
|
||||
groupIdx: 1, // The lastIdx+1, group not determined
|
||||
isFolder: true,
|
||||
sortString: "TestEmptyFolder",
|
||||
ctimeNewest: 0,
|
||||
ctimeOldest: 0,
|
||||
mtime: 0,
|
||||
path: 'TestEmptyFolder',
|
||||
folder: {
|
||||
children: [],
|
||||
isRoot: expect.any(Function),
|
||||
name: "TestEmptyFolder",
|
||||
parent: {},
|
||||
path: "TestEmptyFolder",
|
||||
vault: {}
|
||||
}
|
||||
});
|
||||
expect(starredPluginInstance.findStarredFile).not.toHaveBeenCalled()
|
||||
})
|
||||
it('should not match folder w/o starred items', () => {
|
||||
// given
|
||||
const folder: TFolder = mockTFolderWithChildren('TestEmptyFolder');
|
||||
const sortSpec: CustomSortSpec = {
|
||||
targetFoldersPaths: ['/'],
|
||||
groups: [{
|
||||
type: CustomSortGroupType.StarredOnly
|
||||
}]
|
||||
}
|
||||
const starredPluginInstance: Partial<Starred_PluginInstance> = {
|
||||
findStarredFile: jest.fn( function(filePath: findStarredFile_pathParam): TFile | null {
|
||||
return filePath.path === 'Some parent folder/References.md' ? {} as TFile : null
|
||||
})
|
||||
}
|
||||
|
||||
// when
|
||||
const result = determineSortingGroup(folder, sortSpec, {
|
||||
starredPluginInstance: starredPluginInstance as Starred_PluginInstance
|
||||
})
|
||||
|
||||
// then
|
||||
expect(result).toEqual({
|
||||
groupIdx: 1, // The lastIdx+1, group not determined
|
||||
isFolder: true,
|
||||
sortString: "TestEmptyFolder",
|
||||
ctimeNewest: 0,
|
||||
ctimeOldest: 0,
|
||||
mtime: 0,
|
||||
path: 'TestEmptyFolder',
|
||||
folder: {
|
||||
children: expect.any(Array),
|
||||
isRoot: expect.any(Function),
|
||||
name: "TestEmptyFolder",
|
||||
parent: {},
|
||||
path: "TestEmptyFolder",
|
||||
vault: {}
|
||||
}
|
||||
});
|
||||
expect(starredPluginInstance.findStarredFile).toHaveBeenCalledTimes(folder.children.filter(f => (f as any).isRoot === undefined).length)
|
||||
})
|
||||
it('should match folder with one starred item', () => {
|
||||
// given
|
||||
const folder: TFolder = mockTFolderWithChildren('TestEmptyFolder');
|
||||
const sortSpec: CustomSortSpec = {
|
||||
targetFoldersPaths: ['/'],
|
||||
groups: [{
|
||||
type: CustomSortGroupType.StarredOnly
|
||||
}]
|
||||
}
|
||||
const starredPluginInstance: Partial<Starred_PluginInstance> = {
|
||||
findStarredFile: jest.fn(function (filePath: findStarredFile_pathParam): TFile | null {
|
||||
return filePath.path === 'Some parent folder/Child file 2 created as newest, not modified at all.md' ? {} as TFile : null
|
||||
})
|
||||
}
|
||||
|
||||
// when
|
||||
const result = determineSortingGroup(folder, sortSpec, {
|
||||
starredPluginInstance: starredPluginInstance as Starred_PluginInstance
|
||||
})
|
||||
|
||||
// then
|
||||
expect(result).toEqual({
|
||||
groupIdx: 0,
|
||||
isFolder: true,
|
||||
sortString: "TestEmptyFolder",
|
||||
ctimeNewest: 0,
|
||||
ctimeOldest: 0,
|
||||
mtime: 0,
|
||||
path: 'TestEmptyFolder',
|
||||
folder: {
|
||||
children: expect.any(Array),
|
||||
isRoot: expect.any(Function),
|
||||
name: "TestEmptyFolder",
|
||||
parent: {},
|
||||
path: "TestEmptyFolder",
|
||||
vault: {}
|
||||
}
|
||||
});
|
||||
// assume optimized checking of starred items -> first match ends the check
|
||||
expect(starredPluginInstance.findStarredFile).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
})
|
||||
describe('when sort by metadata is involved', () => {
|
||||
it('should correctly read direct metadata from File item (order by metadata set on group) alph', () => {
|
||||
// given
|
||||
|
@ -1367,34 +1555,10 @@ describe('determineFolderDatesIfNeeded', () => {
|
|||
}],
|
||||
outsidersGroupIdx: OUTSIDERS_GROUP_IDX
|
||||
}
|
||||
const cardinality = {[OUTSIDERS_GROUP_IDX]: 10} // Group 0 contains 10 items
|
||||
|
||||
// when
|
||||
const result: FolderItemForSorting = determineSortingGroup(folder, sortSpec)
|
||||
determineFolderDatesIfNeeded([result], sortSpec, cardinality)
|
||||
|
||||
// then
|
||||
expect(result.ctimeOldest).toEqual(DEFAULT_FOLDER_CTIME)
|
||||
expect(result.ctimeNewest).toEqual(DEFAULT_FOLDER_CTIME)
|
||||
expect(result.mtime).toEqual(DEFAULT_FOLDER_CTIME)
|
||||
})
|
||||
it('should not be triggered if not needed - the folder is an only item', () => {
|
||||
// given
|
||||
const folder: TFolder = mockTFolderWithChildren('Test folder 1')
|
||||
const OUTSIDERS_GROUP_IDX = 0
|
||||
const sortSpec: CustomSortSpec = {
|
||||
targetFoldersPaths: ['/'],
|
||||
groups: [{
|
||||
type: CustomSortGroupType.Outsiders,
|
||||
order: CustomSortOrder.byModifiedTimeAdvanced
|
||||
}],
|
||||
outsidersGroupIdx: OUTSIDERS_GROUP_IDX
|
||||
}
|
||||
const cardinality = {[OUTSIDERS_GROUP_IDX]: 1} // Group 0 contains the folder alone
|
||||
|
||||
// when
|
||||
const result: FolderItemForSorting = determineSortingGroup(folder, sortSpec)
|
||||
determineFolderDatesIfNeeded([result], sortSpec, cardinality)
|
||||
determineFolderDatesIfNeeded([result], sortSpec)
|
||||
|
||||
// then
|
||||
expect(result.ctimeOldest).toEqual(DEFAULT_FOLDER_CTIME)
|
||||
|
@ -1413,11 +1577,10 @@ describe('determineFolderDatesIfNeeded', () => {
|
|||
}],
|
||||
outsidersGroupIdx: OUTSIDERS_GROUP_IDX
|
||||
}
|
||||
const cardinality = {[OUTSIDERS_GROUP_IDX]: 10} // Group 0 contains 10 items
|
||||
|
||||
// when
|
||||
const result: FolderItemForSorting = determineSortingGroup(folder, sortSpec)
|
||||
determineFolderDatesIfNeeded([result], sortSpec, cardinality)
|
||||
determineFolderDatesIfNeeded([result], sortSpec)
|
||||
|
||||
// then
|
||||
expect(result.ctimeOldest).toEqual(TIMESTAMP_OLDEST)
|
||||
|
|
|
@ -1,4 +1,16 @@
|
|||
import {FrontMatterCache, requireApiVersion, TAbstractFile, TFile, TFolder} from 'obsidian';
|
||||
import {
|
||||
App,
|
||||
FrontMatterCache,
|
||||
InstalledPlugin,
|
||||
requireApiVersion,
|
||||
TAbstractFile,
|
||||
TFile,
|
||||
TFolder
|
||||
} from 'obsidian';
|
||||
import {
|
||||
Starred_PluginInstance,
|
||||
StarredPlugin_findStarredFile_methodName
|
||||
} from '../utils/StarredPluginSignature'
|
||||
import {
|
||||
CustomSortGroup,
|
||||
CustomSortGroupType,
|
||||
|
@ -140,7 +152,11 @@ export const matchGroupRegex = (theRegex: RegExpSpec, nameForMatching: string):
|
|||
return [false, undefined, undefined]
|
||||
}
|
||||
|
||||
export const determineSortingGroup = function (entry: TFile | TFolder, spec: CustomSortSpec): FolderItemForSorting {
|
||||
export interface Context {
|
||||
starredPluginInstance?: Starred_PluginInstance
|
||||
}
|
||||
|
||||
export const determineSortingGroup = function (entry: TFile | TFolder, spec: CustomSortSpec, ctx?: Context): FolderItemForSorting {
|
||||
let groupIdx: number
|
||||
let determined: boolean = false
|
||||
let matchedGroup: string | null | undefined
|
||||
|
@ -235,6 +251,19 @@ export const determineSortingGroup = function (entry: TFile | TFolder, spec: Cus
|
|||
}
|
||||
}
|
||||
break
|
||||
case CustomSortGroupType.StarredOnly:
|
||||
if (ctx?.starredPluginInstance) {
|
||||
let starred: boolean
|
||||
if (aFile) {
|
||||
starred = !!ctx.starredPluginInstance[StarredPlugin_findStarredFile_methodName]({path: entry.path})
|
||||
} else { // aFolder
|
||||
starred = determineStarredStatusOfFolder(entry as TFolder, ctx.starredPluginInstance)
|
||||
}
|
||||
if (starred) {
|
||||
determined = true
|
||||
}
|
||||
}
|
||||
break
|
||||
case CustomSortGroupType.MatchAll:
|
||||
determined = true;
|
||||
break
|
||||
|
@ -360,11 +389,30 @@ export const determineDatesForFolder = (folder: TFolder, now: number): [Modified
|
|||
return [mtimeOfFolder, ctimeNewestOfFolder, ctimeOldestOfFolder]
|
||||
}
|
||||
|
||||
export const determineFolderDatesIfNeeded = (folderItems: Array<FolderItemForSorting>, sortingSpec: CustomSortSpec, sortingGroupsCardinality: {[key: number]: number} = {}) => {
|
||||
export const StarredCorePluginId: string = 'starred'
|
||||
|
||||
export const getStarredPlugin = (app?: App): Starred_PluginInstance | undefined => {
|
||||
const starredPlugin: InstalledPlugin | undefined = app?.internalPlugins?.getPluginById(StarredCorePluginId)
|
||||
if (starredPlugin && starredPlugin.enabled && starredPlugin.instance) {
|
||||
const starredPluginInstance: Starred_PluginInstance = starredPlugin.instance as Starred_PluginInstance
|
||||
// defensive programming, in case Obsidian changes its internal APIs
|
||||
if (typeof starredPluginInstance?.[StarredPlugin_findStarredFile_methodName] === 'function') {
|
||||
return starredPluginInstance
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const determineStarredStatusOfFolder = (folder: TFolder, starredPluginInstance: Starred_PluginInstance): boolean => {
|
||||
return folder.children.some((folderItem) => {
|
||||
return !isFolder(folderItem) && starredPluginInstance[StarredPlugin_findStarredFile_methodName]({path: folderItem.path})
|
||||
})
|
||||
}
|
||||
|
||||
export const determineFolderDatesIfNeeded = (folderItems: Array<FolderItemForSorting>, sortingSpec: CustomSortSpec) => {
|
||||
const Now: number = Date.now()
|
||||
folderItems.forEach((item) => {
|
||||
const groupIdx: number | undefined = item.groupIdx
|
||||
if (groupIdx !== undefined && sortingGroupsCardinality[groupIdx] > 1) {
|
||||
if (groupIdx !== undefined) {
|
||||
const groupOrder: CustomSortOrder | undefined = sortingSpec.groups[groupIdx].order
|
||||
if (sortOrderNeedsFolderDates(groupOrder)) {
|
||||
if (item.folder) {
|
||||
|
@ -377,8 +425,8 @@ export const determineFolderDatesIfNeeded = (folderItems: Array<FolderItemForSor
|
|||
|
||||
export const folderSort = function (sortingSpec: CustomSortSpec, order: string[]) {
|
||||
let fileExplorer = this.fileExplorer
|
||||
const sortingGroupsCardinality: {[key: number]: number} = {}
|
||||
sortingSpec._mCache = sortingSpec.plugin?.app.metadataCache
|
||||
const starredPluginInstance: Starred_PluginInstance | undefined = getStarredPlugin(sortingSpec?.plugin?.app)
|
||||
|
||||
const folderItems: Array<FolderItemForSorting> = (sortingSpec.itemsToHide ?
|
||||
this.file.children.filter((entry: TFile | TFolder) => {
|
||||
|
@ -387,16 +435,14 @@ export const folderSort = function (sortingSpec: CustomSortSpec, order: string[]
|
|||
:
|
||||
this.file.children)
|
||||
.map((entry: TFile | TFolder) => {
|
||||
const itemForSorting: FolderItemForSorting = determineSortingGroup(entry, sortingSpec)
|
||||
const groupIdx: number | undefined = itemForSorting.groupIdx
|
||||
if (groupIdx !== undefined) {
|
||||
sortingGroupsCardinality[groupIdx] = 1 + (sortingGroupsCardinality[groupIdx] ?? 0)
|
||||
}
|
||||
const itemForSorting: FolderItemForSorting = determineSortingGroup(entry, sortingSpec, {
|
||||
starredPluginInstance: starredPluginInstance
|
||||
})
|
||||
return itemForSorting
|
||||
})
|
||||
|
||||
// Finally, for advanced sorting by modified date, for some folders the modified date has to be determined
|
||||
determineFolderDatesIfNeeded(folderItems, sortingSpec, sortingGroupsCardinality)
|
||||
determineFolderDatesIfNeeded(folderItems, sortingSpec)
|
||||
|
||||
folderItems.sort(function (itA: FolderItemForSorting, itB: FolderItemForSorting) {
|
||||
return compareTwoItems(itA, itB, sortingSpec);
|
||||
|
|
|
@ -4,6 +4,7 @@ export const ICON_SORT_ENABLED_ACTIVE: string = 'custom-sort-icon-active'
|
|||
export const ICON_SORT_SUSPENDED: string = 'custom-sort-icon-suspended'
|
||||
export const ICON_SORT_ENABLED_NOT_APPLIED: string = 'custom-sort-icon-enabled-not-applied'
|
||||
export const ICON_SORT_SUSPENDED_SYNTAX_ERROR: string = 'custom-sort-icon-syntax-error'
|
||||
export const ICON_SORT_SUSPENDED_GENERAL_ERROR: string = 'custom-sort-icon-general-error'
|
||||
|
||||
export function addIcons() {
|
||||
addIcon(ICON_SORT_ENABLED_ACTIVE,
|
||||
|
@ -24,6 +25,14 @@ export function addIcons() {
|
|||
<path d="M 11.096126 55.71973 L 20.217128 41.991214 C 21.134003 40.611175 22.99602 40.235707 24.376058 41.15258 C 24.708624 41.373533 24.99374 41.65865 25.21469 41.991214 L 34.33569 55.71973 C 35.252567 57.09977 34.8771 58.96179 33.49706 59.87866 C 33.005085 60.20552 32.42757 60.37988 31.83691 60.37988 L 13.594907 60.37988 C 11.938053 60.37988 10.594907 59.036736 10.594907 57.37988 C 10.594907 56.78922 10.769266 56.21171 11.096126 55.71973 Z" stroke="red" stroke-width="2" fill="none"/>
|
||||
<path d="M 2.5382185 90.37054 L 20.217128 63.76105 C 21.134003 62.38101 22.99602 62.005545 24.376058 62.92242 C 24.708624 63.14337 24.99374 63.428486 25.21469 63.76105 L 42.8936 90.37054 C 43.810475 91.75058 43.435006 93.6126 42.05497 94.52947 C 41.562993 94.85633 40.985477 95.03069 40.39482 95.03069 L 5.0369993 95.03069 C 3.380145 95.03069 2.0369993 93.68755 2.0369993 92.03069 C 2.0369993 91.44004 2.2113584 90.86252 2.5382185 90.37054 Z" stroke="red" stroke-width="2" fill="none"/>
|
||||
<path d="M 88.33569 46.24901 L 79.21469 59.97753 C 78.297815 61.35757 76.4358 61.73304 75.05576 60.81616 C 74.72319 60.59521 74.43808 60.310096 74.21713 59.97753 L 65.09613 46.24901 C 64.17925 44.868973 64.55472 43.006957 65.93476 42.09008 C 66.42673 41.76322 67.00425 41.588863 67.59491 41.588863 L 85.83691 41.588863 C 87.49377 41.588863 88.83691 42.93201 88.83691 44.58886 C 88.83691 45.17952 88.66255 45.757036 88.33569 46.24901 Z" fill="red"/>
|
||||
<path d="M 88.33569 77.48964 L 79.21469 91.21816 C 78.297815 92.5982 76.4358 92.97366 75.05576 92.05679 C 74.72319 91.83584 74.43808 91.55072 74.21713 91.21816 L 65.09613 77.48964 C 64.17925 76.1096 64.55472 74.247585 65.93476 73.33071 C 66.42673 73.00385 67.00425 72.82949 67.59491 72.82949 L 85.83691 72.82949 C 87.49377 72.82949 88.83691 74.17264 88.83691 75.82949 C 88.83691 76.42015 88.66255 76.99766 88.33569 77.48964 Z" fill="red"/>`
|
||||
)
|
||||
addIcon(ICON_SORT_SUSPENDED_GENERAL_ERROR,
|
||||
`<path d="M 93.54751 9.983795 L 79.21469 31.556912 C 78.297815 32.93695 76.4358 33.31242 75.05576 32.395544 C 74.72319 32.174593 74.43808 31.88948 74.21713 31.556912 L 59.8843 9.983795 C 58.96743 8.603756 59.3429 6.74174 60.722935 5.824865 C 61.21491 5.4980047 61.792426 5.3236456 62.383084 5.3236456 L 91.04873 5.3236456 C 92.70559 5.3236456 94.04873 6.666791 94.04873 8.323646 C 94.04873 8.914304 93.87437 9.49182 93.54751 9.983795 Z" fill="red"/>
|
||||
<path d="M 11.096126 32.678017 L 20.217128 18.949499 C 21.134003 17.56946 22.99602 17.193992 24.376058 18.110867 C 24.708624 18.331818 24.99374 18.616933 25.21469 18.949499 L 34.33569 32.678017 C 35.252567 34.058055 34.8771 35.92007 33.49706 36.836947 C 33.005085 37.163807 32.42757 37.338166 31.83691 37.338166 L 13.594907 37.338166 C 11.938053 37.338166 10.594907 35.99502 10.594907 34.338166 C 10.594907 33.747508 10.769266 33.16999 11.096126 32.678017 Z" fill="red"/>
|
||||
<path d="M 11.096126 55.71973 L 20.217128 41.991214 C 21.134003 40.611175 22.99602 40.235707 24.376058 41.15258 C 24.708624 41.373533 24.99374 41.65865 25.21469 41.991214 L 34.33569 55.71973 C 35.252567 57.09977 34.8771 58.96179 33.49706 59.87866 C 33.005085 60.20552 32.42757 60.37988 31.83691 60.37988 L 13.594907 60.37988 C 11.938053 60.37988 10.594907 59.036736 10.594907 57.37988 C 10.594907 56.78922 10.769266 56.21171 11.096126 55.71973 Z" fill="red"/>
|
||||
<path d="M 2.5382185 90.37054 L 20.217128 63.76105 C 21.134003 62.38101 22.99602 62.005545 24.376058 62.92242 C 24.708624 63.14337 24.99374 63.428486 25.21469 63.76105 L 42.8936 90.37054 C 43.810475 91.75058 43.435006 93.6126 42.05497 94.52947 C 41.562993 94.85633 40.985477 95.03069 40.39482 95.03069 L 5.0369993 95.03069 C 3.380145 95.03069 2.0369993 93.68755 2.0369993 92.03069 C 2.0369993 91.44004 2.2113584 90.86252 2.5382185 90.37054 Z" fill="red"/>
|
||||
<path d="M 88.33569 46.24901 L 79.21469 59.97753 C 78.297815 61.35757 76.4358 61.73304 75.05576 60.81616 C 74.72319 60.59521 74.43808 60.310096 74.21713 59.97753 L 65.09613 46.24901 C 64.17925 44.868973 64.55472 43.006957 65.93476 42.09008 C 66.42673 41.76322 67.00425 41.588863 67.59491 41.588863 L 85.83691 41.588863 C 87.49377 41.588863 88.83691 42.93201 88.83691 44.58886 C 88.83691 45.17952 88.66255 45.757036 88.33569 46.24901 Z" fill="red"/>
|
||||
<path d="M 88.33569 77.48964 L 79.21469 91.21816 C 78.297815 92.5982 76.4358 92.97366 75.05576 92.05679 C 74.72319 91.83584 74.43808 91.55072 74.21713 91.21816 L 65.09613 77.48964 C 64.17925 76.1096 64.55472 74.247585 65.93476 73.33071 C 66.42673 73.00385 67.00425 72.82949 67.59491 72.82949 L 85.83691 72.82949 C 87.49377 72.82949 88.83691 74.17264 88.83691 75.82949 C 88.83691 76.42015 88.66255 76.99766 88.33569 77.48964 Z" fill="red"/>`
|
||||
)
|
||||
addIcon(ICON_SORT_ENABLED_NOT_APPLIED,
|
||||
|
|
|
@ -29,6 +29,9 @@ target-folder: tricky folder
|
|||
< a-z by-metadata: Some-dedicated-field
|
||||
with-metadata: Pages
|
||||
> a-z by-metadata:
|
||||
starred:
|
||||
/:files starred:
|
||||
/folders starred:
|
||||
|
||||
:::: Conceptual model
|
||||
/: Entities
|
||||
|
@ -82,6 +85,9 @@ target-folder: tricky folder 2
|
|||
< a-z by-metadata: Some-dedicated-field
|
||||
% with-metadata: Pages
|
||||
> a-z by-metadata:
|
||||
/folders:files starred:
|
||||
/:files starred:
|
||||
/folders starred:
|
||||
|
||||
:::: Conceptual model
|
||||
/:files Entities
|
||||
|
@ -165,11 +171,22 @@ const expectedSortSpecsExampleA: { [key: string]: CustomSortSpec } = {
|
|||
type: CustomSortGroupType.HasMetadataField,
|
||||
withMetadataFieldName: 'Pages',
|
||||
order: CustomSortOrder.byMetadataFieldAlphabeticalReverse
|
||||
}, {
|
||||
type: CustomSortGroupType.StarredOnly,
|
||||
order: CustomSortOrder.alphabetical
|
||||
}, {
|
||||
type: CustomSortGroupType.StarredOnly,
|
||||
filesOnly: true,
|
||||
order: CustomSortOrder.alphabetical
|
||||
}, {
|
||||
type: CustomSortGroupType.StarredOnly,
|
||||
foldersOnly: true,
|
||||
order: CustomSortOrder.alphabetical
|
||||
}, {
|
||||
order: CustomSortOrder.alphabetical,
|
||||
type: CustomSortGroupType.Outsiders
|
||||
}],
|
||||
outsidersGroupIdx: 2,
|
||||
outsidersGroupIdx: 5,
|
||||
targetFoldersPaths: [
|
||||
'tricky folder 2'
|
||||
]
|
||||
|
|
|
@ -188,12 +188,15 @@ const FilesWithExtGroupShortLexeme: string = '/:.'
|
|||
const FoldersGroupVerboseLexeme: string = '/folders'
|
||||
const FoldersGroupShortLexeme: string = '/'
|
||||
const AnyTypeGroupLexemeShort: string = '%' // See % as a combination of / and :
|
||||
const AnyTypeGroupLexeme: string = '/%' // See % as a combination of / and :
|
||||
const AnyTypeGroupLexeme1: string = '/folders:files'
|
||||
const AnyTypeGroupLexeme2: string = '/%' // See % as a combination of / and :
|
||||
const HideItemShortLexeme: string = '--%' // See % as a combination of / and :
|
||||
const HideItemVerboseLexeme: string = '/--hide:'
|
||||
|
||||
const MetadataFieldIndicatorLexeme: string = 'with-metadata:'
|
||||
|
||||
const StarredItemsIndicatorLexeme: string = 'starred:'
|
||||
|
||||
const CommentPrefix: string = '//'
|
||||
|
||||
const PriorityModifierPrio1Lexeme: string = '/!'
|
||||
|
@ -232,7 +235,8 @@ const SortingGroupPrefixes: { [key: string]: SortingGroupType } = {
|
|||
[FoldersGroupShortLexeme]: {foldersOnly: true},
|
||||
[FoldersGroupVerboseLexeme]: {foldersOnly: true},
|
||||
[AnyTypeGroupLexemeShort]: {},
|
||||
[AnyTypeGroupLexeme]: {},
|
||||
[AnyTypeGroupLexeme1]: {},
|
||||
[AnyTypeGroupLexeme2]: {},
|
||||
[HideItemShortLexeme]: {itemToHide: true},
|
||||
[HideItemVerboseLexeme]: {itemToHide: true}
|
||||
}
|
||||
|
@ -1337,6 +1341,13 @@ export class SortingSpecProcessor {
|
|||
foldersOnly: spec.foldersOnly,
|
||||
matchFilenameWithExt: spec.matchFilenameWithExt
|
||||
}
|
||||
} else if (theOnly.startsWith(StarredItemsIndicatorLexeme)) {
|
||||
return {
|
||||
type: CustomSortGroupType.StarredOnly,
|
||||
filesOnly: spec.filesOnly,
|
||||
foldersOnly: spec.foldersOnly,
|
||||
matchFilenameWithExt: spec.matchFilenameWithExt
|
||||
}
|
||||
} else {
|
||||
// For non-three dots single text line assume exact match group
|
||||
return {
|
||||
|
|
62
src/main.ts
62
src/main.ts
|
@ -23,6 +23,7 @@ import {
|
|||
ICON_SORT_ENABLED_ACTIVE,
|
||||
ICON_SORT_ENABLED_NOT_APPLIED,
|
||||
ICON_SORT_SUSPENDED,
|
||||
ICON_SORT_SUSPENDED_GENERAL_ERROR,
|
||||
ICON_SORT_SUSPENDED_SYNTAX_ERROR
|
||||
} from "./custom-sort/icons";
|
||||
|
||||
|
@ -124,8 +125,39 @@ export default class CustomSortPlugin extends Plugin {
|
|||
}
|
||||
}
|
||||
|
||||
checkFileExplorerIsAvailableAndPatchable(logWarning: boolean = true): FileExplorerView | undefined {
|
||||
let fileExplorerView: FileExplorerView | undefined = this.getFileExplorer()
|
||||
if (fileExplorerView
|
||||
&& typeof fileExplorerView.createFolderDom === 'function'
|
||||
&& typeof fileExplorerView.requestSort === 'function') {
|
||||
return fileExplorerView
|
||||
} else {
|
||||
// Various scenarios when File Explorer was turned off (e.g. by some other plugin)
|
||||
if (logWarning) {
|
||||
this.logWarningFileExplorerNotAvailable()
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
logWarningFileExplorerNotAvailable() {
|
||||
const msg = `custom-sort v${this.manifest.version}: failed to locate File Explorer. The 'Files' core plugin can be disabled.\n`
|
||||
+ `Some community plugins can also disable it.\n`
|
||||
+ `See the example of MAKE.md plugin: https://github.com/Make-md/makemd/issues/25\n`
|
||||
+ `You can find there instructions on how to re-enable the File Explorer in MAKE.md plugin`
|
||||
console.warn(msg)
|
||||
}
|
||||
|
||||
// Safe to suspend when suspended and re-enable when enabled
|
||||
switchPluginStateTo(enabled: boolean, updateRibbonBtnIcon: boolean = true) {
|
||||
let fileExplorerView: FileExplorerView | undefined = this.checkFileExplorerIsAvailableAndPatchable()
|
||||
if (fileExplorerView && !this.fileExplorerFolderPatched) {
|
||||
this.fileExplorerFolderPatched = this.patchFileExplorerFolder(fileExplorerView);
|
||||
|
||||
if (!this.fileExplorerFolderPatched) {
|
||||
fileExplorerView = undefined
|
||||
}
|
||||
}
|
||||
this.settings.suspended = !enabled;
|
||||
this.saveSettings()
|
||||
let iconToSet: string
|
||||
|
@ -136,20 +168,24 @@ export default class CustomSortPlugin extends Plugin {
|
|||
} else {
|
||||
this.readAndParseSortingSpec();
|
||||
if (this.sortSpecCache) {
|
||||
this.showNotice('Custom sort ON');
|
||||
this.initialAutoOrManualSortingTriggered = true
|
||||
iconToSet = ICON_SORT_ENABLED_ACTIVE
|
||||
if (fileExplorerView) {
|
||||
this.showNotice('Custom sort ON');
|
||||
this.initialAutoOrManualSortingTriggered = true
|
||||
iconToSet = ICON_SORT_ENABLED_ACTIVE
|
||||
} else {
|
||||
this.showNotice('Custom sort GENERAL PROBLEM. See console for detailed message.');
|
||||
iconToSet = ICON_SORT_SUSPENDED_GENERAL_ERROR
|
||||
this.settings.suspended = true
|
||||
this.saveSettings()
|
||||
}
|
||||
} else {
|
||||
iconToSet = ICON_SORT_SUSPENDED_SYNTAX_ERROR
|
||||
this.settings.suspended = true
|
||||
this.saveSettings()
|
||||
}
|
||||
}
|
||||
const fileExplorerView: FileExplorerView | undefined = this.getFileExplorer()
|
||||
|
||||
if (fileExplorerView) {
|
||||
if (!this.fileExplorerFolderPatched) {
|
||||
this.fileExplorerFolderPatched = this.patchFileExplorerFolder(fileExplorerView);
|
||||
}
|
||||
if (this.fileExplorerFolderPatched) {
|
||||
fileExplorerView.requestSort();
|
||||
}
|
||||
|
@ -215,7 +251,7 @@ export default class CustomSortPlugin extends Plugin {
|
|||
this.initialAutoOrManualSortingTriggered = true
|
||||
if (this.sortSpecCache) { // successful read of sorting specifications?
|
||||
this.showNotice('Custom sort ON')
|
||||
const fileExplorerView: FileExplorerView | undefined = this.getFileExplorer()
|
||||
const fileExplorerView: FileExplorerView | undefined = this.checkFileExplorerIsAvailableAndPatchable(false)
|
||||
if (fileExplorerView) {
|
||||
setIcon(this.ribbonIconEl, ICON_SORT_ENABLED_ACTIVE)
|
||||
fileExplorerView.requestSort()
|
||||
|
@ -260,13 +296,15 @@ export default class CustomSortPlugin extends Plugin {
|
|||
}
|
||||
|
||||
// For the idea of monkey-patching credits go to https://github.com/nothingislost/obsidian-bartender
|
||||
patchFileExplorerFolder(fileExplorer?: FileExplorerView): boolean {
|
||||
patchFileExplorerFolder(patchableFileExplorer?: FileExplorerView): boolean {
|
||||
let plugin = this;
|
||||
fileExplorer = fileExplorer ?? this.getFileExplorer()
|
||||
if (fileExplorer) {
|
||||
// patching file explorer might fail here because of various non-error reasons.
|
||||
// That's why not showing and not logging error message here
|
||||
patchableFileExplorer = patchableFileExplorer ?? this.checkFileExplorerIsAvailableAndPatchable(false)
|
||||
if (patchableFileExplorer) {
|
||||
// @ts-ignore
|
||||
let tmpFolder = new TFolder(Vault, "");
|
||||
let Folder = fileExplorer.createFolderDom(tmpFolder).constructor;
|
||||
let Folder = patchableFileExplorer.createFolderDom(tmpFolder).constructor;
|
||||
const uninstallerOfFolderSortFunctionWrapper: MonkeyAroundUninstaller = around(Folder.prototype, {
|
||||
sort(old: any) {
|
||||
return function (...args: any[]) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {TFolder, WorkspaceLeaf} from "obsidian";
|
||||
import {PluginInstance, TFolder, WorkspaceLeaf} from "obsidian";
|
||||
|
||||
// Needed to support monkey-patching of the folder sort() function
|
||||
|
||||
|
@ -7,10 +7,28 @@ declare module 'obsidian' {
|
|||
viewByType: Record<string, (leaf: WorkspaceLeaf) => unknown>;
|
||||
}
|
||||
|
||||
// undocumented internal interface - for experimental features
|
||||
export interface PluginInstance {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface App {
|
||||
internalPlugins: InternalPlugins; // undocumented internal API - for experimental features
|
||||
viewRegistry: ViewRegistry;
|
||||
}
|
||||
|
||||
// undocumented internal interface - for experimental features
|
||||
export interface InstalledPlugin {
|
||||
enabled: boolean;
|
||||
instance: PluginInstance;
|
||||
}
|
||||
|
||||
// undocumented internal interface - for experimental features
|
||||
export interface InternalPlugins {
|
||||
plugins: Record<string, InstalledPlugin>;
|
||||
getPluginById(id: string): InstalledPlugin;
|
||||
}
|
||||
|
||||
interface FileExplorerFolder {
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
import {PluginInstance, TFile} from "obsidian";
|
||||
|
||||
export const StarredPlugin_findStarredFile_methodName = 'findStarredFile'
|
||||
|
||||
export interface findStarredFile_pathParam {
|
||||
path: string
|
||||
}
|
||||
|
||||
export interface Starred_PluginInstance extends PluginInstance {
|
||||
[StarredPlugin_findStarredFile_methodName]: (filePath: findStarredFile_pathParam) => TFile | null
|
||||
}
|
Loading…
Reference in New Issue