Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ The list of automatic sorting orders includes:
- `vsc-unicode` or `unicode-charcode` - tricky for geeks
- `by-metadata:` modifier to use specific metadata for sorting
- `using-extractor:` in connection with `by-metadata:` to use only part of metadata value, for example a date in specified format
- `null-default:` in connection with `by-metadata:` to specify fallback value for missing metadata, e.g. `a-z by-metadata: property_name null-default: 0`
- `,` separator to specify two levels of sorting. When combining folder-level and group-level sorting this allows for up to 4 sorting levels
- `advanced recursive modified` or `advanced recursive created` - advanced variants of `advanced modified` and `advanced created`.
Use with care on larger vaults because the deep scanning of folder descendants can have impact on performance on mobile devices
Expand Down
1 change: 1 addition & 0 deletions src/custom-sort/custom-sort-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export interface CustomSort {
order: CustomSortOrder // mandatory
byMetadata?: string
metadataValueExtractor?: MDataExtractor
nullDefault?: string // fallback value for missing metadata
}

export interface RecognizedSorting {
Expand Down
8 changes: 4 additions & 4 deletions src/custom-sort/custom-sort.ts
Original file line number Diff line number Diff line change
Expand Up @@ -575,25 +575,25 @@ export const determineSortingGroup = function (entry: TFile | TFolder, spec: Cus
group.sorting!.byMetadata || group.withMetadataFieldName || DEFAULT_METADATA_FIELD_FOR_SORTING,
group.sorting!.metadataValueExtractor,
frontMatterCache,
prioFrontMatterCache)
prioFrontMatterCache) ?? group.sorting!.nullDefault
if (isSecondaryOrderByMetadata) metadataValueSecondaryToSortBy =
mdataValueFromFMCaches (
group.secondarySorting!.byMetadata || group.withMetadataFieldName || DEFAULT_METADATA_FIELD_FOR_SORTING,
group.secondarySorting!.metadataValueExtractor,
frontMatterCache,
prioFrontMatterCache)
prioFrontMatterCache) ?? group.secondarySorting!.nullDefault
if (isDerivedPrimaryByMetadata) metadataValueDerivedPrimaryToSortBy =
mdataValueFromFMCaches (
spec.defaultSorting!.byMetadata || DEFAULT_METADATA_FIELD_FOR_SORTING,
spec.defaultSorting!.metadataValueExtractor,
frontMatterCache,
prioFrontMatterCache)
prioFrontMatterCache) ?? spec.defaultSorting!.nullDefault
if (isDerivedSecondaryByMetadata) metadataValueDerivedSecondaryToSortBy =
mdataValueFromFMCaches (
spec.defaultSecondarySorting!.byMetadata || DEFAULT_METADATA_FIELD_FOR_SORTING,
spec.defaultSecondarySorting!.metadataValueExtractor,
frontMatterCache,
prioFrontMatterCache)
prioFrontMatterCache) ?? spec.defaultSecondarySorting!.nullDefault
}
}
}
Expand Down
15 changes: 13 additions & 2 deletions src/custom-sort/sorting-spec-processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ const OrderByMetadataLexeme: string = 'by-metadata:'

const ValueExtractorLexeme: string = 'using-extractor:'

const NullDefaultLexeme: string = 'null-default:'

const OrderLevelsSeparator: string = ','

enum Attribute {
Expand Down Expand Up @@ -1573,9 +1575,17 @@ export class SortingSpecProcessor {

let metadataName: string|undefined
let metadataExtractor: MDataExtractor|undefined
let nullDefault: string|undefined
if (orderSpec.startsWith(OrderByMetadataLexeme)) {
applyToMetadata = true
const metadataNameAndOptionalExtractorSpec = orderSpec.substring(OrderByMetadataLexeme.length).trim() || undefined
let metadataNameAndOptionalExtractorSpec = orderSpec.substring(OrderByMetadataLexeme.length).trim() || undefined

if (metadataNameAndOptionalExtractorSpec?.includes(NullDefaultLexeme)) {
const parts = metadataNameAndOptionalExtractorSpec.split(NullDefaultLexeme)
metadataNameAndOptionalExtractorSpec = parts[0]?.trim()
nullDefault = parts[1]?.trim()
}

if (metadataNameAndOptionalExtractorSpec) {
if (metadataNameAndOptionalExtractorSpec.indexOf(ValueExtractorLexeme) > -1) {
const metadataSpec = metadataNameAndOptionalExtractorSpec.split(ValueExtractorLexeme)
Expand Down Expand Up @@ -1648,7 +1658,8 @@ export class SortingSpecProcessor {
sortOrderSpec[level] = {
order: order!,
byMetadata: metadataName,
metadataValueExtractor: metadataExtractor
metadataValueExtractor: metadataExtractor,
nullDefault: nullDefault
}
}
return sortOrderSpec
Expand Down
46 changes: 46 additions & 0 deletions src/test/unit/custom-sort.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1800,6 +1800,52 @@ describe('determineSortingGroup', () => {
metadataFieldValue: 'direct metadata on file, under default name'
} as FolderItemForSorting);
})
it('should use nullDefault when metadata is missing for derived sorting', () => {
// given
const file: TFile = mockTFile('References', 'md', 111, MOCK_TIMESTAMP + 222, MOCK_TIMESTAMP + 333);
const sortSpec: CustomSortSpec = {
targetFoldersPaths: ['/'],
groups: [{
type: CustomSortGroupType.ExactPrefix,
exactPrefix: 'Ref',
sorting: { order: CustomSortOrder.alphabetical },
}],
defaultSorting: {
order: CustomSortOrder.byMetadataFieldAlphabetical,
byMetadata: 'missing-field',
nullDefault: 'default-value'
}
}
const ctx: Partial<ProcessingContext> = {
_mCache: {
getCache: function (path: string): CachedMetadata | undefined {
return {
'Some parent folder/References.md': {
frontmatter: {
// missing-field is not present
position: MockedLoc
}
}
}[path]
}
} as MetadataCache
}

// when
const result = determineSortingGroup(file, sortSpec, ctx as ProcessingContext)

// then
expect(result).toEqual({
groupIdx: 0,
isFolder: false,
sortString: "References",
sortStringWithExt: "References.md",
ctime: MOCK_TIMESTAMP + 222,
mtime: MOCK_TIMESTAMP + 333,
path: 'Some parent folder/References.md',
metadataFieldValueForDerived: 'default-value' // Should use nullDefault
} as FolderItemForSorting);
})
})

describe('when sort by metadata is involved (specified in secondary sort, for group of for target folder)', () => {
Expand Down
36 changes: 36 additions & 0 deletions src/test/unit/sorting-spec-processor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,37 @@ const expectedSortSpecsExampleMDataExtractors2: { [key: string]: CustomSortSpec
}
}

const txtInputExampleMDataNullDefault: string = `
< a-z by-metadata: priority null-default: zzz
/folders Chapter...
> a-z by-metadata: status using-extractor: date(dd/mm/yyyy) null-default: 1900-01-01
`

const expectedSortSpecsExampleMDataNullDefault: { [key: string]: CustomSortSpec } = {
"mock-folder": {
defaultSorting: {
order: CustomSortOrder.byMetadataFieldAlphabetical,
byMetadata: 'priority',
nullDefault: 'zzz'
},
groups: [{
foldersOnly: true,
type: CustomSortGroupType.ExactPrefix,
exactPrefix: 'Chapter',
sorting: {
order: CustomSortOrder.byMetadataFieldAlphabeticalReverse,
byMetadata: 'status',
metadataValueExtractor: _unitTests.extractorFnForDate_ddmmyyyy,
nullDefault: '1900-01-01'
}
}, {
type: CustomSortGroupType.Outsiders
}],
targetFoldersPaths: ['mock-folder'],
outsidersGroupIdx: 1
}
}

describe('SortingSpecProcessor', () => {
let processor: SortingSpecProcessor;
beforeEach(() => {
Expand Down Expand Up @@ -606,6 +637,11 @@ describe('SortingSpecProcessor', () => {
const result = processor.parseSortSpecFromText(inputTxtArr, 'mock-folder', 'custom-name-note.md')
expect(result?.sortSpecByPath).toEqual(expectedSortSpecsExampleMDataExtractors2)
})
it('should generate correct SortSpecs (example with mdata null-default)', () => {
const inputTxtArr: Array<string> = txtInputExampleMDataNullDefault.split('\n')
const result = processor.parseSortSpecFromText(inputTxtArr, 'mock-folder', 'custom-name-note.md')
expect(result?.sortSpecByPath).toEqual(expectedSortSpecsExampleMDataNullDefault)
})
})

const txtInputNotDuplicatedSortSpec: string = `
Expand Down