Merge pull request #9 from SebastianMC/merge-upstream-1

Merge upstream obsidianmd/obsidian-sample-plugin + bugfix
This commit is contained in:
SebastianMC 2022-09-14 11:14:05 +02:00 committed by GitHub
commit 3a02ecd65c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 176 additions and 161 deletions

View File

@ -21,30 +21,20 @@ esbuild.build({
'obsidian',
'electron',
'@codemirror/autocomplete',
'@codemirror/closebrackets',
'@codemirror/collab',
'@codemirror/commands',
'@codemirror/comment',
'@codemirror/fold',
'@codemirror/gutter',
'@codemirror/highlight',
'@codemirror/history',
'@codemirror/language',
'@codemirror/lint',
'@codemirror/matchbrackets',
'@codemirror/panel',
'@codemirror/rangeset',
'@codemirror/rectangular-selection',
'@codemirror/search',
'@codemirror/state',
'@codemirror/stream-parser',
'@codemirror/text',
'@codemirror/tooltip',
'@codemirror/view',
'@lezer/common',
'@lezer/highlight',
'@lezer/lr',
...builtins],
format: 'cjs',
watch: !prod,
target: 'es2016',
target: 'es2018',
logLevel: "info",
sourcemap: prod ? false : 'inline',
minify: prod,

View File

@ -18,15 +18,15 @@
"devDependencies": {
"@types/jest": "^28.1.2",
"@types/node": "^16.11.6",
"@typescript-eslint/eslint-plugin": "^5.2.0",
"@typescript-eslint/parser": "^5.2.0",
"builtin-modules": "^3.2.0",
"esbuild": "0.13.12",
"@typescript-eslint/eslint-plugin": "5.29.0",
"@typescript-eslint/parser": "5.29.0",
"builtin-modules": "3.3.0",
"esbuild": "0.14.47",
"jest": "^28.1.1",
"monkey-around": "^2.3.0",
"obsidian": "^0.15.4",
"ts-jest": "^28.0.5",
"tslib": "2.3.1",
"typescript": "4.4.4"
"tslib": "2.4.0",
"typescript": "4.7.4"
}
}

View File

@ -14,7 +14,8 @@ export enum CustomSortOrder {
byModifiedTimeReverse,
byCreatedTime,
byCreatedTimeReverse,
standardObsidian// Let the folder sorting be in hands of Obsidian, whatever user selected in the UI
standardObsidian, // Let the folder sorting be in hands of Obsidian, whatever user selected in the UI
default = alphabetical
}
export interface RecognizedOrderValue {
@ -22,7 +23,7 @@ export interface RecognizedOrderValue {
secondaryOrder?: CustomSortOrder
}
export type NormalizerFn = (s: string) => string
export type NormalizerFn = (s: string) => string | null
export interface RegExpSpec {
regex: RegExp

View File

@ -1,4 +1,4 @@
import {TFile} from 'obsidian';
import {TFile, TFolder, Vault} from 'obsidian';
import {determineSortingGroup} from './custom-sort';
import {CustomSortGroupType, CustomSortSpec} from './custom-sort-types';
import {CompoundDashNumberNormalizerFn, CompoundDotRomanNumberNormalizerFn} from "./sorting-spec-processor";
@ -12,10 +12,10 @@ const mockTFile = (basename: string, ext: string, size?: number, ctime?: number,
},
basename: basename,
extension: ext,
vault: null,
vault: {} as Vault, // To satisfy TS typechecking
path: `Some parent folder/${basename}.${ext}`,
name: `${basename}.${ext}`,
parent: null
parent: {} as TFolder // To satisfy TS typechecking
}
}

View File

@ -1,10 +1,5 @@
import {TFile, TFolder, requireApiVersion} from 'obsidian';
import {
CustomSortGroup,
CustomSortGroupType,
CustomSortOrder,
CustomSortSpec
} from "./custom-sort-types";
import {requireApiVersion, TFile, TFolder} from 'obsidian';
import {CustomSortGroup, CustomSortGroupType, CustomSortOrder, CustomSortSpec} from "./custom-sort-types";
import {isDefined} from "../utils/utils";
let Collator = new Intl.Collator(undefined, {
@ -15,7 +10,7 @@ let Collator = new Intl.Collator(undefined, {
interface FolderItemForSorting {
path: string
groupIdx: number // the index itself represents order for groups
groupIdx?: number // the index itself represents order for groups
sortString: string // fragment (or full name) to be used for sorting
matchGroup?: string // advanced - used for secondary sorting rule, to recognize 'same regex match'
ctime: number
@ -38,16 +33,23 @@ let Sorters: { [key in CustomSortOrder]: SorterFn } = {
};
function compareTwoItems(itA: FolderItemForSorting, itB: FolderItemForSorting, sortSpec: CustomSortSpec) {
if (itA.groupIdx != undefined && itB.groupIdx != undefined) {
if (itA.groupIdx === itB.groupIdx) {
const group: CustomSortGroup = sortSpec.groups[itA.groupIdx]
if (group.regexSpec && group.secondaryOrder && itA.matchGroup === itB.matchGroup) {
return Sorters[group.secondaryOrder](itA, itB)
const group: CustomSortGroup | undefined = sortSpec.groups[itA.groupIdx]
if (group?.regexSpec && group.secondaryOrder && itA.matchGroup === itB.matchGroup) {
return Sorters[group.secondaryOrder ?? CustomSortOrder.default](itA, itB)
} else {
return Sorters[group.order](itA, itB)
return Sorters[group?.order ?? CustomSortOrder.default](itA, itB)
}
} else {
return itA.groupIdx - itB.groupIdx;
}
} else {
// should never happen - groupIdx is not known for at least one of items to compare.
// The logic of determining the index always sets some idx
// Yet for sanity and to satisfy TS code analyzer a fallback to default behavior below
return Sorters[CustomSortOrder.default](itA, itB)
}
}
const isFolder = (entry: TFile | TFolder) => {
@ -58,7 +60,7 @@ const isFolder = (entry: TFile | TFolder) => {
export const determineSortingGroup = function (entry: TFile | TFolder, spec: CustomSortSpec): FolderItemForSorting {
let groupIdx: number
let determined: boolean = false
let matchedGroup: string
let matchedGroup: string | null | undefined
const aFolder: boolean = isFolder(entry)
const aFile: boolean = !aFolder
const entryAsTFile: TFile = entry as TFile
@ -77,10 +79,10 @@ export const determineSortingGroup = function (entry: TFile | TFolder, spec: Cus
determined = true;
}
} else { // regexp is involved
const match: RegExpMatchArray = group.regexSpec.regex.exec(nameForMatching);
const match: RegExpMatchArray | null | undefined = group.regexSpec?.regex.exec(nameForMatching);
if (match) {
determined = true
matchedGroup = group.regexSpec.normalizerFn(match[1]);
matchedGroup = group.regexSpec?.normalizerFn(match[1]);
}
}
break;
@ -90,10 +92,10 @@ export const determineSortingGroup = function (entry: TFile | TFolder, spec: Cus
determined = true;
}
} else { // regexp is involved
const match: RegExpMatchArray = group.regexSpec.regex.exec(nameForMatching);
const match: RegExpMatchArray | null | undefined = group.regexSpec?.regex.exec(nameForMatching);
if (match) {
determined = true
matchedGroup = group.regexSpec.normalizerFn(match[1]);
matchedGroup = group.regexSpec?.normalizerFn(match[1]);
}
}
break;
@ -107,10 +109,10 @@ export const determineSortingGroup = function (entry: TFile | TFolder, spec: Cus
} else { // regexp is involved as the prefix or as the suffix
if ((group.exactPrefix && nameForMatching.startsWith(group.exactPrefix)) ||
(group.exactSuffix && nameForMatching.endsWith(group.exactSuffix))) {
const match: RegExpMatchArray = group.regexSpec.regex.exec(nameForMatching);
const match: RegExpMatchArray | null | undefined = group.regexSpec?.regex.exec(nameForMatching);
if (match) {
const fullMatch: string = match[0]
matchedGroup = group.regexSpec.normalizerFn(match[1]);
matchedGroup = group.regexSpec?.normalizerFn(match[1]);
// check for overlapping of prefix and suffix match (not allowed)
if ((fullMatch.length + (group.exactPrefix?.length ?? 0) + (group.exactSuffix?.length ?? 0)) <= nameForMatching.length) {
determined = true
@ -127,10 +129,10 @@ export const determineSortingGroup = function (entry: TFile | TFolder, spec: Cus
determined = true;
}
} else { // regexp is involved
const match: RegExpMatchArray = group.regexSpec.regex.exec(nameForMatching);
const match: RegExpMatchArray | null | undefined = group.regexSpec?.regex.exec(nameForMatching);
if (match) {
determined = true
matchedGroup = group.regexSpec.normalizerFn(match[1]);
matchedGroup = group.regexSpec?.normalizerFn(match[1]);
}
}
break;
@ -144,7 +146,7 @@ export const determineSortingGroup = function (entry: TFile | TFolder, spec: Cus
}
// the final groupIdx for undetermined folder entry is either the last+1 groupIdx or idx of explicitly defined outsiders group
let determinedGroupIdx = groupIdx;
let determinedGroupIdx: number | undefined = groupIdx;
if (!determined) {
// Automatically assign the index to outsiders group, if relevant was configured
@ -174,7 +176,7 @@ export const folderSort = function (sortingSpec: CustomSortSpec, order: string[]
const folderItems: Array<FolderItemForSorting> = (sortingSpec.itemsToHide ?
this.file.children.filter((entry: TFile | TFolder) => {
return !sortingSpec.itemsToHide.has(entry.name)
return !sortingSpec.itemsToHide!.has(entry.name)
})
:
this.file.children)

View File

@ -58,18 +58,18 @@ describe('folderMatch', () => {
['Reviews/daily/a/Tue/Early/9am', '4 Reviews/daily/a/*']
])('%s should match %s', (path: string, rule: string) => {
const matcher: FolderWildcardMatching<SortingSpec> = createMockMatcherRichVersion()
const match: SortingSpec = matcher.folderMatch(path)
const matchFromCache: SortingSpec = matcher.folderMatch(path)
const match: SortingSpec | null = matcher.folderMatch(path)
const matchFromCache: SortingSpec | null = matcher.folderMatch(path)
expect(match).toBe(rule)
expect(matchFromCache).toBe(rule)
})
it('should correctly handle no-root definitions', () => {
const matcher: FolderWildcardMatching<SortingSpec> = createMockMatcherSimplestVersion()
const match1: SortingSpec = matcher.folderMatch('/')
const match2: SortingSpec = matcher.folderMatch('/Reviews')
const match3: SortingSpec = matcher.folderMatch('/Reviews/daily/')
const match4: SortingSpec = matcher.folderMatch('/Reviews/daily/Mon')
const match5: SortingSpec = matcher.folderMatch('/Reviews/daily/Mon')
const match1: SortingSpec | null = matcher.folderMatch('/')
const match2: SortingSpec | null = matcher.folderMatch('/Reviews')
const match3: SortingSpec | null = matcher.folderMatch('/Reviews/daily/')
const match4: SortingSpec | null = matcher.folderMatch('/Reviews/daily/Mon')
const match5: SortingSpec | null = matcher.folderMatch('/Reviews/daily/Mon')
expect(match1).toBeNull()
expect(match2).toBeNull()
expect(match3).toBe('/Reviews/daily/*')
@ -78,27 +78,27 @@ describe('folderMatch', () => {
})
it('should correctly handle root-only definition', () => {
const matcher: FolderWildcardMatching<SortingSpec> = createMockMatcherRootOnlyVersion()
const match1: SortingSpec = matcher.folderMatch('/')
const match2: SortingSpec = matcher.folderMatch('/Reviews')
const match3: SortingSpec = matcher.folderMatch('/Reviews/daily/')
const match1: SortingSpec | null = matcher.folderMatch('/')
const match2: SortingSpec | null = matcher.folderMatch('/Reviews')
const match3: SortingSpec | null = matcher.folderMatch('/Reviews/daily/')
expect(match1).toBe('/...')
expect(match2).toBe('/...')
expect(match3).toBeNull()
})
it('should correctly handle root-only deep definition', () => {
const matcher: FolderWildcardMatching<SortingSpec> = createMockMatcherRootOnlyDeepVersion()
const match1: SortingSpec = matcher.folderMatch('/')
const match2: SortingSpec = matcher.folderMatch('/Reviews')
const match3: SortingSpec = matcher.folderMatch('/Reviews/daily/')
const match1: SortingSpec | null = matcher.folderMatch('/')
const match2: SortingSpec | null = matcher.folderMatch('/Reviews')
const match3: SortingSpec | null = matcher.folderMatch('/Reviews/daily/')
expect(match1).toBe('/*')
expect(match2).toBe('/*')
expect(match3).toBe('/*')
})
it('should correctly handle match all and match children definitions for same path', () => {
const matcher: FolderWildcardMatching<SortingSpec> = createMockMatcherSimpleVersion()
const match1: SortingSpec = matcher.folderMatch('/')
const match2: SortingSpec = matcher.folderMatch('/Reviews/daily/')
const match3: SortingSpec = matcher.folderMatch('/Reviews/daily/1')
const match1: SortingSpec | null = matcher.folderMatch('/')
const match2: SortingSpec | null = matcher.folderMatch('/Reviews/daily/')
const match3: SortingSpec | null = matcher.folderMatch('/Reviews/daily/1')
expect(match1).toBeNull()
expect(match2).toBe('/Reviews/daily/...')
expect(match3).toBe('/Reviews/daily/...')

View File

@ -41,9 +41,9 @@ export class FolderWildcardMatching<SortingSpec> {
// cache
determinedWildcardRules: { [key: string]: DeterminedSortingSpec<SortingSpec> } = {}
addWildcardDefinition = (wilcardDefinition: string, rule: SortingSpec): AddingWildcardFailure | null => {
addWildcardDefinition = (wilcardDefinition: string, rule: SortingSpec): AddingWildcardFailure | null | undefined => {
const pathComponents: Array<string> = splitPath(wilcardDefinition)
const lastComponent: string = pathComponents.pop()
const lastComponent: string | undefined = pathComponents.pop()
if (lastComponent !== MATCH_ALL_PATH_TOKEN && lastComponent !== MATCH_CHILDREN_PATH_TOKEN) {
return null
}
@ -82,8 +82,8 @@ export class FolderWildcardMatching<SortingSpec> {
if (spec) {
return spec.spec ?? null
} else {
let rule: SortingSpec = this.tree.matchChildren
let inheritedRule: SortingSpec = this.tree.matchAll
let rule: SortingSpec | null | undefined = this.tree.matchChildren
let inheritedRule: SortingSpec | undefined = this.tree.matchAll
const pathComponents: Array<string> = splitPath(folderPath)
let parentNode: FolderMatchingTreeNode<SortingSpec> = this.tree
let lastIdx: number = pathComponents.length - 1

View File

@ -26,7 +26,7 @@ describe('Plain numbers regexp', () => {
const match: RegExpMatchArray | null = s.match(NumberRegex)
if (out) {
expect(match).not.toBeNull()
expect(match[1]).toBe(out)
expect(match?.[1]).toBe(out)
} else {
expect(match).toBeNull()
}
@ -58,7 +58,7 @@ describe('Plain compound numbers regexp (dot)', () => {
const match: RegExpMatchArray | null = s.match(CompoundNumberDotRegex)
if (out) {
expect(match).not.toBeNull()
expect(match[1]).toBe(out)
expect(match?.[1]).toBe(out)
} else {
expect(match).toBeNull()
}
@ -90,7 +90,7 @@ describe('Plain compound numbers regexp (dash)', () => {
const match: RegExpMatchArray | null = s.match(CompoundNumberDashRegex)
if (out) {
expect(match).not.toBeNull()
expect(match[1]).toBe(out)
expect(match?.[1]).toBe(out)
} else {
expect(match).toBeNull()
}
@ -112,7 +112,7 @@ describe('Plain Roman numbers regexp', () => {
const match: RegExpMatchArray | null = s.match(RomanNumberRegex)
if (out) {
expect(match).not.toBeNull()
expect(match[1]).toBe(out)
expect(match?.[1]).toBe(out)
} else {
expect(match).toBeNull()
}
@ -146,7 +146,7 @@ describe('Roman compound numbers regexp (dot)', () => {
const match: RegExpMatchArray | null = s.match(CompoundRomanNumberDotRegex)
if (out) {
expect(match).not.toBeNull()
expect(match[1]).toBe(out)
expect(match?.[1]).toBe(out)
} else {
expect(match).toBeNull()
}
@ -180,7 +180,7 @@ describe('Roman compound numbers regexp (dash)', () => {
const match: RegExpMatchArray | null = s.match(CompoundRomanNumberDashRegex)
if (out) {
expect(match).not.toBeNull()
expect(match[1]).toBe(out)
expect(match?.[1]).toBe(out)
} else {
expect(match).toBeNull()
}

View File

@ -506,7 +506,7 @@ describe('SortingSpecProcessor', () => {
const inputTxtArr: Array<string> = txtInputSimplistic1.split('\n')
const result = processor.parseSortSpecFromText(inputTxtArr, 'mock-folder', 'custom-name-note.md')
expect(result?.sortSpecByPath).toEqual(expectedSortSpecForSimplistic1)
expect(result?.sortSpecByWildcard.tree).toEqual(expectedWildcardMatchingTreeForSimplistic1)
expect(result?.sortSpecByWildcard?.tree).toEqual(expectedWildcardMatchingTreeForSimplistic1)
})
it('should recognize the simplistic sorting spec to put files first (direct / rule)', () => {
const inputTxtArr: Array<string> = txtInputSimplistic2.split('\n')
@ -837,31 +837,31 @@ describe('SortingSpecProcessor path wildcard priorities', () => {
const inputTxtArr: Array<string> = txtInputTargetFolderMultiSpecA.split('\n')
const result = processor.parseSortSpecFromText(inputTxtArr, 'mock-folder', 'custom-name-note.md')
expect(result?.sortSpecByPath).toEqual(expectedSortSpecForMultiSpecAandB)
expect(result?.sortSpecByWildcard.tree).toEqual(expectedWildcardMatchingTreeForMultiSpecAandB)
expect(result?.sortSpecByWildcard?.tree).toEqual(expectedWildcardMatchingTreeForMultiSpecAandB)
})
it('should not raise error for multiple spec for the same path and choose correct spec, case B', () => {
const inputTxtArr: Array<string> = txtInputTargetFolderMultiSpecB.split('\n')
const result = processor.parseSortSpecFromText(inputTxtArr, 'mock-folder', 'custom-name-note.md')
expect(result?.sortSpecByPath).toEqual(expectedSortSpecForMultiSpecAandB)
expect(result?.sortSpecByWildcard.tree).toEqual(expectedWildcardMatchingTreeForMultiSpecAandB)
expect(result?.sortSpecByWildcard?.tree).toEqual(expectedWildcardMatchingTreeForMultiSpecAandB)
})
it('should not raise error for multiple spec for the same path and choose correct spec, case C', () => {
const inputTxtArr: Array<string> = txtInputTargetFolderMultiSpecC.split('\n')
const result = processor.parseSortSpecFromText(inputTxtArr, 'mock-folder', 'custom-name-note.md')
expect(result?.sortSpecByPath).toEqual(expectedSortSpecForMultiSpecC)
expect(result?.sortSpecByWildcard.tree).toEqual(expectedWildcardMatchingTreeForMultiSpecC)
expect(result?.sortSpecByWildcard?.tree).toEqual(expectedWildcardMatchingTreeForMultiSpecC)
})
it('should not raise error for multiple spec for the same path and choose correct spec, case D', () => {
const inputTxtArr: Array<string> = txtInputTargetFolderMultiSpecD.split('\n')
const result = processor.parseSortSpecFromText(inputTxtArr, 'mock-folder', 'custom-name-note.md')
expect(result?.sortSpecByPath).toEqual(expectedSortSpecForMultiSpecD)
expect(result?.sortSpecByWildcard.tree).toEqual(expectedWildcardMatchingTreeForMultiSpecD)
expect(result?.sortSpecByWildcard?.tree).toEqual(expectedWildcardMatchingTreeForMultiSpecD)
})
it('should not raise error for multiple spec for the same path and choose correct spec, case E', () => {
const inputTxtArr: Array<string> = txtInputTargetFolderMultiSpecE.split('\n')
const result = processor.parseSortSpecFromText(inputTxtArr, 'mock-folder', 'custom-name-note.md')
expect(result?.sortSpecByPath).toEqual(expectedSortSpecForMultiSpecE)
expect(result?.sortSpecByWildcard.tree).toEqual(expectedWildcardMatchingTreeForMultiSpecE)
expect(result?.sortSpecByWildcard?.tree).toEqual(expectedWildcardMatchingTreeForMultiSpecE)
})
})
@ -1224,7 +1224,7 @@ describe('convertPlainStringWithNumericSortingSymbolToRegex', () => {
['abc\\d+efg\\d+hij', /abc *(\d+)efg/i], // Double numerical sorting symbol, error case, covered for clarity of implementation detail
])('should correctly extract from >%s< the numeric sorting symbol (%s)', (s: string, regex: RegExp) => {
const result = convertPlainStringWithNumericSortingSymbolToRegex(s, RegexpUsedAs.InUnitTest)
expect(result.regexpSpec.regex).toEqual(regex)
expect(result?.regexpSpec.regex).toEqual(regex)
// No need to examine prefix and suffix fields of result, they are secondary and derived from the returned regexp
})
it('should not process string not containing numeric sorting symbol', () => {
@ -1235,16 +1235,16 @@ describe('convertPlainStringWithNumericSortingSymbolToRegex', () => {
it('should correctly include regex token for string begin', () => {
const input = 'Part\\-D+:'
const result = convertPlainStringWithNumericSortingSymbolToRegex(input, RegexpUsedAs.Prefix)
expect(result.regexpSpec.regex).toEqual(/^Part *(\d+(?:-\d+)*):/i)
expect(result?.regexpSpec.regex).toEqual(/^Part *(\d+(?:-\d+)*):/i)
})
it('should correctly include regex token for string end', () => {
const input = 'Part\\-D+:'
const result = convertPlainStringWithNumericSortingSymbolToRegex(input, RegexpUsedAs.Suffix)
expect(result.regexpSpec.regex).toEqual(/Part *(\d+(?:-\d+)*):$/i)
expect(result?.regexpSpec.regex).toEqual(/Part *(\d+(?:-\d+)*):$/i)
})
it('should correctly include regex token for string begin and end', () => {
const input = 'Part\\.D+:'
const result = convertPlainStringWithNumericSortingSymbolToRegex(input, RegexpUsedAs.FullMatch)
expect(result.regexpSpec.regex).toEqual(/^Part *(\d+(?:\.\d+)*):$/i)
expect(result?.regexpSpec.regex).toEqual(/^Part *(\d+(?:\.\d+)*):$/i)
})
})

View File

@ -212,10 +212,14 @@ export const detectNumericSortingSymbols = (s: string): boolean => {
return numericSortingSymbolsRegex.test(s)
}
export const extractNumericSortingSymbol = (s: string): string => {
export const extractNumericSortingSymbol = (s?: string): string | null => {
if (s) {
numericSortingSymbolsRegex.lastIndex = 0
const matches: RegExpMatchArray = numericSortingSymbolsRegex.exec(s)
const matches: RegExpMatchArray | null = numericSortingSymbolsRegex.exec(s)
return matches ? matches[0] : null
} else {
return null
}
}
export interface RegExpSpecStr {
@ -271,11 +275,11 @@ export enum RegexpUsedAs {
FullMatch
}
export const convertPlainStringWithNumericSortingSymbolToRegex = (s: string, actAs: RegexpUsedAs): ExtractedNumericSortingSymbolInfo => {
const detectedSymbol: string = extractNumericSortingSymbol(s)
export const convertPlainStringWithNumericSortingSymbolToRegex = (s?: string, actAs?: RegexpUsedAs): ExtractedNumericSortingSymbolInfo | null => {
const detectedSymbol: string | null = extractNumericSortingSymbol(s)
if (detectedSymbol) {
const replacement: RegExpSpecStr = numericSortingSymbolToRegexpStr[detectedSymbol.toLowerCase()]
const [extractedPrefix, extractedSuffix] = s.split(detectedSymbol)
const [extractedPrefix, extractedSuffix] = s!.split(detectedSymbol)
const regexPrefix: string = actAs === RegexpUsedAs.Prefix || actAs === RegexpUsedAs.FullMatch ? '^' : ''
const regexSuffix: string = actAs === RegexpUsedAs.Suffix || actAs === RegexpUsedAs.FullMatch ? '$' : ''
return {
@ -356,11 +360,11 @@ const ADJACENCY_ERROR: string = "Numerical sorting symbol must not be directly a
export class SortingSpecProcessor {
ctx: ProcessingContext
currentEntryLine: string
currentEntryLineIdx: number
currentSortingSpecContainerFilePath: string
problemAlreadyReportedForCurrentLine: boolean
recentErrorMessage: string
currentEntryLine: string | null
currentEntryLineIdx: number | null
currentSortingSpecContainerFilePath: string | null
problemAlreadyReportedForCurrentLine: boolean | null
recentErrorMessage: string | null
// Helper map to deal with rule priorities for the same path
// and also detect non-wildcard duplicates.
@ -376,8 +380,8 @@ export class SortingSpecProcessor {
parseSortSpecFromText(text: Array<string>,
folderPath: string,
sortingSpecFileName: string,
collection?: SortSpecsCollection
): SortSpecsCollection {
collection?: SortSpecsCollection | null
): SortSpecsCollection | null | undefined {
// reset / init processing state after potential previous invocation
this.ctx = {
folderPath: folderPath, // location of the sorting spec file
@ -404,12 +408,12 @@ export class SortingSpecProcessor {
success = false // Empty lines and comments are OK, that's why setting so late
const attr: ParsedSortingAttribute = this._l1s1_parseAttribute(entryLine);
const attr: ParsedSortingAttribute | null = this._l1s1_parseAttribute(entryLine);
if (attr) {
success = this._l1s2_processParsedSortingAttribute(attr);
this.ctx.previousValidEntryWasTargetFolderAttr = success && (attr.attribute === Attribute.TargetFolder)
} else if (!this.problemAlreadyReportedForCurrentLine && !this._l1s3_checkForRiskyAttrSyntaxError(entryLine)) {
let group: ParsedSortingGroup = this._l1s4_parseSortingGroupSpec(entryLine);
let group: ParsedSortingGroup | null = this._l1s4_parseSortingGroupSpec(entryLine);
if (!this.problemAlreadyReportedForCurrentLine && !group) {
// Default for unrecognized syntax: treat the line as exact name (of file or folder)
group = {plainSpec: trimmedEntryLine}
@ -433,7 +437,7 @@ export class SortingSpecProcessor {
this._l1s6_postprocessSortSpec(spec)
}
let sortspecByWildcard: FolderWildcardMatching<CustomSortSpec>
let sortspecByWildcard: FolderWildcardMatching<CustomSortSpec> | undefined
for (let spec of this.ctx.specs) {
// Consume the folder paths ending with wildcard specs
for (let idx = 0; idx<spec.targetFoldersPaths.length; idx++) {
@ -553,9 +557,14 @@ export class SortingSpecProcessor {
if (attr.attribute === Attribute.TargetFolder) {
if (attr.nesting === 0) { // root-level attribute causing creation of new spec or decoration of a previous one
if (this.ctx.previousValidEntryWasTargetFolderAttr) {
if (this.ctx.currentSpec) {
this.ctx.currentSpec.targetFoldersPaths.push(attr.value)
} else {
this._l2s2_putNewSpecForNewTargetFolder(attr.value)
// Should never reach this execution path, yet for sanity and clarity:
this.ctx.currentSpec = this._l2s2_putNewSpecForNewTargetFolder(attr.value)
}
} else {
this.ctx.currentSpec = this._l2s2_putNewSpecForNewTargetFolder(attr.value)
}
return true
} else {
@ -565,7 +574,7 @@ export class SortingSpecProcessor {
} else if (attr.attribute === Attribute.OrderAsc || attr.attribute === Attribute.OrderDesc || attr.attribute === Attribute.OrderStandardObsidian) {
if (attr.nesting === 0) {
if (!this.ctx.currentSpec) {
this._l2s2_putNewSpecForNewTargetFolder()
this.ctx.currentSpec = this._l2s2_putNewSpecForNewTargetFolder()
}
if (this.ctx.currentSpec.defaultOrder) {
const folderPathsForProblemMsg: string = this.ctx.currentSpec.targetFoldersPaths.join(' :: ');
@ -663,7 +672,7 @@ export class SortingSpecProcessor {
private _l1s5_processParsedSortGroupSpec(group: ParsedSortingGroup): boolean {
if (!this.ctx.currentSpec) {
this._l2s2_putNewSpecForNewTargetFolder()
this.ctx.currentSpec = this._l2s2_putNewSpecForNewTargetFolder()
}
if (group.plainSpec) {
@ -679,14 +688,18 @@ export class SortingSpecProcessor {
return true
}
} else { // !group.itemToHide
const newGroup: CustomSortGroup = this._l2s4_consumeParsedSortingGroupSpec(group)
const newGroup: CustomSortGroup | null = this._l2s4_consumeParsedSortingGroupSpec(group)
if (newGroup) {
if (this._l2s5_adjustSortingGroupForNumericSortingSymbol(newGroup)) {
if (this.ctx.currentSpec) {
this.ctx.currentSpec.groups.push(newGroup)
this.ctx.currentSpecGroup = newGroup
return true;
} else {
return false;
return false
}
} else {
return false
}
} else {
return false;
@ -699,8 +712,8 @@ export class SortingSpecProcessor {
spec.outsidersGroupIdx = undefined
spec.outsidersFilesGroupIdx = undefined
spec.outsidersFoldersGroupIdx = undefined
let outsidersGroupForFolders: boolean
let outsidersGroupForFiles: boolean
let outsidersGroupForFolders: boolean | undefined
let outsidersGroupForFiles: boolean | undefined
// process all defined sorting groups
for (let groupIdx = 0; groupIdx < spec.groups.length; groupIdx++) {
@ -778,7 +791,7 @@ export class SortingSpecProcessor {
}
private _l2s1_validateOrderAscAttrValue = (v: string): RecognizedOrderValue | null => {
const recognized: CustomSortOrderAscDescPair = this._l2s1_internal_validateOrderAttrValue(v)
const recognized: CustomSortOrderAscDescPair | null = this._l2s1_internal_validateOrderAttrValue(v)
return recognized ? {
order: recognized.asc,
secondaryOrder: recognized.secondary
@ -786,7 +799,7 @@ export class SortingSpecProcessor {
}
private _l2s1_validateOrderDescAttrValue = (v: string): RecognizedOrderValue | null => {
const recognized: CustomSortOrderAscDescPair = this._l2s1_internal_validateOrderAttrValue(v)
const recognized: CustomSortOrderAscDescPair | null = this._l2s1_internal_validateOrderAttrValue(v)
return recognized ? {
order: recognized.desc,
secondaryOrder: recognized.secondary
@ -833,27 +846,30 @@ export class SortingSpecProcessor {
return [spec];
}
private _l2s2_putNewSpecForNewTargetFolder(folderPath?: string) {
private _l2s2_putNewSpecForNewTargetFolder(folderPath?: string): CustomSortSpec {
const newSpec: CustomSortSpec = {
targetFoldersPaths: [folderPath ?? this.ctx.folderPath],
groups: []
}
this.ctx.specs.push(newSpec);
this.ctx.currentSpec = newSpec;
this.ctx.currentSpec = undefined;
this.ctx.currentSpecGroup = undefined;
return newSpec
}
// Detection of slippery syntax errors which can confuse user due to false positive parsing with an unexpected sorting result
private _l2s3_consumeParsedItemToHide(spec: ParsedSortingGroup): boolean {
if (spec.arraySpec.length === 1) {
if (spec.arraySpec?.length === 1) {
const theOnly: string = spec.arraySpec[0]
if (!isThreeDots(theOnly)) {
const nameWithExt: string = theOnly.trim()
if (nameWithExt) { // Sanity check
if (!detectNumericSortingSymbols(nameWithExt)) {
const itemsToHide: Set<string> = this.ctx.currentSpec.itemsToHide ?? new Set<string>()
if (this.ctx.currentSpec) {
const itemsToHide: Set<string> = this.ctx.currentSpec?.itemsToHide ?? new Set<string>()
itemsToHide.add(nameWithExt)
this.ctx.currentSpec.itemsToHide = itemsToHide
return true
@ -861,10 +877,11 @@ export class SortingSpecProcessor {
}
}
}
}
return false
}
private _l2s4_consumeParsedSortingGroupSpec = (spec: ParsedSortingGroup): CustomSortGroup => {
private _l2s4_consumeParsedSortingGroupSpec = (spec: ParsedSortingGroup): CustomSortGroup | null => {
if (spec.outsidersGroup) {
return {
type: CustomSortGroupType.Outsiders,
@ -874,7 +891,7 @@ export class SortingSpecProcessor {
} // theoretically could match the sorting of matched files
}
if (spec.arraySpec.length === 1) {
if (spec.arraySpec?.length === 1) {
const theOnly: string = spec.arraySpec[0]
if (isThreeDots(theOnly)) {
return {
@ -894,7 +911,7 @@ export class SortingSpecProcessor {
}
}
}
if (spec.arraySpec.length === 2) {
if (spec.arraySpec?.length === 2) {
const theFirst: string = spec.arraySpec[0]
const theSecond: string = spec.arraySpec[1]
if (isThreeDots(theFirst) && !isThreeDots(theSecond) && !containsThreeDots(theSecond)) {
@ -919,7 +936,7 @@ export class SortingSpecProcessor {
return null;
}
}
if (spec.arraySpec.length === 3) {
if (spec.arraySpec?.length === 3) {
const theFirst: string = spec.arraySpec[0]
const theMiddle: string = spec.arraySpec[1]
const theLast: string = spec.arraySpec[2]

View File

@ -42,19 +42,22 @@ const SORTINGSPEC_YAML_KEY: string = 'sorting-spec'
const ERROR_NOTICE_TIMEOUT: number = 10000
// the monkey-around package doesn't export the below type
type MonkeyAroundUninstaller = () => void
export default class CustomSortPlugin extends Plugin {
settings: CustomSortPluginSettings
statusBarItemEl: HTMLElement
ribbonIconEl: HTMLElement
sortSpecCache: SortSpecsCollection
sortSpecCache?: SortSpecsCollection | null
initialAutoOrManualSortingTriggered: boolean
readAndParseSortingSpec() {
const mCache: MetadataCache = this.app.metadataCache
let failed: boolean = false
let anySortingSpecFound: boolean = false
let errorMessage: string
let errorMessage: string | null = null
// reset cache
this.sortSpecCache = null
const processor: SortingSpecProcessor = new SortingSpecProcessor()
@ -68,7 +71,7 @@ export default class CustomSortPlugin extends Plugin {
// - files with designated name (sortspec.md by default)
// - files with the same name as parent folders (aka folder notes): References/References.md
// - the file explicitly indicated in documentation, by default Inbox/Inbox.md
if (aFile.name === SORTSPEC_FILE_NAME || aFile.basename === parent.name || aFile.path === DEFAULT_SETTINGS.additionalSortspecFile) {
if (aFile.name === SORTSPEC_FILE_NAME || aFile.basename === parent.name || aFile.path === this.settings.additionalSortspecFile) {
const sortingSpecTxt: string = mCache.getCache(aFile.path)?.frontmatter?.[SORTINGSPEC_YAML_KEY]
if (sortingSpecTxt) {
anySortingSpecFound = true
@ -229,9 +232,8 @@ export default class CustomSortPlugin extends Plugin {
// @ts-ignore
let tmpFolder = new TFolder(Vault, "");
let Folder = fileExplorer.createFolderDom(tmpFolder).constructor;
this.register(
// TODO: Unit tests please!!! The logic below becomes more and more complex, bugs are captured at run-time...
around(Folder.prototype, {
const uninstallerOfFolderSortFunctionWrapper: MonkeyAroundUninstaller = around(Folder.prototype, {
// TODO: Unit tests, the logic below becomes more and more complex, bugs are captured at run-time...
sort(old: any) {
return function (...args: any[]) {
// quick check for plugin status
@ -241,7 +243,7 @@ export default class CustomSortPlugin extends Plugin {
// if custom sort is not specified, use the UI-selected
const folder: TFolder = this.file
let sortSpec: CustomSortSpec = plugin.sortSpecCache?.sortSpecByPath[folder.path]
let sortSpec: CustomSortSpec | null | undefined = plugin.sortSpecCache?.sortSpecByPath[folder.path]
if (sortSpec) {
if (sortSpec.defaultOrder === CustomSortOrder.standardObsidian) {
sortSpec = null // A folder is explicitly excluded from custom sorting plugin
@ -259,9 +261,9 @@ export default class CustomSortPlugin extends Plugin {
return old.call(this, ...args);
}
};
},
}
})
);
this.register(uninstallerOfFolderSortFunctionWrapper)
leaf.detach()
}

View File

@ -1,5 +1,7 @@
import {TFolder, WorkspaceLeaf} from "obsidian";
// Needed to support monkey-patching of the folder sort() function
declare module 'obsidian' {
export interface ViewRegistry {
viewByType: Record<string, (leaf: WorkspaceLeaf) => unknown>;

View File

@ -11,6 +11,7 @@
"moduleResolution": "node",
"importHelpers": true,
"isolatedModules": true,
"strictNullChecks": true,
"lib": [
"DOM",
"ES5",