mirror of
https://github.com/callumalpass/tasknotes.git
synced 2026-07-22 12:50:26 +00:00
fix materialized occurrences in bases calendar
This commit is contained in:
parent
bf1428c959
commit
df9bcd8286
3 changed files with 137 additions and 4 deletions
|
|
@ -31,3 +31,7 @@ When a change has user-facing documentation, include a canonical tasknotes.dev l
|
|||
```
|
||||
|
||||
-->
|
||||
|
||||
## Fixed
|
||||
|
||||
- (#1995) Fixed Bases Calendar views showing both a materialized occurrence note and its matching virtual recurring task instance. Thanks to @rdefaccio for reporting this.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import TaskNotesPlugin from "../main";
|
||||
import { Reminder, TaskDependency, TaskInfo, TimeEntry } from "../types";
|
||||
import type { OccurrenceMaterializationMode, OccurrenceNextTrigger } from "@tasknotes/model";
|
||||
import { setIcon } from "obsidian";
|
||||
import { calculateTotalTimeSpent } from "../utils/helpers";
|
||||
import { format } from "date-fns";
|
||||
|
|
@ -86,6 +87,20 @@ function toOptionalNumber(value: unknown): number | undefined {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
function toOccurrenceMaterializationMode(
|
||||
value: unknown
|
||||
): OccurrenceMaterializationMode | undefined {
|
||||
const mode = toOptionalString(value);
|
||||
return mode === "manual" || mode === "on_completion" || mode === "rolling"
|
||||
? mode
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function toOccurrenceNextTrigger(value: unknown): OccurrenceNextTrigger | undefined {
|
||||
const trigger = toOptionalString(value);
|
||||
return trigger === "completion" || trigger === "completion_or_skip" ? trigger : undefined;
|
||||
}
|
||||
|
||||
function toTimeEntries(value: unknown): TimeEntry[] | undefined {
|
||||
return Array.isArray(value) ? (value as TimeEntry[]) : undefined;
|
||||
}
|
||||
|
|
@ -166,6 +181,13 @@ function createTaskInfoFromProperties(
|
|||
"timeEstimate",
|
||||
"completedDate",
|
||||
"recurrence",
|
||||
"recurrence_parent",
|
||||
"occurrence_date",
|
||||
"occurrence_materialization",
|
||||
"occurrence_next_trigger",
|
||||
"occurrence_template",
|
||||
"occurrence_past_horizon",
|
||||
"occurrence_future_horizon",
|
||||
"dateCreated",
|
||||
"dateModified",
|
||||
"timeEntries",
|
||||
|
|
@ -224,6 +246,15 @@ function createTaskInfoFromProperties(
|
|||
timeEstimate: toOptionalNumber(props.timeEstimate),
|
||||
completedDate: toOptionalString(props.completedDate),
|
||||
recurrence: toOptionalString(props.recurrence),
|
||||
recurrence_parent: toOptionalString(props.recurrence_parent),
|
||||
occurrence_date: toOptionalString(props.occurrence_date),
|
||||
occurrence_materialization: toOccurrenceMaterializationMode(
|
||||
props.occurrence_materialization
|
||||
),
|
||||
occurrence_next_trigger: toOccurrenceNextTrigger(props.occurrence_next_trigger),
|
||||
occurrence_template: toOptionalString(props.occurrence_template),
|
||||
occurrence_past_horizon: toOptionalString(props.occurrence_past_horizon),
|
||||
occurrence_future_horizon: toOptionalString(props.occurrence_future_horizon),
|
||||
dateCreated: toOptionalString(props.dateCreated),
|
||||
dateModified: toOptionalString(props.dateModified),
|
||||
timeEntries,
|
||||
|
|
@ -242,6 +273,33 @@ function createTaskInfoFromProperties(
|
|||
};
|
||||
}
|
||||
|
||||
function mergeCustomProperties(
|
||||
first: Record<string, unknown> | undefined,
|
||||
second: Record<string, unknown> | undefined
|
||||
): Record<string, unknown> | undefined {
|
||||
const merged = {
|
||||
...(first ?? {}),
|
||||
...(second ?? {}),
|
||||
};
|
||||
return Object.keys(merged).length > 0 ? merged : undefined;
|
||||
}
|
||||
|
||||
function enrichTaskInfoFromCache(taskInfo: TaskInfo, plugin?: TaskNotesPlugin): TaskInfo {
|
||||
const cachedTask = plugin?.cacheManager?.getCachedTaskInfoSync?.(taskInfo.path);
|
||||
if (!cachedTask) {
|
||||
return taskInfo;
|
||||
}
|
||||
|
||||
return {
|
||||
...cachedTask,
|
||||
basesData: taskInfo.basesData,
|
||||
customProperties: mergeCustomProperties(
|
||||
cachedTask.customProperties,
|
||||
taskInfo.customProperties
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
export function createTaskInfoFromBasesData(
|
||||
basesItem: BasesDataItem,
|
||||
plugin?: TaskNotesPlugin
|
||||
|
|
@ -267,16 +325,16 @@ export function createTaskInfoFromBasesData(
|
|||
});
|
||||
|
||||
// Merge file properties with existing custom properties
|
||||
return {
|
||||
return enrichTaskInfoFromCache({
|
||||
...taskInfo,
|
||||
customProperties: {
|
||||
...mappedTaskInfo.customProperties,
|
||||
...taskInfo.customProperties,
|
||||
...fileProperties,
|
||||
},
|
||||
};
|
||||
}, plugin);
|
||||
} else {
|
||||
return createTaskInfoFromProperties(props, basesItem, plugin);
|
||||
return enrichTaskInfoFromCache(createTaskInfoFromProperties(props, basesItem, plugin), plugin);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
import { createTaskInfoFromBasesData } from "../../../src/bases/helpers";
|
||||
import {
|
||||
createTaskInfoFromBasesData,
|
||||
identifyTaskNotesFromBasesData,
|
||||
} from "../../../src/bases/helpers";
|
||||
import type TaskNotesPlugin from "../../../src/main";
|
||||
import { FieldMapper } from "../../../src/services/FieldMapper";
|
||||
import { DEFAULT_FIELD_MAPPING } from "../../../src/settings/defaults";
|
||||
|
|
@ -16,6 +19,9 @@ function createPlugin(sortOrderField = "sort_order") {
|
|||
storeTitleInFilename: false,
|
||||
},
|
||||
dependencyCache: undefined,
|
||||
cacheManager: {
|
||||
getCachedTaskInfoSync: jest.fn(),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -37,4 +43,69 @@ describe("Bases TaskInfo assembly", () => {
|
|||
expect(task?.sortOrder).toBe("0|hzzzzz:");
|
||||
expect(task?.customProperties?.sortOrder).toBeUndefined();
|
||||
});
|
||||
|
||||
it("preserves materialized occurrence fields on TaskInfo", () => {
|
||||
const task = createTaskInfoFromBasesData(
|
||||
{
|
||||
path: "Tasks/Occurrence.md",
|
||||
name: "Occurrence",
|
||||
properties: {
|
||||
title: "Occurrence",
|
||||
status: "open",
|
||||
scheduled: "2026-06-05T10:30",
|
||||
recurrence_parent: "[[Tasks/Parent]]",
|
||||
occurrence_date: "2026-06-05",
|
||||
},
|
||||
},
|
||||
createPlugin() as unknown as TaskNotesPlugin
|
||||
);
|
||||
|
||||
expect(task?.recurrence_parent).toBe("[[Tasks/Parent]]");
|
||||
expect(task?.occurrence_date).toBe("2026-06-05");
|
||||
expect(task?.customProperties?.recurrence_parent).toBeUndefined();
|
||||
expect(task?.customProperties?.occurrence_date).toBeUndefined();
|
||||
});
|
||||
|
||||
it("enriches hidden materialized occurrence fields from the TaskNotes cache", async () => {
|
||||
const plugin = createPlugin() as unknown as TaskNotesPlugin;
|
||||
(plugin.cacheManager.getCachedTaskInfoSync as jest.Mock).mockReturnValue({
|
||||
title: "Occurrence",
|
||||
status: "open",
|
||||
priority: "normal",
|
||||
path: "Tasks/Occurrence.md",
|
||||
archived: false,
|
||||
scheduled: "2026-06-05T10:30",
|
||||
recurrence_parent: "[[Tasks/Parent]]",
|
||||
occurrence_date: "2026-06-05",
|
||||
customProperties: {
|
||||
cachedOnly: true,
|
||||
},
|
||||
});
|
||||
|
||||
const tasks = await identifyTaskNotesFromBasesData(
|
||||
[
|
||||
{
|
||||
path: "Tasks/Occurrence.md",
|
||||
name: "Occurrence",
|
||||
properties: {
|
||||
title: "Occurrence",
|
||||
status: "open",
|
||||
scheduled: "2026-06-05T10:30",
|
||||
"file.path": "Tasks/Occurrence.md",
|
||||
},
|
||||
basesData: { source: "bases-entry" },
|
||||
},
|
||||
],
|
||||
plugin
|
||||
);
|
||||
|
||||
expect(tasks).toHaveLength(1);
|
||||
expect(tasks[0].recurrence_parent).toBe("[[Tasks/Parent]]");
|
||||
expect(tasks[0].occurrence_date).toBe("2026-06-05");
|
||||
expect(tasks[0].basesData).toEqual({ source: "bases-entry" });
|
||||
expect(tasks[0].customProperties).toMatchObject({
|
||||
cachedOnly: true,
|
||||
"file.path": "Tasks/Occurrence.md",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue