Merge pull request #64 from SebastianMC/60-simplified-integration-with-obsidian-icon-folder
60 simplified integration with obsidian icon folder
This commit is contained in:
commit
63698cb2ed
|
@ -8,7 +8,8 @@ export enum CustomSortGroupType {
|
||||||
ExactSuffix,
|
ExactSuffix,
|
||||||
ExactHeadAndTail, // Like W...n or Un...ed, which is shorter variant of typing the entire title
|
ExactHeadAndTail, // Like W...n or Un...ed, which is shorter variant of typing the entire title
|
||||||
HasMetadataField, // Notes (or folder's notes) containing a specific metadata field
|
HasMetadataField, // Notes (or folder's notes) containing a specific metadata field
|
||||||
StarredOnly
|
StarredOnly,
|
||||||
|
HasIcon
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum CustomSortOrder {
|
export enum CustomSortOrder {
|
||||||
|
@ -59,6 +60,7 @@ export interface CustomSortGroup {
|
||||||
matchFilenameWithExt?: boolean
|
matchFilenameWithExt?: boolean
|
||||||
foldersOnly?: boolean
|
foldersOnly?: boolean
|
||||||
withMetadataFieldName?: string // for 'with-metadata:' grouping
|
withMetadataFieldName?: string // for 'with-metadata:' grouping
|
||||||
|
iconName?: string // for integration with obsidian-folder-icon community plugin
|
||||||
priority?: number
|
priority?: number
|
||||||
combineWithIdx?: number
|
combineWithIdx?: number
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,10 @@ import {
|
||||||
import {CustomSortGroupType, CustomSortOrder, CustomSortSpec, RegExpSpec} from './custom-sort-types';
|
import {CustomSortGroupType, CustomSortOrder, CustomSortSpec, RegExpSpec} from './custom-sort-types';
|
||||||
import {CompoundDashNumberNormalizerFn, CompoundDotRomanNumberNormalizerFn} from "./sorting-spec-processor";
|
import {CompoundDashNumberNormalizerFn, CompoundDotRomanNumberNormalizerFn} from "./sorting-spec-processor";
|
||||||
import {findStarredFile_pathParam, Starred_PluginInstance} from "../utils/StarredPluginSignature";
|
import {findStarredFile_pathParam, Starred_PluginInstance} from "../utils/StarredPluginSignature";
|
||||||
|
import {
|
||||||
|
ObsidianIconFolder_PluginInstance,
|
||||||
|
ObsidianIconFolderPlugin_Data
|
||||||
|
} from "../utils/ObsidianIconFolderPluginSignature";
|
||||||
|
|
||||||
const mockTFile = (basename: string, ext: string, size?: number, ctime?: number, mtime?: number): TFile => {
|
const mockTFile = (basename: string, ext: string, size?: number, ctime?: number, mtime?: number): TFile => {
|
||||||
return {
|
return {
|
||||||
|
@ -1067,6 +1071,458 @@ describe('determineSortingGroup', () => {
|
||||||
expect(starredPluginInstance.findStarredFile).toHaveBeenCalledTimes(2)
|
expect(starredPluginInstance.findStarredFile).toHaveBeenCalledTimes(2)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
describe('CustomSortGroupType.HasIcon', () => {
|
||||||
|
it('should not match file w/o icon', () => {
|
||||||
|
// given
|
||||||
|
const file: TFile = mockTFile('References', 'md', 111, MOCK_TIMESTAMP + 222, MOCK_TIMESTAMP + 333);
|
||||||
|
const sortSpec: CustomSortSpec = {
|
||||||
|
targetFoldersPaths: ['/'],
|
||||||
|
groups: [{
|
||||||
|
type: CustomSortGroupType.HasIcon
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
const obsidianIconFolderPluginInstance: Partial<ObsidianIconFolder_PluginInstance> = {
|
||||||
|
getData: jest.fn( function(): ObsidianIconFolderPlugin_Data {
|
||||||
|
return {settings: {}} // The obsidian-folder-icon plugin keeps the settings there indeed ;-)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// when
|
||||||
|
const result = determineSortingGroup(file, sortSpec, {
|
||||||
|
iconFolderPluginInstance: obsidianIconFolderPluginInstance as ObsidianIconFolder_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(obsidianIconFolderPluginInstance.getData).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
it('should not match file with icon of different name', () => {
|
||||||
|
// given
|
||||||
|
const file: TFile = mockTFile('References', 'md', 111, MOCK_TIMESTAMP + 222, MOCK_TIMESTAMP + 333);
|
||||||
|
const sortSpec: CustomSortSpec = {
|
||||||
|
targetFoldersPaths: ['/'],
|
||||||
|
groups: [{
|
||||||
|
type: CustomSortGroupType.HasIcon,
|
||||||
|
iconName: 'IncorrectIconName'
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
const obsidianIconFolderPluginInstance: Partial<ObsidianIconFolder_PluginInstance> = {
|
||||||
|
getData: jest.fn( function(): ObsidianIconFolderPlugin_Data {
|
||||||
|
return {
|
||||||
|
settings: {}, // The obsidian-folder-icon plugin keeps the settings there indeed ;-)
|
||||||
|
'Some parent folder/References.md': 'CorrectIconName'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// when
|
||||||
|
const result = determineSortingGroup(file, sortSpec, {
|
||||||
|
iconFolderPluginInstance: obsidianIconFolderPluginInstance as ObsidianIconFolder_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(obsidianIconFolderPluginInstance.getData).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
it('should match file with any icon', () => {
|
||||||
|
// given
|
||||||
|
const file: TFile = mockTFile('References', 'md', 111, MOCK_TIMESTAMP + 222, MOCK_TIMESTAMP + 333);
|
||||||
|
const sortSpec: CustomSortSpec = {
|
||||||
|
targetFoldersPaths: ['/'],
|
||||||
|
groups: [{
|
||||||
|
type: CustomSortGroupType.HasIcon
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
const obsidianIconFolderPluginInstance: Partial<ObsidianIconFolder_PluginInstance> = {
|
||||||
|
getData: jest.fn( function(): ObsidianIconFolderPlugin_Data {
|
||||||
|
return {
|
||||||
|
settings: {}, // The obsidian-folder-icon plugin keeps the settings there indeed ;-)
|
||||||
|
'Some parent folder/References.md': 'Irrelevant icon name, only presence matters'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// when
|
||||||
|
const result = determineSortingGroup(file, sortSpec, {
|
||||||
|
iconFolderPluginInstance: obsidianIconFolderPluginInstance as ObsidianIconFolder_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(obsidianIconFolderPluginInstance.getData).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
it('should match file with icon of expected name', () => {
|
||||||
|
// given
|
||||||
|
const file: TFile = mockTFile('References', 'md', 111, MOCK_TIMESTAMP + 222, MOCK_TIMESTAMP + 333);
|
||||||
|
const sortSpec: CustomSortSpec = {
|
||||||
|
targetFoldersPaths: ['/'],
|
||||||
|
groups: [{
|
||||||
|
type: CustomSortGroupType.HasIcon,
|
||||||
|
iconName: 'CorrectIconName'
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
const obsidianIconFolderPluginInstance: Partial<ObsidianIconFolder_PluginInstance> = {
|
||||||
|
getData: jest.fn( function(): ObsidianIconFolderPlugin_Data {
|
||||||
|
return {
|
||||||
|
settings: {}, // The obsidian-folder-icon plugin keeps the settings there indeed ;-)
|
||||||
|
'Some parent folder/References.md': 'CorrectIconName'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// when
|
||||||
|
const result = determineSortingGroup(file, sortSpec, {
|
||||||
|
iconFolderPluginInstance: obsidianIconFolderPluginInstance as ObsidianIconFolder_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(obsidianIconFolderPluginInstance.getData).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
it('should not match folder w/o icon', () => {
|
||||||
|
// given
|
||||||
|
const folder: TFolder = mockTFolder('TestEmptyFolder');
|
||||||
|
const sortSpec: CustomSortSpec = {
|
||||||
|
targetFoldersPaths: ['/'],
|
||||||
|
groups: [{
|
||||||
|
type: CustomSortGroupType.HasIcon
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
const obsidianIconFolderPluginInstance: Partial<ObsidianIconFolder_PluginInstance> = {
|
||||||
|
getData: jest.fn( function(): ObsidianIconFolderPlugin_Data {
|
||||||
|
return {settings: {}} // The obsidian-folder-icon plugin keeps the settings there indeed ;-)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// when
|
||||||
|
const result = determineSortingGroup(folder, sortSpec, {
|
||||||
|
iconFolderPluginInstance: obsidianIconFolderPluginInstance as ObsidianIconFolder_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(obsidianIconFolderPluginInstance.getData).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
it('should match folder with any icon (icon specified by string alone)', () => {
|
||||||
|
// given
|
||||||
|
const folder: TFolder = mockTFolderWithChildren('TestEmptyFolder');
|
||||||
|
const sortSpec: CustomSortSpec = {
|
||||||
|
targetFoldersPaths: ['/'],
|
||||||
|
groups: [{
|
||||||
|
type: CustomSortGroupType.HasIcon
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
const obsidianIconFolderPluginInstance: Partial<ObsidianIconFolder_PluginInstance> = {
|
||||||
|
getData: jest.fn( function(): ObsidianIconFolderPlugin_Data {
|
||||||
|
return {
|
||||||
|
settings: {}, // The obsidian-folder-icon plugin keeps the settings there indeed ;-)
|
||||||
|
'TestEmptyFolder': 'Irrelevant icon name, only presence matters'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// when
|
||||||
|
const result = determineSortingGroup(folder, sortSpec, {
|
||||||
|
iconFolderPluginInstance: obsidianIconFolderPluginInstance as ObsidianIconFolder_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: {}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
expect(obsidianIconFolderPluginInstance.getData).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
it('should match folder with any icon (icon specified together with inheritance)', () => {
|
||||||
|
// given
|
||||||
|
const folder: TFolder = mockTFolderWithChildren('TestEmptyFolder');
|
||||||
|
const sortSpec: CustomSortSpec = {
|
||||||
|
targetFoldersPaths: ['/'],
|
||||||
|
groups: [{
|
||||||
|
type: CustomSortGroupType.HasIcon
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
const obsidianIconFolderPluginInstance: Partial<ObsidianIconFolder_PluginInstance> = {
|
||||||
|
getData: jest.fn( function(): ObsidianIconFolderPlugin_Data {
|
||||||
|
return {
|
||||||
|
settings: {}, // The obsidian-folder-icon plugin keeps the settings there indeed ;-)
|
||||||
|
'TestEmptyFolder': {
|
||||||
|
iconName: 'ConfiguredIcon',
|
||||||
|
inheritanceIcon: 'ConfiguredInheritanceIcon'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// when
|
||||||
|
const result = determineSortingGroup(folder, sortSpec, {
|
||||||
|
iconFolderPluginInstance: obsidianIconFolderPluginInstance as ObsidianIconFolder_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: {}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
expect(obsidianIconFolderPluginInstance.getData).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
it('should match folder with specified icon (icon specified by string alone)', () => {
|
||||||
|
// given
|
||||||
|
const folder: TFolder = mockTFolderWithChildren('TestEmptyFolder');
|
||||||
|
const sortSpec: CustomSortSpec = {
|
||||||
|
targetFoldersPaths: ['/'],
|
||||||
|
groups: [{
|
||||||
|
type: CustomSortGroupType.HasIcon,
|
||||||
|
iconName: 'ConfiguredIcon-by-string'
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
const obsidianIconFolderPluginInstance: Partial<ObsidianIconFolder_PluginInstance> = {
|
||||||
|
getData: jest.fn( function(): ObsidianIconFolderPlugin_Data {
|
||||||
|
return {
|
||||||
|
settings: {}, // The obsidian-folder-icon plugin keeps the settings there indeed ;-)
|
||||||
|
'TestEmptyFolder': 'ConfiguredIcon-by-string'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// when
|
||||||
|
const result = determineSortingGroup(folder, sortSpec, {
|
||||||
|
iconFolderPluginInstance: obsidianIconFolderPluginInstance as ObsidianIconFolder_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: {}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
expect(obsidianIconFolderPluginInstance.getData).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
it('should match folder with specified icon (icon specified together with inheritance)', () => {
|
||||||
|
// given
|
||||||
|
const folder: TFolder = mockTFolderWithChildren('TestEmptyFolder');
|
||||||
|
const sortSpec: CustomSortSpec = {
|
||||||
|
targetFoldersPaths: ['/'],
|
||||||
|
groups: [{
|
||||||
|
type: CustomSortGroupType.HasIcon,
|
||||||
|
iconName: 'ConfiguredIcon'
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
const obsidianIconFolderPluginInstance: Partial<ObsidianIconFolder_PluginInstance> = {
|
||||||
|
getData: jest.fn( function(): ObsidianIconFolderPlugin_Data {
|
||||||
|
return {
|
||||||
|
settings: {}, // The obsidian-folder-icon plugin keeps the settings there indeed ;-)
|
||||||
|
'TestEmptyFolder': {
|
||||||
|
iconName: 'ConfiguredIcon',
|
||||||
|
inheritanceIcon: 'ConfiguredInheritanceIcon'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// when
|
||||||
|
const result = determineSortingGroup(folder, sortSpec, {
|
||||||
|
iconFolderPluginInstance: obsidianIconFolderPluginInstance as ObsidianIconFolder_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: {}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
expect(obsidianIconFolderPluginInstance.getData).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
it('should not match folder with different icon (icon specified by string alone)', () => {
|
||||||
|
// given
|
||||||
|
const folder: TFolder = mockTFolderWithChildren('TestEmptyFolder');
|
||||||
|
const sortSpec: CustomSortSpec = {
|
||||||
|
targetFoldersPaths: ['/'],
|
||||||
|
groups: [{
|
||||||
|
type: CustomSortGroupType.HasIcon,
|
||||||
|
iconName: 'ConfiguredIcon-by-string'
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
const obsidianIconFolderPluginInstance: Partial<ObsidianIconFolder_PluginInstance> = {
|
||||||
|
getData: jest.fn( function(): ObsidianIconFolderPlugin_Data {
|
||||||
|
return {
|
||||||
|
settings: {}, // The obsidian-folder-icon plugin keeps the settings there indeed ;-)
|
||||||
|
'TestEmptyFolder': 'AnotherConfiguredIcon-by-string'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// when
|
||||||
|
const result = determineSortingGroup(folder, sortSpec, {
|
||||||
|
iconFolderPluginInstance: obsidianIconFolderPluginInstance as ObsidianIconFolder_PluginInstance
|
||||||
|
})
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(result).toEqual({
|
||||||
|
groupIdx: 1, // lastIdx+1 - no match
|
||||||
|
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(obsidianIconFolderPluginInstance.getData).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
it('should not match folder with different icon (icon specified together with inheritance)', () => {
|
||||||
|
// given
|
||||||
|
const folder: TFolder = mockTFolderWithChildren('TestEmptyFolder');
|
||||||
|
const sortSpec: CustomSortSpec = {
|
||||||
|
targetFoldersPaths: ['/'],
|
||||||
|
groups: [{
|
||||||
|
type: CustomSortGroupType.HasIcon,
|
||||||
|
iconName: 'ConfiguredIcon'
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
const obsidianIconFolderPluginInstance: Partial<ObsidianIconFolder_PluginInstance> = {
|
||||||
|
getData: jest.fn( function(): ObsidianIconFolderPlugin_Data {
|
||||||
|
return {
|
||||||
|
settings: {}, // The obsidian-folder-icon plugin keeps the settings there indeed ;-)
|
||||||
|
'TestEmptyFolder': {
|
||||||
|
iconName: 'OtherConfiguredIcon',
|
||||||
|
inheritanceIcon: 'ConfiguredInheritanceIcon'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// when
|
||||||
|
const result = determineSortingGroup(folder, sortSpec, {
|
||||||
|
iconFolderPluginInstance: obsidianIconFolderPluginInstance as ObsidianIconFolder_PluginInstance
|
||||||
|
})
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(result).toEqual({
|
||||||
|
groupIdx: 1, // lastIdx+1 - no match
|
||||||
|
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(obsidianIconFolderPluginInstance.getData).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
})
|
||||||
describe('when sort by metadata is involved', () => {
|
describe('when sort by metadata is involved', () => {
|
||||||
it('should correctly read direct metadata from File item (order by metadata set on group) alph', () => {
|
it('should correctly read direct metadata from File item (order by metadata set on group) alph', () => {
|
||||||
// given
|
// given
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import {
|
import {
|
||||||
App,
|
App,
|
||||||
|
CommunityPlugin,
|
||||||
FrontMatterCache,
|
FrontMatterCache,
|
||||||
InstalledPlugin,
|
InstalledPlugin,
|
||||||
requireApiVersion,
|
requireApiVersion,
|
||||||
|
@ -8,9 +9,19 @@ import {
|
||||||
TFolder
|
TFolder
|
||||||
} from 'obsidian';
|
} from 'obsidian';
|
||||||
import {
|
import {
|
||||||
|
determineStarredStatusOf,
|
||||||
|
getStarredPlugin,
|
||||||
Starred_PluginInstance,
|
Starred_PluginInstance,
|
||||||
StarredPlugin_findStarredFile_methodName
|
StarredPlugin_findStarredFile_methodName
|
||||||
} from '../utils/StarredPluginSignature'
|
} from '../utils/StarredPluginSignature';
|
||||||
|
import {
|
||||||
|
determineIconOf,
|
||||||
|
getIconFolderPlugin,
|
||||||
|
FolderIconObject,
|
||||||
|
ObsidianIconFolder_PluginInstance,
|
||||||
|
ObsidianIconFolderPlugin_Data,
|
||||||
|
ObsidianIconFolderPlugin_getData_methodName
|
||||||
|
} from '../utils/ObsidianIconFolderPluginSignature'
|
||||||
import {
|
import {
|
||||||
CustomSortGroup,
|
CustomSortGroup,
|
||||||
CustomSortGroupType,
|
CustomSortGroupType,
|
||||||
|
@ -154,6 +165,7 @@ export const matchGroupRegex = (theRegex: RegExpSpec, nameForMatching: string):
|
||||||
|
|
||||||
export interface Context {
|
export interface Context {
|
||||||
starredPluginInstance?: Starred_PluginInstance
|
starredPluginInstance?: Starred_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?: Context): FolderItemForSorting {
|
||||||
|
@ -253,17 +265,24 @@ export const determineSortingGroup = function (entry: TFile | TFolder, spec: Cus
|
||||||
break
|
break
|
||||||
case CustomSortGroupType.StarredOnly:
|
case CustomSortGroupType.StarredOnly:
|
||||||
if (ctx?.starredPluginInstance) {
|
if (ctx?.starredPluginInstance) {
|
||||||
let starred: boolean
|
let starred: boolean = determineStarredStatusOf(entry, aFile, ctx.starredPluginInstance)
|
||||||
if (aFile) {
|
|
||||||
starred = !!ctx.starredPluginInstance[StarredPlugin_findStarredFile_methodName]({path: entry.path})
|
|
||||||
} else { // aFolder
|
|
||||||
starred = determineStarredStatusOfFolder(entry as TFolder, ctx.starredPluginInstance)
|
|
||||||
}
|
|
||||||
if (starred) {
|
if (starred) {
|
||||||
determined = true
|
determined = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
case CustomSortGroupType.HasIcon:
|
||||||
|
if(ctx?.iconFolderPluginInstance) {
|
||||||
|
let iconName: string | undefined = determineIconOf(entry, ctx.iconFolderPluginInstance)
|
||||||
|
if (iconName) {
|
||||||
|
if (group.iconName) {
|
||||||
|
determined = iconName === group.iconName
|
||||||
|
} else {
|
||||||
|
determined = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
case CustomSortGroupType.MatchAll:
|
case CustomSortGroupType.MatchAll:
|
||||||
determined = true;
|
determined = true;
|
||||||
break
|
break
|
||||||
|
@ -389,25 +408,6 @@ export const determineDatesForFolder = (folder: TFolder, now: number): [Modified
|
||||||
return [mtimeOfFolder, ctimeNewestOfFolder, ctimeOldestOfFolder]
|
return [mtimeOfFolder, ctimeNewestOfFolder, ctimeOldestOfFolder]
|
||||||
}
|
}
|
||||||
|
|
||||||
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) => {
|
export const determineFolderDatesIfNeeded = (folderItems: Array<FolderItemForSorting>, sortingSpec: CustomSortSpec) => {
|
||||||
const Now: number = Date.now()
|
const Now: number = Date.now()
|
||||||
folderItems.forEach((item) => {
|
folderItems.forEach((item) => {
|
||||||
|
@ -427,6 +427,7 @@ export const folderSort = function (sortingSpec: CustomSortSpec, order: string[]
|
||||||
let fileExplorer = this.fileExplorer
|
let fileExplorer = this.fileExplorer
|
||||||
sortingSpec._mCache = sortingSpec.plugin?.app.metadataCache
|
sortingSpec._mCache = sortingSpec.plugin?.app.metadataCache
|
||||||
const starredPluginInstance: Starred_PluginInstance | undefined = getStarredPlugin(sortingSpec?.plugin?.app)
|
const starredPluginInstance: Starred_PluginInstance | undefined = getStarredPlugin(sortingSpec?.plugin?.app)
|
||||||
|
const iconFolderPluginInstance: ObsidianIconFolder_PluginInstance | undefined = getIconFolderPlugin(sortingSpec?.plugin?.app)
|
||||||
|
|
||||||
const folderItems: Array<FolderItemForSorting> = (sortingSpec.itemsToHide ?
|
const folderItems: Array<FolderItemForSorting> = (sortingSpec.itemsToHide ?
|
||||||
this.file.children.filter((entry: TFile | TFolder) => {
|
this.file.children.filter((entry: TFile | TFolder) => {
|
||||||
|
@ -436,7 +437,8 @@ export const folderSort = function (sortingSpec: CustomSortSpec, order: string[]
|
||||||
this.file.children)
|
this.file.children)
|
||||||
.map((entry: TFile | TFolder) => {
|
.map((entry: TFile | TFolder) => {
|
||||||
const itemForSorting: FolderItemForSorting = determineSortingGroup(entry, sortingSpec, {
|
const itemForSorting: FolderItemForSorting = determineSortingGroup(entry, sortingSpec, {
|
||||||
starredPluginInstance: starredPluginInstance
|
starredPluginInstance: starredPluginInstance,
|
||||||
|
iconFolderPluginInstance: iconFolderPluginInstance
|
||||||
})
|
})
|
||||||
return itemForSorting
|
return itemForSorting
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import {
|
import {
|
||||||
CompoundDashNumberNormalizerFn,
|
CompoundDashNumberNormalizerFn,
|
||||||
CompoundDashRomanNumberNormalizerFn,
|
CompoundDashRomanNumberNormalizerFn,
|
||||||
CompoundDotNumberNormalizerFn, ConsumedFolderMatchingRegexp, consumeFolderByRegexpExpression,
|
CompoundDotNumberNormalizerFn,
|
||||||
|
ConsumedFolderMatchingRegexp,
|
||||||
|
consumeFolderByRegexpExpression,
|
||||||
convertPlainStringToRegex,
|
convertPlainStringToRegex,
|
||||||
detectNumericSortingSymbols,
|
detectNumericSortingSymbols,
|
||||||
escapeRegexUnsafeCharacters,
|
escapeRegexUnsafeCharacters,
|
||||||
|
@ -29,6 +31,8 @@ target-folder: tricky folder
|
||||||
< a-z by-metadata: Some-dedicated-field
|
< a-z by-metadata: Some-dedicated-field
|
||||||
with-metadata: Pages
|
with-metadata: Pages
|
||||||
> a-z by-metadata:
|
> a-z by-metadata:
|
||||||
|
/: with-icon:
|
||||||
|
with-icon: RiClock24
|
||||||
starred:
|
starred:
|
||||||
/:files starred:
|
/:files starred:
|
||||||
/folders starred:
|
/folders starred:
|
||||||
|
@ -85,6 +89,8 @@ target-folder: tricky folder 2
|
||||||
< a-z by-metadata: Some-dedicated-field
|
< a-z by-metadata: Some-dedicated-field
|
||||||
% with-metadata: Pages
|
% with-metadata: Pages
|
||||||
> a-z by-metadata:
|
> a-z by-metadata:
|
||||||
|
/:files with-icon:
|
||||||
|
/folders:files with-icon: RiClock24
|
||||||
/folders:files starred:
|
/folders:files starred:
|
||||||
/:files starred:
|
/:files starred:
|
||||||
/folders starred:
|
/folders starred:
|
||||||
|
@ -171,6 +177,14 @@ const expectedSortSpecsExampleA: { [key: string]: CustomSortSpec } = {
|
||||||
type: CustomSortGroupType.HasMetadataField,
|
type: CustomSortGroupType.HasMetadataField,
|
||||||
withMetadataFieldName: 'Pages',
|
withMetadataFieldName: 'Pages',
|
||||||
order: CustomSortOrder.byMetadataFieldAlphabeticalReverse
|
order: CustomSortOrder.byMetadataFieldAlphabeticalReverse
|
||||||
|
}, {
|
||||||
|
type: CustomSortGroupType.HasIcon,
|
||||||
|
order: CustomSortOrder.alphabetical,
|
||||||
|
filesOnly: true
|
||||||
|
}, {
|
||||||
|
type: CustomSortGroupType.HasIcon,
|
||||||
|
order: CustomSortOrder.alphabetical,
|
||||||
|
iconName: 'RiClock24'
|
||||||
}, {
|
}, {
|
||||||
type: CustomSortGroupType.StarredOnly,
|
type: CustomSortGroupType.StarredOnly,
|
||||||
order: CustomSortOrder.alphabetical
|
order: CustomSortOrder.alphabetical
|
||||||
|
@ -186,7 +200,7 @@ const expectedSortSpecsExampleA: { [key: string]: CustomSortSpec } = {
|
||||||
order: CustomSortOrder.alphabetical,
|
order: CustomSortOrder.alphabetical,
|
||||||
type: CustomSortGroupType.Outsiders
|
type: CustomSortGroupType.Outsiders
|
||||||
}],
|
}],
|
||||||
outsidersGroupIdx: 5,
|
outsidersGroupIdx: 7,
|
||||||
targetFoldersPaths: [
|
targetFoldersPaths: [
|
||||||
'tricky folder 2'
|
'tricky folder 2'
|
||||||
]
|
]
|
||||||
|
|
|
@ -207,6 +207,8 @@ const MetadataFieldIndicatorLexeme: string = 'with-metadata:'
|
||||||
|
|
||||||
const StarredItemsIndicatorLexeme: string = 'starred:'
|
const StarredItemsIndicatorLexeme: string = 'starred:'
|
||||||
|
|
||||||
|
const IconIndicatorLexeme: string = 'with-icon:'
|
||||||
|
|
||||||
const CommentPrefix: string = '//'
|
const CommentPrefix: string = '//'
|
||||||
|
|
||||||
const PriorityModifierPrio1Lexeme: string = '/!'
|
const PriorityModifierPrio1Lexeme: string = '/!'
|
||||||
|
@ -1489,6 +1491,15 @@ export class SortingSpecProcessor {
|
||||||
foldersOnly: spec.foldersOnly,
|
foldersOnly: spec.foldersOnly,
|
||||||
matchFilenameWithExt: spec.matchFilenameWithExt
|
matchFilenameWithExt: spec.matchFilenameWithExt
|
||||||
}
|
}
|
||||||
|
} else if (theOnly.startsWith(IconIndicatorLexeme)) {
|
||||||
|
const iconName: string | undefined = extractIdentifier(theOnly.substring(IconIndicatorLexeme.length))
|
||||||
|
return {
|
||||||
|
type: CustomSortGroupType.HasIcon,
|
||||||
|
iconName: iconName,
|
||||||
|
filesOnly: spec.filesOnly,
|
||||||
|
foldersOnly: spec.foldersOnly,
|
||||||
|
matchFilenameWithExt: spec.matchFilenameWithExt
|
||||||
|
}
|
||||||
} else if (theOnly.startsWith(StarredItemsIndicatorLexeme)) {
|
} else if (theOnly.startsWith(StarredItemsIndicatorLexeme)) {
|
||||||
return {
|
return {
|
||||||
type: CustomSortGroupType.StarredOnly,
|
type: CustomSortGroupType.StarredOnly,
|
||||||
|
|
|
@ -12,7 +12,24 @@ declare module 'obsidian' {
|
||||||
id: string;
|
id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type CommunityPluginId = string
|
||||||
|
|
||||||
|
// undocumented internal interface - for experimental features
|
||||||
|
export interface CommunityPlugin {
|
||||||
|
manifest: {
|
||||||
|
id: CommunityPluginId
|
||||||
|
}
|
||||||
|
_loaded: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
// undocumented internal interface - for experimental features
|
||||||
|
export interface CommunityPlugins {
|
||||||
|
enabledPlugins: Set<CommunityPluginId>
|
||||||
|
plugins: {[key: CommunityPluginId]: CommunityPlugin}
|
||||||
|
}
|
||||||
|
|
||||||
export interface App {
|
export interface App {
|
||||||
|
plugins: CommunityPlugins;
|
||||||
internalPlugins: InternalPlugins; // undocumented internal API - for experimental features
|
internalPlugins: InternalPlugins; // undocumented internal API - for experimental features
|
||||||
viewRegistry: ViewRegistry;
|
viewRegistry: ViewRegistry;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
import {App, CommunityPlugin, TAbstractFile, TFile, TFolder} from "obsidian";
|
||||||
|
import {Starred_PluginInstance} from "./StarredPluginSignature";
|
||||||
|
|
||||||
|
// For https://github.com/FlorianWoelki/obsidian-icon-folder
|
||||||
|
|
||||||
|
export const ObsidianIconFolderPlugin_getData_methodName = 'getData'
|
||||||
|
|
||||||
|
export interface FolderIconObject {
|
||||||
|
iconName: string | null;
|
||||||
|
inheritanceIcon: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ObsidianIconFolderPlugin_Data = Record<string, boolean | string | FolderIconObject | any>
|
||||||
|
|
||||||
|
export interface ObsidianIconFolder_PluginInstance extends CommunityPlugin {
|
||||||
|
[ObsidianIconFolderPlugin_getData_methodName]: () => ObsidianIconFolderPlugin_Data
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/FlorianWoelki/obsidian-icon-folder/blob/fd9c7df1486744450cec3d7ee9cee2b34d008e56/manifest.json#L2
|
||||||
|
export const ObsidianIconFolderPluginId: string = 'obsidian-icon-folder'
|
||||||
|
|
||||||
|
export const getIconFolderPlugin = (app?: App): ObsidianIconFolder_PluginInstance | undefined => {
|
||||||
|
const iconFolderPlugin: CommunityPlugin | undefined = app?.plugins?.plugins?.[ObsidianIconFolderPluginId]
|
||||||
|
if (iconFolderPlugin && iconFolderPlugin._loaded && app?.plugins?.enabledPlugins?.has(ObsidianIconFolderPluginId)) {
|
||||||
|
const iconFolderPluginInstance: ObsidianIconFolder_PluginInstance = iconFolderPlugin as ObsidianIconFolder_PluginInstance
|
||||||
|
// defensive programming, in case the community plugin changes its internal APIs
|
||||||
|
if (typeof iconFolderPluginInstance?.[ObsidianIconFolderPlugin_getData_methodName] === 'function') {
|
||||||
|
return iconFolderPluginInstance
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intentionally partial and simplified, only detect icons configured directly,
|
||||||
|
// ignoring any icon inheritance or regexp-based applied icons
|
||||||
|
export const determineIconOf = (entry: TAbstractFile, iconFolderPluginInstance: ObsidianIconFolder_PluginInstance): string | undefined => {
|
||||||
|
const iconsData: ObsidianIconFolderPlugin_Data | undefined = iconFolderPluginInstance[ObsidianIconFolderPlugin_getData_methodName]()
|
||||||
|
const entryForPath: any = iconsData?.[entry.path]
|
||||||
|
// Icons configured directly
|
||||||
|
if (typeof entryForPath === 'string') {
|
||||||
|
return entryForPath
|
||||||
|
} else if (typeof (entryForPath as FolderIconObject)?.iconName === 'string') {
|
||||||
|
return (entryForPath as FolderIconObject)?.iconName ?? undefined
|
||||||
|
} else {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {PluginInstance, TFile} from "obsidian";
|
import {App, InstalledPlugin, PluginInstance, TAbstractFile, TFile, TFolder} from "obsidian";
|
||||||
|
|
||||||
export const StarredPlugin_findStarredFile_methodName = 'findStarredFile'
|
export const StarredPlugin_findStarredFile_methodName = 'findStarredFile'
|
||||||
|
|
||||||
|
@ -9,3 +9,35 @@ export interface findStarredFile_pathParam {
|
||||||
export interface Starred_PluginInstance extends PluginInstance {
|
export interface Starred_PluginInstance extends PluginInstance {
|
||||||
[StarredPlugin_findStarredFile_methodName]: (filePath: findStarredFile_pathParam) => TFile | null
|
[StarredPlugin_findStarredFile_methodName]: (filePath: findStarredFile_pathParam) => TFile | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const isFolder = (entry: TAbstractFile) => {
|
||||||
|
// The plain obvious 'entry instanceof TFolder' doesn't work inside Jest unit tests, hence a workaround below
|
||||||
|
return !!((entry as any).isRoot);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 determineStarredStatusOf = (entry: TFile | TFolder, aFile: boolean, starredPluginInstance: Starred_PluginInstance) => {
|
||||||
|
if (aFile) {
|
||||||
|
return !!starredPluginInstance[StarredPlugin_findStarredFile_methodName]({path: entry.path})
|
||||||
|
} else { // aFolder
|
||||||
|
return determineStarredStatusOfFolder(entry as TFolder, starredPluginInstance)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue