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:
SebastianMC 2023-03-01 10:54:34 +01:00 committed by GitHub
commit 63698cb2ed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 613 additions and 31 deletions

View File

@ -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,
HasIcon
}
export enum CustomSortOrder {
@ -59,6 +60,7 @@ export interface CustomSortGroup {
matchFilenameWithExt?: boolean
foldersOnly?: boolean
withMetadataFieldName?: string // for 'with-metadata:' grouping
iconName?: string // for integration with obsidian-folder-icon community plugin
priority?: number
combineWithIdx?: number
}

View File

@ -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.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', () => {
it('should correctly read direct metadata from File item (order by metadata set on group) alph', () => {
// given

View File

@ -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.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:
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<FolderItemForSorting>, 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<FolderItemForSorting> = (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
})

View File

@ -1,7 +1,9 @@
import {
CompoundDashNumberNormalizerFn,
CompoundDashRomanNumberNormalizerFn,
CompoundDotNumberNormalizerFn, ConsumedFolderMatchingRegexp, consumeFolderByRegexpExpression,
CompoundDotNumberNormalizerFn,
ConsumedFolderMatchingRegexp,
consumeFolderByRegexpExpression,
convertPlainStringToRegex,
detectNumericSortingSymbols,
escapeRegexUnsafeCharacters,
@ -29,6 +31,8 @@ target-folder: tricky folder
< a-z by-metadata: Some-dedicated-field
with-metadata: Pages
> a-z by-metadata:
/: with-icon:
with-icon: RiClock24
starred:
/:files starred:
/folders starred:
@ -85,6 +89,8 @@ target-folder: tricky folder 2
< a-z by-metadata: Some-dedicated-field
% with-metadata: Pages
> a-z by-metadata:
/:files with-icon:
/folders:files with-icon: RiClock24
/folders:files starred:
/:files starred:
/folders starred:
@ -171,6 +177,14 @@ const expectedSortSpecsExampleA: { [key: string]: CustomSortSpec } = {
type: CustomSortGroupType.HasMetadataField,
withMetadataFieldName: 'Pages',
order: CustomSortOrder.byMetadataFieldAlphabeticalReverse
}, {
type: CustomSortGroupType.HasIcon,
order: CustomSortOrder.alphabetical,
filesOnly: true
}, {
type: CustomSortGroupType.HasIcon,
order: CustomSortOrder.alphabetical,
iconName: 'RiClock24'
}, {
type: CustomSortGroupType.StarredOnly,
order: CustomSortOrder.alphabetical
@ -186,7 +200,7 @@ const expectedSortSpecsExampleA: { [key: string]: CustomSortSpec } = {
order: CustomSortOrder.alphabetical,
type: CustomSortGroupType.Outsiders
}],
outsidersGroupIdx: 5,
outsidersGroupIdx: 7,
targetFoldersPaths: [
'tricky folder 2'
]

View File

@ -207,6 +207,8 @@ const MetadataFieldIndicatorLexeme: string = 'with-metadata:'
const StarredItemsIndicatorLexeme: string = 'starred:'
const IconIndicatorLexeme: string = 'with-icon:'
const CommentPrefix: string = '//'
const PriorityModifierPrio1Lexeme: string = '/!'
@ -1489,6 +1491,15 @@ export class SortingSpecProcessor {
foldersOnly: spec.foldersOnly,
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)) {
return {
type: CustomSortGroupType.StarredOnly,

17
src/types/types.d.ts vendored
View File

@ -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<CommunityPluginId>
plugins: {[key: CommunityPluginId]: CommunityPlugin}
}
export interface App {
plugins: CommunityPlugins;
internalPlugins: InternalPlugins; // undocumented internal API - for experimental features
viewRegistry: ViewRegistry;
}

View File

@ -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
}
}

View File

@ -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)
}
}