diff --git a/src/custom-sort/custom-sort-types.ts b/src/custom-sort/custom-sort-types.ts index f50f916..05d86e7 100644 --- a/src/custom-sort/custom-sort-types.ts +++ b/src/custom-sort/custom-sort-types.ts @@ -8,7 +8,8 @@ export enum CustomSortGroupType { 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 - StarredOnly + StarredOnly, + IconFolderPlugin } export enum CustomSortOrder { @@ -59,6 +60,7 @@ export interface CustomSortGroup { matchFilenameWithExt?: boolean foldersOnly?: boolean withMetadataFieldName?: string // for 'with-metadata:' grouping + folderIconName?: string // for integration with obsidian-folder-icon community plugin priority?: number combineWithIdx?: number } diff --git a/src/custom-sort/custom-sort.spec.ts b/src/custom-sort/custom-sort.spec.ts index fabb123..fa89b25 100644 --- a/src/custom-sort/custom-sort.spec.ts +++ b/src/custom-sort/custom-sort.spec.ts @@ -12,6 +12,10 @@ import { import {CustomSortGroupType, CustomSortOrder, CustomSortSpec, RegExpSpec} from './custom-sort-types'; import {CompoundDashNumberNormalizerFn, CompoundDotRomanNumberNormalizerFn} from "./sorting-spec-processor"; 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 => { return { @@ -1067,6 +1071,458 @@ describe('determineSortingGroup', () => { expect(starredPluginInstance.findStarredFile).toHaveBeenCalledTimes(2) }) }) + describe('CustomSortGroupType.IconFolderPlugin', () => { + 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.IconFolderPlugin + }] + } + const obsidianIconFolderPluginInstance: Partial = { + 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.IconFolderPlugin, + folderIconName: 'IncorrectIconName' + }] + } + const obsidianIconFolderPluginInstance: Partial = { + 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.IconFolderPlugin + }] + } + const obsidianIconFolderPluginInstance: Partial = { + 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.IconFolderPlugin, + folderIconName: 'CorrectIconName' + }] + } + const obsidianIconFolderPluginInstance: Partial = { + 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.IconFolderPlugin + }] + } + const obsidianIconFolderPluginInstance: Partial = { + 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.IconFolderPlugin + }] + } + const obsidianIconFolderPluginInstance: Partial = { + 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.IconFolderPlugin + }] + } + const obsidianIconFolderPluginInstance: Partial = { + 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.IconFolderPlugin, + folderIconName: 'ConfiguredIcon-by-string' + }] + } + const obsidianIconFolderPluginInstance: Partial = { + 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.IconFolderPlugin, + folderIconName: 'ConfiguredIcon' + }] + } + const obsidianIconFolderPluginInstance: Partial = { + 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.IconFolderPlugin, + folderIconName: 'ConfiguredIcon-by-string' + }] + } + const obsidianIconFolderPluginInstance: Partial = { + 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.IconFolderPlugin, + folderIconName: 'ConfiguredIcon' + }] + } + const obsidianIconFolderPluginInstance: Partial = { + 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', () => { it('should correctly read direct metadata from File item (order by metadata set on group) alph', () => { // given diff --git a/src/custom-sort/custom-sort.ts b/src/custom-sort/custom-sort.ts index c09021f..3e38730 100644 --- a/src/custom-sort/custom-sort.ts +++ b/src/custom-sort/custom-sort.ts @@ -1,5 +1,6 @@ import { App, + CommunityPlugin, FrontMatterCache, InstalledPlugin, requireApiVersion, @@ -8,9 +9,19 @@ import { TFolder } from 'obsidian'; import { + determineStarredStatusOf, + getStarredPlugin, Starred_PluginInstance, StarredPlugin_findStarredFile_methodName -} from '../utils/StarredPluginSignature' +} from '../utils/StarredPluginSignature'; +import { + determineIconOf, + getIconFolderPlugin, + FolderIconObject, + ObsidianIconFolder_PluginInstance, + ObsidianIconFolderPlugin_Data, + ObsidianIconFolderPlugin_getData_methodName +} from '../utils/ObsidianIconFolderPluginSignature' import { CustomSortGroup, CustomSortGroupType, @@ -154,6 +165,7 @@ export const matchGroupRegex = (theRegex: RegExpSpec, nameForMatching: string): export interface Context { starredPluginInstance?: Starred_PluginInstance + iconFolderPluginInstance?: ObsidianIconFolder_PluginInstance } 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 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) - } + let starred: boolean = determineStarredStatusOf(entry, aFile, ctx.starredPluginInstance) if (starred) { determined = true } } break + case CustomSortGroupType.IconFolderPlugin: + if(ctx?.iconFolderPluginInstance) { + let iconName: string | undefined = determineIconOf(entry, ctx.iconFolderPluginInstance) + if (iconName) { + if (group.folderIconName) { + determined = iconName === group.folderIconName + } else { + determined = true + } + } + } + break case CustomSortGroupType.MatchAll: determined = true; break @@ -389,25 +408,6 @@ export const determineDatesForFolder = (folder: TFolder, now: number): [Modified 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, sortingSpec: CustomSortSpec) => { const Now: number = Date.now() folderItems.forEach((item) => { @@ -427,6 +427,7 @@ export const folderSort = function (sortingSpec: CustomSortSpec, order: string[] let fileExplorer = this.fileExplorer sortingSpec._mCache = sortingSpec.plugin?.app.metadataCache const starredPluginInstance: Starred_PluginInstance | undefined = getStarredPlugin(sortingSpec?.plugin?.app) + const iconFolderPluginInstance: ObsidianIconFolder_PluginInstance | undefined = getIconFolderPlugin(sortingSpec?.plugin?.app) const folderItems: Array = (sortingSpec.itemsToHide ? this.file.children.filter((entry: TFile | TFolder) => { @@ -436,7 +437,8 @@ export const folderSort = function (sortingSpec: CustomSortSpec, order: string[] this.file.children) .map((entry: TFile | TFolder) => { const itemForSorting: FolderItemForSorting = determineSortingGroup(entry, sortingSpec, { - starredPluginInstance: starredPluginInstance + starredPluginInstance: starredPluginInstance, + iconFolderPluginInstance: iconFolderPluginInstance }) return itemForSorting }) diff --git a/src/types/types.d.ts b/src/types/types.d.ts index 240f256..ef295f5 100644 --- a/src/types/types.d.ts +++ b/src/types/types.d.ts @@ -12,7 +12,24 @@ declare module 'obsidian' { 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 + plugins: {[key: CommunityPluginId]: CommunityPlugin} + } + export interface App { + plugins: CommunityPlugins; internalPlugins: InternalPlugins; // undocumented internal API - for experimental features viewRegistry: ViewRegistry; } diff --git a/src/utils/ObsidianIconFolderPluginSignature.ts b/src/utils/ObsidianIconFolderPluginSignature.ts new file mode 100644 index 0000000..f1f6e30 --- /dev/null +++ b/src/utils/ObsidianIconFolderPluginSignature.ts @@ -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 + +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 + } +} + + diff --git a/src/utils/StarredPluginSignature.ts b/src/utils/StarredPluginSignature.ts index db5df02..0a541ae 100644 --- a/src/utils/StarredPluginSignature.ts +++ b/src/utils/StarredPluginSignature.ts @@ -1,4 +1,4 @@ -import {PluginInstance, TFile} from "obsidian"; +import {App, InstalledPlugin, PluginInstance, TAbstractFile, TFile, TFolder} from "obsidian"; export const StarredPlugin_findStarredFile_methodName = 'findStarredFile' @@ -9,3 +9,35 @@ export interface findStarredFile_pathParam { export interface Starred_PluginInstance extends PluginInstance { [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) + } +}