mirror of
https://github.com/taskgenius/taskgenius-plugin.git
synced 2026-07-22 06:40:25 +00:00
fix(metadata): add reindexing prompts for inheritance settings and improve augmentor sync
- Add confirmation dialogs when file metadata inheritance settings change - Sync inheritance settings to Augmentor on initialization and updates - Fix formatting consistency across multiple modules - Ensure immediate task index rebuild when inheritance behavior changes
This commit is contained in:
parent
ff2d304157
commit
6c51119b36
8 changed files with 782 additions and 686 deletions
|
|
@ -164,17 +164,17 @@ export function renderIndexSettingsTab(
|
|||
});
|
||||
|
||||
const exampleFormats = [
|
||||
{ format: "yyyy-MM-dd", example: "2025-08-16" },
|
||||
{ format: "dd/MM/yyyy", example: "16/08/2025" },
|
||||
{ format: "MM-dd-yyyy", example: "08-16-2025" },
|
||||
{ format: "yyyy.MM.dd", example: "2025.08.16" },
|
||||
{ format: "yyyyMMdd", example: "20250816" },
|
||||
{ format: "yyyyMMdd_HHmmss", example: "20250816_144403" },
|
||||
{ format: "yyyyMMddHHmmss", example: "20250816144403" },
|
||||
{ format: "yyyy-MM-dd'T'HH:mm", example: "2025-08-16T14:44" },
|
||||
{ format: "dd MMM yyyy", example: "16 Aug 2025" },
|
||||
{ format: "MMM dd, yyyy", example: "Aug 16, 2025" },
|
||||
{ format: "yyyy年MM月dd日", example: "2025年08月16日" },
|
||||
{format: "yyyy-MM-dd", example: "2025-08-16"},
|
||||
{format: "dd/MM/yyyy", example: "16/08/2025"},
|
||||
{format: "MM-dd-yyyy", example: "08-16-2025"},
|
||||
{format: "yyyy.MM.dd", example: "2025.08.16"},
|
||||
{format: "yyyyMMdd", example: "20250816"},
|
||||
{format: "yyyyMMdd_HHmmss", example: "20250816_144403"},
|
||||
{format: "yyyyMMddHHmmss", example: "20250816144403"},
|
||||
{format: "yyyy-MM-dd'T'HH:mm", example: "2025-08-16T14:44"},
|
||||
{format: "dd MMM yyyy", example: "16 Aug 2025"},
|
||||
{format: "MMM dd, yyyy", example: "Aug 16, 2025"},
|
||||
{format: "yyyy年MM月dd日", example: "2025年08月16日"},
|
||||
];
|
||||
|
||||
const table = examplesContainer.createEl("table", {
|
||||
|
|
@ -182,13 +182,13 @@ export function renderIndexSettingsTab(
|
|||
});
|
||||
|
||||
const headerRow = table.createEl("tr");
|
||||
headerRow.createEl("th", { text: t("Format Pattern") });
|
||||
headerRow.createEl("th", { text: t("Example") });
|
||||
headerRow.createEl("th", {text: t("Format Pattern")});
|
||||
headerRow.createEl("th", {text: t("Example")});
|
||||
|
||||
exampleFormats.forEach(({ format, example }) => {
|
||||
exampleFormats.forEach(({format, example}) => {
|
||||
const row = table.createEl("tr");
|
||||
row.createEl("td", { text: format });
|
||||
row.createEl("td", { text: example });
|
||||
row.createEl("td", {text: format});
|
||||
row.createEl("td", {text: example});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -202,23 +202,23 @@ export function renderIndexSettingsTab(
|
|||
.setDesc(
|
||||
isDataviewFormat
|
||||
? t(
|
||||
"Customize the prefix used for project tags in dataview format (e.g., 'project' for [project:: myproject]). Changes require reindexing."
|
||||
)
|
||||
"Customize the prefix used for project tags in dataview format (e.g., 'project' for [project:: myproject]). Changes require reindexing."
|
||||
)
|
||||
: t(
|
||||
"Customize the prefix used for project tags (e.g., 'project' for #project/myproject). Changes require reindexing."
|
||||
)
|
||||
"Customize the prefix used for project tags (e.g., 'project' for #project/myproject). Changes require reindexing."
|
||||
)
|
||||
)
|
||||
.addText((text) => {
|
||||
text.setPlaceholder("project")
|
||||
.setValue(
|
||||
settingTab.plugin.settings.projectTagPrefix[
|
||||
settingTab.plugin.settings.preferMetadataFormat
|
||||
]
|
||||
]
|
||||
)
|
||||
.onChange(async (value) => {
|
||||
settingTab.plugin.settings.projectTagPrefix[
|
||||
settingTab.plugin.settings.preferMetadataFormat
|
||||
] = value || "project";
|
||||
] = value || "project";
|
||||
settingTab.applySettingsUpdate();
|
||||
});
|
||||
});
|
||||
|
|
@ -229,23 +229,23 @@ export function renderIndexSettingsTab(
|
|||
.setDesc(
|
||||
isDataviewFormat
|
||||
? t(
|
||||
"Customize the prefix used for context tags in dataview format (e.g., 'context' for [context:: home]). Changes require reindexing."
|
||||
)
|
||||
"Customize the prefix used for context tags in dataview format (e.g., 'context' for [context:: home]). Changes require reindexing."
|
||||
)
|
||||
: t(
|
||||
"Customize the prefix used for context tags (e.g., '@home' for @home). Changes require reindexing."
|
||||
)
|
||||
"Customize the prefix used for context tags (e.g., '@home' for @home). Changes require reindexing."
|
||||
)
|
||||
)
|
||||
.addText((text) => {
|
||||
text.setPlaceholder("context")
|
||||
.setValue(
|
||||
settingTab.plugin.settings.contextTagPrefix[
|
||||
settingTab.plugin.settings.preferMetadataFormat
|
||||
]
|
||||
]
|
||||
)
|
||||
.onChange(async (value) => {
|
||||
settingTab.plugin.settings.contextTagPrefix[
|
||||
settingTab.plugin.settings.preferMetadataFormat
|
||||
] = value || (isDataviewFormat ? "context" : "@");
|
||||
] = value || (isDataviewFormat ? "context" : "@");
|
||||
settingTab.applySettingsUpdate();
|
||||
});
|
||||
});
|
||||
|
|
@ -464,10 +464,27 @@ export function renderIndexSettingsTab(
|
|||
settingTab.plugin.settings.fileMetadataInheritance.enabled
|
||||
)
|
||||
.onChange(async (value) => {
|
||||
settingTab.plugin.settings.fileMetadataInheritance.enabled =
|
||||
value;
|
||||
settingTab.plugin.settings.fileMetadataInheritance.enabled = value;
|
||||
settingTab.applySettingsUpdate();
|
||||
|
||||
new ConfirmModal(settingTab.plugin, {
|
||||
title: t("Reindex"),
|
||||
message: t("This change affects how tasks inherit metadata from files. Rebuild the index now so changes take effect immediately?"),
|
||||
confirmText: t("Reindex"),
|
||||
cancelText: t("Cancel"),
|
||||
onConfirm: async (confirmed: boolean) => {
|
||||
if (!confirmed) return;
|
||||
try {
|
||||
new Notice(t("Clearing task cache and rebuilding index..."));
|
||||
await settingTab.plugin.dataflowOrchestrator?.onSettingsChange(["parser"]);
|
||||
new Notice(t("Task index completely rebuilt"));
|
||||
} catch (error) {
|
||||
console.error("Failed to reindex after inheritance setting change:", error);
|
||||
new Notice(t("Failed to reindex tasks"));
|
||||
}
|
||||
},
|
||||
}).open();
|
||||
|
||||
setTimeout(() => {
|
||||
settingTab.display();
|
||||
}, 200);
|
||||
|
|
@ -489,9 +506,26 @@ export function renderIndexSettingsTab(
|
|||
.inheritFromFrontmatter
|
||||
)
|
||||
.onChange(async (value) => {
|
||||
settingTab.plugin.settings.fileMetadataInheritance.inheritFromFrontmatter =
|
||||
value;
|
||||
settingTab.plugin.settings.fileMetadataInheritance.inheritFromFrontmatter = value;
|
||||
settingTab.applySettingsUpdate();
|
||||
|
||||
new ConfirmModal(settingTab.plugin, {
|
||||
title: t("Reindex"),
|
||||
message: t("This change affects how tasks inherit metadata from files. Rebuild the index now so changes take effect immediately?"),
|
||||
confirmText: t("Reindex"),
|
||||
cancelText: t("Cancel"),
|
||||
onConfirm: async (confirmed: boolean) => {
|
||||
if (!confirmed) return;
|
||||
try {
|
||||
new Notice(t("Clearing task cache and rebuilding index..."));
|
||||
await settingTab.plugin.dataflowOrchestrator?.onSettingsChange(["parser"]);
|
||||
new Notice(t("Task index completely rebuilt"));
|
||||
} catch (error) {
|
||||
console.error("Failed to reindex after inheritance setting change:", error);
|
||||
new Notice(t("Failed to reindex tasks"));
|
||||
}
|
||||
},
|
||||
}).open();
|
||||
})
|
||||
);
|
||||
|
||||
|
|
@ -509,9 +543,26 @@ export function renderIndexSettingsTab(
|
|||
.inheritFromFrontmatterForSubtasks
|
||||
)
|
||||
.onChange(async (value) => {
|
||||
settingTab.plugin.settings.fileMetadataInheritance.inheritFromFrontmatterForSubtasks =
|
||||
value;
|
||||
settingTab.plugin.settings.fileMetadataInheritance.inheritFromFrontmatterForSubtasks = value;
|
||||
settingTab.applySettingsUpdate();
|
||||
|
||||
new ConfirmModal(settingTab.plugin, {
|
||||
title: t("Reindex"),
|
||||
message: t("This change affects how tasks inherit metadata from files. Rebuild the index now so changes take effect immediately?"),
|
||||
confirmText: t("Reindex"),
|
||||
cancelText: t("Cancel"),
|
||||
onConfirm: async (confirmed: boolean) => {
|
||||
if (!confirmed) return;
|
||||
try {
|
||||
new Notice(t("Clearing task cache and rebuilding index..."));
|
||||
await settingTab.plugin.dataflowOrchestrator?.onSettingsChange(["parser"]);
|
||||
new Notice(t("Task index completely rebuilt"));
|
||||
} catch (error) {
|
||||
console.error("Failed to reindex after inheritance setting change:", error);
|
||||
new Notice(t("Failed to reindex tasks"));
|
||||
}
|
||||
},
|
||||
}).open();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,7 +67,6 @@ export class DataflowOrchestrator {
|
|||
|
||||
constructor(
|
||||
private app: App,
|
||||
|
||||
private vault: Vault,
|
||||
private metadataCache: MetadataCache,
|
||||
private plugin: any, // Plugin instance for parser access
|
||||
|
|
@ -87,6 +86,13 @@ export class DataflowOrchestrator {
|
|||
vault,
|
||||
metadataCache,
|
||||
});
|
||||
// Initial sync of settings to Augmentor to ensure correct inheritance behavior on startup
|
||||
try {
|
||||
const initFmi = this.plugin?.settings?.fileMetadataInheritance;
|
||||
this.augmentor.updateSettings({fileMetadataInheritance: initFmi});
|
||||
} catch (e) {
|
||||
console.warn('[DataflowOrchestrator][init] Failed to sync settings to Augmentor', e);
|
||||
}
|
||||
this.storage = this.repository.getStorage();
|
||||
|
||||
// Initialize FileFilterManager from settings early so sources get it
|
||||
|
|
@ -145,9 +151,9 @@ export class DataflowOrchestrator {
|
|||
focusHeading: this.plugin.settings.focusHeading || "",
|
||||
fileParsingConfig: undefined,
|
||||
fileMetadataInheritance:
|
||||
this.plugin.settings.fileMetadataInheritance,
|
||||
this.plugin.settings.fileMetadataInheritance,
|
||||
enableCustomDateFormats:
|
||||
this.plugin.settings.enableCustomDateFormats,
|
||||
this.plugin.settings.enableCustomDateFormats,
|
||||
customDateFormats: this.plugin.settings.customDateFormats,
|
||||
// Include tag prefixes for custom dataview field support
|
||||
projectTagPrefix: this.plugin.settings.projectTagPrefix,
|
||||
|
|
@ -171,7 +177,7 @@ export class DataflowOrchestrator {
|
|||
this.workerOrchestrator = new WorkerOrchestrator(
|
||||
taskWorkerManager,
|
||||
projectWorkerManager,
|
||||
{ enableWorkerProcessing }
|
||||
{enableWorkerProcessing}
|
||||
);
|
||||
|
||||
// Initialize Obsidian event source
|
||||
|
|
@ -183,28 +189,28 @@ export class DataflowOrchestrator {
|
|||
// Initialize TimeParsingService with plugin settings
|
||||
this.timeParsingService = new TimeParsingService(
|
||||
this.plugin.settings?.timeParsing ||
|
||||
({
|
||||
enabled: true,
|
||||
supportedLanguages: ["en", "zh"],
|
||||
dateKeywords: {
|
||||
start: ["start", "begin", "from"],
|
||||
due: ["due", "deadline", "by", "until"],
|
||||
scheduled: ["scheduled", "on", "at"],
|
||||
},
|
||||
removeOriginalText: true,
|
||||
perLineProcessing: true,
|
||||
realTimeReplacement: true,
|
||||
timePatterns: {
|
||||
singleTime: [],
|
||||
timeRange: [],
|
||||
rangeSeparators: ["-", "~", "~"],
|
||||
},
|
||||
timeDefaults: {
|
||||
preferredFormat: "24h",
|
||||
defaultPeriod: "AM",
|
||||
midnightCrossing: "next-day",
|
||||
},
|
||||
} as EnhancedTimeParsingConfig)
|
||||
({
|
||||
enabled: true,
|
||||
supportedLanguages: ["en", "zh"],
|
||||
dateKeywords: {
|
||||
start: ["start", "begin", "from"],
|
||||
due: ["due", "deadline", "by", "until"],
|
||||
scheduled: ["scheduled", "on", "at"],
|
||||
},
|
||||
removeOriginalText: true,
|
||||
perLineProcessing: true,
|
||||
realTimeReplacement: true,
|
||||
timePatterns: {
|
||||
singleTime: [],
|
||||
timeRange: [],
|
||||
rangeSeparators: ["-", "~", "~"],
|
||||
},
|
||||
timeDefaults: {
|
||||
preferredFormat: "24h",
|
||||
defaultPeriod: "AM",
|
||||
midnightCrossing: "next-day",
|
||||
},
|
||||
} as EnhancedTimeParsingConfig)
|
||||
);
|
||||
|
||||
// Initialize FileSource (conditionally based on settings)
|
||||
|
|
@ -403,7 +409,7 @@ export class DataflowOrchestrator {
|
|||
// Listen for ICS events updates
|
||||
this.eventRefs.push(
|
||||
on(this.app, Events.ICS_EVENTS_UPDATED, async (payload: any) => {
|
||||
const { events, seq } = payload;
|
||||
const {events, seq} = payload;
|
||||
console.log(
|
||||
`[DataflowOrchestrator] ICS_EVENTS_UPDATED: ${
|
||||
events?.length || 0
|
||||
|
|
@ -420,7 +426,7 @@ export class DataflowOrchestrator {
|
|||
// Listen for file updates from ObsidianSource
|
||||
this.eventRefs.push(
|
||||
on(this.app, Events.FILE_UPDATED, async (payload: any) => {
|
||||
const { path, reason } = payload;
|
||||
const {path, reason} = payload;
|
||||
console.log(
|
||||
`[DataflowOrchestrator] FILE_UPDATED event: ${path} (${reason})`
|
||||
);
|
||||
|
|
@ -444,7 +450,7 @@ export class DataflowOrchestrator {
|
|||
// ObsidianSource uses FILE_UPDATED events instead
|
||||
this.eventRefs.push(
|
||||
on(this.app, Events.TASK_CACHE_UPDATED, async (payload: any) => {
|
||||
const { changedFiles, sourceSeq } = payload;
|
||||
const {changedFiles, sourceSeq} = payload;
|
||||
|
||||
// Skip if this is our own event (avoid infinite loop)
|
||||
// Check sourceSeq to identify origin from our own processing
|
||||
|
|
@ -484,7 +490,7 @@ export class DataflowOrchestrator {
|
|||
this.app,
|
||||
Events.WRITE_OPERATION_COMPLETE,
|
||||
async (payload: any) => {
|
||||
const { path, taskId } = payload;
|
||||
const {path, taskId} = payload;
|
||||
console.log(
|
||||
`[DataflowOrchestrator] WRITE_OPERATION_COMPLETE: ${path}, taskId: ${taskId}`
|
||||
);
|
||||
|
|
@ -509,7 +515,7 @@ export class DataflowOrchestrator {
|
|||
// Listen for direct task updates (from inline editing)
|
||||
this.eventRefs.push(
|
||||
on(this.app, Events.TASK_UPDATED, async (payload: any) => {
|
||||
const { task } = payload;
|
||||
const {task} = payload;
|
||||
if (task) {
|
||||
console.log(
|
||||
`[DataflowOrchestrator] TASK_UPDATED: ${task.id} in ${task.filePath}`
|
||||
|
|
@ -523,7 +529,7 @@ export class DataflowOrchestrator {
|
|||
// Listen for task deletion events
|
||||
this.eventRefs.push(
|
||||
on(this.app, Events.TASK_DELETED, async (payload: any) => {
|
||||
const { taskId, filePath, deletedTaskIds, mode } = payload;
|
||||
const {taskId, filePath, deletedTaskIds, mode} = payload;
|
||||
console.log(
|
||||
`[DataflowOrchestrator] TASK_DELETED: ${taskId} in ${filePath}, mode: ${mode}, deleted: ${
|
||||
deletedTaskIds?.length || 1
|
||||
|
|
@ -551,7 +557,7 @@ export class DataflowOrchestrator {
|
|||
if (this.fileSource) {
|
||||
this.eventRefs.push(
|
||||
on(this.app, Events.FILE_TASK_UPDATED, async (payload: any) => {
|
||||
const { task } = payload;
|
||||
const {task} = payload;
|
||||
console.log(
|
||||
`[DataflowOrchestrator] FILE_TASK_UPDATED: ${task?.filePath}`
|
||||
);
|
||||
|
|
@ -564,7 +570,7 @@ export class DataflowOrchestrator {
|
|||
|
||||
this.eventRefs.push(
|
||||
on(this.app, Events.FILE_TASK_REMOVED, async (payload: any) => {
|
||||
const { filePath } = payload;
|
||||
const {filePath} = payload;
|
||||
console.log(
|
||||
`[DataflowOrchestrator] FILE_TASK_REMOVED: ${filePath}`
|
||||
);
|
||||
|
|
@ -643,13 +649,13 @@ export class DataflowOrchestrator {
|
|||
// Apply inline filter even when using cached augmented tasks
|
||||
const includeInlineCached = this.fileFilterManager
|
||||
? this.fileFilterManager.shouldIncludePath(
|
||||
filePath,
|
||||
"inline"
|
||||
)
|
||||
filePath,
|
||||
"inline"
|
||||
)
|
||||
: true;
|
||||
console.log(
|
||||
"[DataflowOrchestrator] Inline filter decision (cached augmented)",
|
||||
{ filePath, includeInline: includeInlineCached }
|
||||
{filePath, includeInline: includeInlineCached}
|
||||
);
|
||||
if (!includeInlineCached) {
|
||||
augmentedTasks = [];
|
||||
|
|
@ -677,13 +683,13 @@ export class DataflowOrchestrator {
|
|||
);
|
||||
const includeInlineReaugment = this.fileFilterManager
|
||||
? this.fileFilterManager.shouldIncludePath(
|
||||
filePath,
|
||||
"inline"
|
||||
)
|
||||
filePath,
|
||||
"inline"
|
||||
)
|
||||
: true;
|
||||
console.log(
|
||||
"[DataflowOrchestrator] Inline filter decision (re-augment cached raw)",
|
||||
{ filePath, includeInline: includeInlineReaugment }
|
||||
{filePath, includeInline: includeInlineReaugment}
|
||||
);
|
||||
rawTasks = includeInlineReaugment ? rawCached.data : [];
|
||||
projectData = await this.projectResolver.get(filePath);
|
||||
|
|
@ -706,27 +712,27 @@ export class DataflowOrchestrator {
|
|||
try {
|
||||
const taskWorkerManager = this.workerOrchestrator[
|
||||
"taskWorkerManager"
|
||||
] as TaskWorkerManager | undefined;
|
||||
] as TaskWorkerManager | undefined;
|
||||
if (taskWorkerManager) {
|
||||
taskWorkerManager.updateSettings({
|
||||
preferMetadataFormat:
|
||||
this.plugin.settings.preferMetadataFormat ||
|
||||
"tasks",
|
||||
customDateFormats:
|
||||
this.plugin.settings.customDateFormats,
|
||||
this.plugin.settings.customDateFormats,
|
||||
fileMetadataInheritance:
|
||||
this.plugin.settings
|
||||
.fileMetadataInheritance,
|
||||
this.plugin.settings
|
||||
.fileMetadataInheritance,
|
||||
ignoreHeading:
|
||||
this.plugin.settings.ignoreHeading,
|
||||
this.plugin.settings.ignoreHeading,
|
||||
focusHeading: this.plugin.settings.focusHeading,
|
||||
// Include tag prefixes for custom dataview field support
|
||||
projectTagPrefix:
|
||||
this.plugin.settings.projectTagPrefix,
|
||||
this.plugin.settings.projectTagPrefix,
|
||||
contextTagPrefix:
|
||||
this.plugin.settings.contextTagPrefix,
|
||||
this.plugin.settings.contextTagPrefix,
|
||||
areaTagPrefix:
|
||||
this.plugin.settings.areaTagPrefix,
|
||||
this.plugin.settings.areaTagPrefix,
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
|
|
@ -739,13 +745,13 @@ export class DataflowOrchestrator {
|
|||
// Apply inline filter for parse path
|
||||
const includeInlineParse = this.fileFilterManager
|
||||
? this.fileFilterManager.shouldIncludePath(
|
||||
filePath,
|
||||
"inline"
|
||||
)
|
||||
filePath,
|
||||
"inline"
|
||||
)
|
||||
: true;
|
||||
console.log(
|
||||
"[DataflowOrchestrator] Inline filter decision (parse path)",
|
||||
{ filePath, includeInline: includeInlineParse }
|
||||
{filePath, includeInline: includeInlineParse}
|
||||
);
|
||||
if (includeInlineParse) {
|
||||
// Parse the file using workers (single-file path)
|
||||
|
|
@ -861,6 +867,16 @@ export class DataflowOrchestrator {
|
|||
this.timeParsingService.updateConfig(settings.timeParsing);
|
||||
}
|
||||
|
||||
// Sync inheritance toggle to augmentor so it can respect disabling file frontmatter inheritance
|
||||
try {
|
||||
console.debug('[DataflowOrchestrator][updateSettings] fileMetadataInheritance =', settings.fileMetadataInheritance);
|
||||
this.augmentor.updateSettings({
|
||||
fileMetadataInheritance: settings.fileMetadataInheritance,
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn("[DataflowOrchestrator] Failed to sync settings to Augmentor", e);
|
||||
}
|
||||
|
||||
// Update FileSource if needed
|
||||
if (settings?.fileSource?.enabled && !this.fileSource) {
|
||||
// Initialize FileSource if enabled but not yet created
|
||||
|
|
@ -910,7 +926,7 @@ export class DataflowOrchestrator {
|
|||
try {
|
||||
const taskWorkerManager = this.workerOrchestrator?.[
|
||||
"taskWorkerManager"
|
||||
] as
|
||||
] as
|
||||
| import("./workers/TaskWorkerManager").TaskWorkerManager
|
||||
| undefined;
|
||||
if (taskWorkerManager) {
|
||||
|
|
@ -954,7 +970,7 @@ export class DataflowOrchestrator {
|
|||
const newEnabled: boolean = Boolean(settings?.fileFilter?.enabled);
|
||||
const rulesCount = Array.isArray(settings?.fileFilter?.rules)
|
||||
? settings.fileFilter.rules.filter((r: any) => r?.enabled)
|
||||
.length
|
||||
.length
|
||||
: 0;
|
||||
console.log("[TG Index Filter] settingsChange", {
|
||||
enabled: newEnabled,
|
||||
|
|
@ -1091,7 +1107,7 @@ export class DataflowOrchestrator {
|
|||
);
|
||||
console.log(
|
||||
"[DataflowOrchestrator] restoreByFilter fallback candidates",
|
||||
{ extra: inlineCandidates.length }
|
||||
{extra: inlineCandidates.length}
|
||||
);
|
||||
} catch (e) {
|
||||
console.warn(
|
||||
|
|
@ -1124,7 +1140,7 @@ export class DataflowOrchestrator {
|
|||
path,
|
||||
augmented.data,
|
||||
undefined,
|
||||
{ forceEmit: true }
|
||||
{forceEmit: true}
|
||||
);
|
||||
restoredFromAugmented++;
|
||||
this.suppressedInline.delete(path);
|
||||
|
|
@ -1183,7 +1199,7 @@ export class DataflowOrchestrator {
|
|||
|
||||
console.warn(
|
||||
"[DataflowOrchestrator] restore inline failed",
|
||||
{ path, e }
|
||||
{path, e}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1219,7 +1235,7 @@ export class DataflowOrchestrator {
|
|||
} catch (e) {
|
||||
console.warn(
|
||||
"[DataflowOrchestrator] restore file-task emit failed",
|
||||
{ path, e }
|
||||
{path, e}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1243,7 +1259,7 @@ export class DataflowOrchestrator {
|
|||
*/
|
||||
getWorkerStatus(): { enabled: boolean; metrics?: any } {
|
||||
if (!this.workerOrchestrator) {
|
||||
return { enabled: false };
|
||||
return {enabled: false};
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
@ -1342,16 +1358,16 @@ export class DataflowOrchestrator {
|
|||
// Configure worker manager with plugin settings
|
||||
const taskWorkerManager = this.workerOrchestrator[
|
||||
"taskWorkerManager"
|
||||
] as TaskWorkerManager;
|
||||
] as TaskWorkerManager;
|
||||
if (taskWorkerManager) {
|
||||
taskWorkerManager.updateSettings({
|
||||
preferMetadataFormat:
|
||||
this.plugin.settings.preferMetadataFormat ||
|
||||
"tasks",
|
||||
customDateFormats:
|
||||
this.plugin.settings.customDateFormats,
|
||||
this.plugin.settings.customDateFormats,
|
||||
fileMetadataInheritance:
|
||||
this.plugin.settings.fileMetadataInheritance,
|
||||
this.plugin.settings.fileMetadataInheritance,
|
||||
projectConfig: this.plugin.settings.projectConfig,
|
||||
ignoreHeading: this.plugin.settings.ignoreHeading,
|
||||
focusHeading: this.plugin.settings.focusHeading,
|
||||
|
|
@ -1418,9 +1434,9 @@ export class DataflowOrchestrator {
|
|||
projectName: projectData?.tgProject?.name,
|
||||
projectMeta: projectData
|
||||
? {
|
||||
...(projectData.enhancedMetadata || {}),
|
||||
tgProject: projectData.tgProject, // Include tgProject in projectMeta
|
||||
}
|
||||
...(projectData.enhancedMetadata || {}),
|
||||
tgProject: projectData.tgProject, // Include tgProject in projectMeta
|
||||
}
|
||||
: {},
|
||||
tasks: rawTasks,
|
||||
};
|
||||
|
|
@ -1558,13 +1574,13 @@ export class DataflowOrchestrator {
|
|||
// Apply file filter scope: skip inline parsing when scope === 'file'
|
||||
const includeInline = this.fileFilterManager
|
||||
? this.fileFilterManager.shouldIncludePath(
|
||||
filePath,
|
||||
"inline"
|
||||
)
|
||||
filePath,
|
||||
"inline"
|
||||
)
|
||||
: true;
|
||||
console.log(
|
||||
"[DataflowOrchestrator] Inline filter decision",
|
||||
{ filePath, includeInline }
|
||||
{filePath, includeInline}
|
||||
);
|
||||
const rawTasks = includeInline
|
||||
? await this.parseFile(file, projectData.tgProject)
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ export class WriteAPI {
|
|||
try {
|
||||
const task = await Promise.resolve(this.getTaskById(args.taskId));
|
||||
if (!task) {
|
||||
return { success: false, error: "Task not found" };
|
||||
return {success: false, error: "Task not found"};
|
||||
}
|
||||
|
||||
// Check if this is a Canvas task
|
||||
|
|
@ -119,14 +119,14 @@ export class WriteAPI {
|
|||
task.filePath
|
||||
) as TFile;
|
||||
if (!file) {
|
||||
return { success: false, error: "File not found" };
|
||||
return {success: false, error: "File not found"};
|
||||
}
|
||||
|
||||
const content = await this.vault.read(file);
|
||||
const lines = content.split("\n");
|
||||
|
||||
if (task.line < 0 || task.line >= lines.length) {
|
||||
return { success: false, error: "Invalid line number" };
|
||||
return {success: false, error: "Invalid line number"};
|
||||
}
|
||||
|
||||
let taskLine = lines[task.line];
|
||||
|
|
@ -172,17 +172,17 @@ export class WriteAPI {
|
|||
|
||||
// Trigger task-completed event if task was just completed
|
||||
if (args.completed === true && !task.completed) {
|
||||
const updatedTask = { ...task, completed: true };
|
||||
const updatedTask = {...task, completed: true};
|
||||
this.app.workspace.trigger(
|
||||
"task-genius:task-completed",
|
||||
updatedTask
|
||||
);
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
return {success: true};
|
||||
} catch (error) {
|
||||
console.error("WriteAPI: Error updating task status:", error);
|
||||
return { success: false, error: String(error) };
|
||||
return {success: false, error: String(error)};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -197,7 +197,7 @@ export class WriteAPI {
|
|||
this.getTaskById(args.taskId)
|
||||
);
|
||||
if (!originalTask) {
|
||||
return { success: false, error: "Task not found" };
|
||||
return {success: false, error: "Task not found"};
|
||||
}
|
||||
|
||||
// Check if this is a Canvas task
|
||||
|
|
@ -221,17 +221,17 @@ export class WriteAPI {
|
|||
originalTask.filePath
|
||||
) as TFile;
|
||||
if (!file) {
|
||||
return { success: false, error: "File not found" };
|
||||
return {success: false, error: "File not found"};
|
||||
}
|
||||
|
||||
const content = await this.vault.read(file);
|
||||
const lines = content.split("\n");
|
||||
|
||||
if (originalTask.line < 0 || originalTask.line >= lines.length) {
|
||||
return { success: false, error: "Invalid line number" };
|
||||
return {success: false, error: "Invalid line number"};
|
||||
}
|
||||
|
||||
const updatedTask = { ...originalTask, ...args.updates };
|
||||
const updatedTask = {...originalTask, ...args.updates};
|
||||
let taskLine = lines[originalTask.line];
|
||||
|
||||
// Update checkbox status or status mark
|
||||
|
|
@ -306,7 +306,7 @@ export class WriteAPI {
|
|||
};
|
||||
|
||||
// Emit task updated event for direct update in dataflow
|
||||
emit(this.app, Events.TASK_UPDATED, { task: updatedTaskObj });
|
||||
emit(this.app, Events.TASK_UPDATED, {task: updatedTaskObj});
|
||||
|
||||
// Trigger task-completed event if task was just completed
|
||||
if (args.updates.completed === true && !originalTask.completed) {
|
||||
|
|
@ -322,10 +322,10 @@ export class WriteAPI {
|
|||
taskId: args.taskId,
|
||||
});
|
||||
|
||||
return { success: true, task: updatedTaskObj };
|
||||
return {success: true, task: updatedTaskObj};
|
||||
} catch (error) {
|
||||
console.error("WriteAPI: Error updating task:", error);
|
||||
return { success: false, error: String(error) };
|
||||
return {success: false, error: String(error)};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -342,7 +342,7 @@ export class WriteAPI {
|
|||
originalTask.filePath
|
||||
) as TFile;
|
||||
if (!file) {
|
||||
return { success: false, error: "File not found" };
|
||||
return {success: false, error: "File not found"};
|
||||
}
|
||||
|
||||
let newFilePath = originalTask.filePath;
|
||||
|
|
@ -354,14 +354,6 @@ export class WriteAPI {
|
|||
this.plugin.settings?.fileSource?.fileTaskProperties as any
|
||||
)?.customContentField as string | undefined;
|
||||
|
||||
console.log("[WriteAPI][FileSource] updateFileSourceTask start", {
|
||||
taskId,
|
||||
contentSource,
|
||||
preferFrontmatterTitle,
|
||||
customContentField,
|
||||
updates,
|
||||
});
|
||||
|
||||
// Apply frontmatter updates for non-content fields (status, completed, metadata)
|
||||
try {
|
||||
const md = (updates.metadata ?? {}) as any;
|
||||
|
|
@ -410,10 +402,6 @@ export class WriteAPI {
|
|||
fsMapping.symbolToMetadata?.[statusToWrite];
|
||||
if (mapped) {
|
||||
statusToWrite = mapped;
|
||||
console.log(
|
||||
"[WriteAPI][FileSource] mapped via symbolToMetadata",
|
||||
{ mapped }
|
||||
);
|
||||
} else {
|
||||
console.log(
|
||||
"[WriteAPI][FileSource] fallback mapping from taskStatuses",
|
||||
|
|
@ -456,10 +444,6 @@ export class WriteAPI {
|
|||
typeToMetadata[entry.type];
|
||||
if (md) {
|
||||
statusToWrite = md;
|
||||
console.log(
|
||||
"[WriteAPI][FileSource] mapped via taskStatuses",
|
||||
{ type: entry.type, md }
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
@ -483,7 +467,7 @@ export class WriteAPI {
|
|||
(fm as any).priority = md.priority;
|
||||
console.log(
|
||||
"[WriteAPI][FileSource] wrote fm.priority",
|
||||
{ priority: md.priority }
|
||||
{priority: md.priority}
|
||||
);
|
||||
}
|
||||
if (
|
||||
|
|
@ -494,23 +478,14 @@ export class WriteAPI {
|
|||
(fm as any).tags = Array.isArray(md.tags)
|
||||
? md.tags
|
||||
: typeof md.tags === "string"
|
||||
? [md.tags]
|
||||
: md.tags;
|
||||
console.log("[WriteAPI][FileSource] wrote fm.tags", {
|
||||
tags: (fm as any).tags,
|
||||
});
|
||||
? [md.tags]
|
||||
: md.tags;
|
||||
}
|
||||
if (md.project !== undefined) {
|
||||
(fm as any).project = md.project;
|
||||
console.log("[WriteAPI][FileSource] wrote fm.project", {
|
||||
project: md.project,
|
||||
});
|
||||
}
|
||||
if (md.context !== undefined) {
|
||||
(fm as any).context = md.context;
|
||||
console.log("[WriteAPI][FileSource] wrote fm.context", {
|
||||
context: md.context,
|
||||
});
|
||||
}
|
||||
if (md.area !== undefined) {
|
||||
(fm as any).area = md.area;
|
||||
|
|
@ -528,7 +503,7 @@ export class WriteAPI {
|
|||
(fm as any).startDate = formatDate(md.startDate);
|
||||
console.log(
|
||||
"[WriteAPI。][FileSource] wrote fm.startDate",
|
||||
{ startDate: (fm as any).startDate }
|
||||
{startDate: (fm as any).startDate}
|
||||
);
|
||||
}
|
||||
if (md.scheduledDate !== undefined) {
|
||||
|
|
@ -537,7 +512,7 @@ export class WriteAPI {
|
|||
);
|
||||
console.log(
|
||||
"[WriteAPI][FileSource] wrote fm.scheduledDate",
|
||||
{ scheduledDate: (fm as any).scheduledDate }
|
||||
{scheduledDate: (fm as any).scheduledDate}
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
@ -553,7 +528,7 @@ export class WriteAPI {
|
|||
"WriteAPI: Error updating file-source task frontmatter:",
|
||||
error
|
||||
);
|
||||
return { success: false, error: String(error) };
|
||||
return {success: false, error: String(error)};
|
||||
}
|
||||
|
||||
// Handle content/title change
|
||||
|
|
@ -587,13 +562,13 @@ export class WriteAPI {
|
|||
);
|
||||
console.log(
|
||||
"[WriteAPI][FileSource] wrote fm.title (branch: title)",
|
||||
{ title: updates.content }
|
||||
{title: updates.content}
|
||||
);
|
||||
const cacheAfter =
|
||||
this.app.metadataCache.getFileCache(file);
|
||||
console.log(
|
||||
"[WriteAPI][FileSource] cache fm.title after write (branch: title)",
|
||||
{ title: cacheAfter?.frontmatter?.title }
|
||||
{title: cacheAfter?.frontmatter?.title}
|
||||
);
|
||||
} else {
|
||||
newFilePath = await this.renameFile(
|
||||
|
|
@ -602,7 +577,7 @@ export class WriteAPI {
|
|||
);
|
||||
console.log(
|
||||
"[WriteAPI][FileSource] renamed file (branch: title)",
|
||||
{ newFilePath }
|
||||
{newFilePath}
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
|
@ -635,7 +610,7 @@ export class WriteAPI {
|
|||
field: customContentField,
|
||||
value: cacheAfter?.frontmatter?.[
|
||||
customContentField
|
||||
],
|
||||
],
|
||||
}
|
||||
);
|
||||
} else if (preferFrontmatterTitle) {
|
||||
|
|
@ -647,13 +622,13 @@ export class WriteAPI {
|
|||
);
|
||||
console.log(
|
||||
"[WriteAPI][FileSource] wrote fm.title (branch: custom fallback)",
|
||||
{ title: updates.content }
|
||||
{title: updates.content}
|
||||
);
|
||||
const cacheAfter2 =
|
||||
this.app.metadataCache.getFileCache(file);
|
||||
console.log(
|
||||
"[WriteAPI][FileSource] cache fm.title after write (branch: custom fallback)",
|
||||
{ title: cacheAfter2?.frontmatter?.title }
|
||||
{title: cacheAfter2?.frontmatter?.title}
|
||||
);
|
||||
} else {
|
||||
newFilePath = await this.renameFile(
|
||||
|
|
@ -662,7 +637,7 @@ export class WriteAPI {
|
|||
);
|
||||
console.log(
|
||||
"[WriteAPI][FileSource] renamed file (branch: custom fallback)",
|
||||
{ newFilePath }
|
||||
{newFilePath}
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
|
@ -678,13 +653,13 @@ export class WriteAPI {
|
|||
);
|
||||
console.log(
|
||||
"[WriteAPI][FileSource] wrote fm.title (branch: filename/default)",
|
||||
{ title: updates.content }
|
||||
{title: updates.content}
|
||||
);
|
||||
const cacheAfter =
|
||||
this.app.metadataCache.getFileCache(file);
|
||||
console.log(
|
||||
"[WriteAPI][FileSource] cache fm.title after write (branch: filename/default)",
|
||||
{ title: cacheAfter?.frontmatter?.title }
|
||||
{title: cacheAfter?.frontmatter?.title}
|
||||
);
|
||||
} else {
|
||||
newFilePath = await this.renameFile(
|
||||
|
|
@ -693,7 +668,7 @@ export class WriteAPI {
|
|||
);
|
||||
console.log(
|
||||
"[WriteAPI][FileSource] renamed file (branch: filename/default)",
|
||||
{ newFilePath }
|
||||
{newFilePath}
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
|
@ -710,7 +685,7 @@ export class WriteAPI {
|
|||
"WriteAPI: Error updating file-source task content:",
|
||||
error
|
||||
);
|
||||
return { success: false, error: String(error) };
|
||||
return {success: false, error: String(error)};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -731,9 +706,9 @@ export class WriteAPI {
|
|||
};
|
||||
|
||||
// Emit file-task update so repository updates fileTasks map directly
|
||||
emit(this.app, Events.FILE_TASK_UPDATED, { task: updatedTaskObj });
|
||||
emit(this.app, Events.FILE_TASK_UPDATED, {task: updatedTaskObj});
|
||||
|
||||
return { success: true, task: updatedTaskObj };
|
||||
return {success: true, task: updatedTaskObj};
|
||||
}
|
||||
|
||||
private async updateH1Heading(
|
||||
|
|
@ -865,10 +840,10 @@ export class WriteAPI {
|
|||
});
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
return {success: true};
|
||||
} catch (error) {
|
||||
console.error("WriteAPI: Error creating task:", error);
|
||||
return { success: false, error: String(error) };
|
||||
return {success: false, error: String(error)};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -909,7 +884,7 @@ export class WriteAPI {
|
|||
try {
|
||||
const task = await Promise.resolve(this.getTaskById(args.taskId));
|
||||
if (!task) {
|
||||
return { success: false, error: "Task not found" };
|
||||
return {success: false, error: "Task not found"};
|
||||
}
|
||||
|
||||
// Check if this is a Canvas task
|
||||
|
|
@ -921,7 +896,7 @@ export class WriteAPI {
|
|||
task.filePath
|
||||
) as TFile;
|
||||
if (!file) {
|
||||
return { success: false, error: "File not found" };
|
||||
return {success: false, error: "File not found"};
|
||||
}
|
||||
|
||||
// Collect all tasks to delete
|
||||
|
|
@ -981,10 +956,10 @@ export class WriteAPI {
|
|||
mode: args.deleteChildren ? "subtree" : "single",
|
||||
});
|
||||
|
||||
return { success: true };
|
||||
return {success: true};
|
||||
} catch (error) {
|
||||
console.error("WriteAPI: Error deleting task:", error);
|
||||
return { success: false, error: String(error) };
|
||||
return {success: false, error: String(error)};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1019,7 +994,7 @@ export class WriteAPI {
|
|||
}
|
||||
}
|
||||
|
||||
return { updated, failed };
|
||||
return {updated, failed};
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1063,7 +1038,7 @@ export class WriteAPI {
|
|||
}
|
||||
}
|
||||
|
||||
return { updated, failed };
|
||||
return {updated, failed};
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1084,7 +1059,7 @@ export class WriteAPI {
|
|||
);
|
||||
const result = await this.updateTask({
|
||||
taskId,
|
||||
updates: { content: newContent },
|
||||
updates: {content: newContent},
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
|
|
@ -1097,7 +1072,7 @@ export class WriteAPI {
|
|||
}
|
||||
}
|
||||
|
||||
return { tasks: updatedTasks };
|
||||
return {tasks: updatedTasks};
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1110,7 +1085,7 @@ export class WriteAPI {
|
|||
this.getTaskById(args.parentTaskId)
|
||||
);
|
||||
if (!parentTask) {
|
||||
return { tasks: [] };
|
||||
return {tasks: []};
|
||||
}
|
||||
|
||||
const createdTasks: Task[] = [];
|
||||
|
|
@ -1127,7 +1102,7 @@ export class WriteAPI {
|
|||
}
|
||||
}
|
||||
|
||||
return { tasks: createdTasks };
|
||||
return {tasks: createdTasks};
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1253,18 +1228,18 @@ export class WriteAPI {
|
|||
}
|
||||
|
||||
// Notify about write operation
|
||||
emit(this.app, Events.WRITE_OPERATION_START, { path: file.path });
|
||||
emit(this.app, Events.WRITE_OPERATION_START, {path: file.path});
|
||||
await this.vault.modify(file, newContent);
|
||||
emit(this.app, Events.WRITE_OPERATION_COMPLETE, {
|
||||
path: file.path,
|
||||
});
|
||||
return { success: true };
|
||||
return {success: true};
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"WriteAPI: Error creating task in daily note:",
|
||||
error
|
||||
);
|
||||
return { success: false, error: String(error) };
|
||||
return {success: false, error: String(error)};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1337,13 +1312,13 @@ export class WriteAPI {
|
|||
dailyNoteSettings: qc.dailyNoteSettings,
|
||||
});
|
||||
|
||||
return { filePath, success: true };
|
||||
return {filePath, success: true};
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"WriteAPI: Error adding project task to quick capture:",
|
||||
error
|
||||
);
|
||||
return { filePath: "", success: false };
|
||||
return {filePath: "", success: false};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1539,7 +1514,7 @@ export class WriteAPI {
|
|||
): { line: number } | null {
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
if (lines[i].includes(taskId)) {
|
||||
return { line: i };
|
||||
return {line: i};
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
|
@ -1602,12 +1577,12 @@ export class WriteAPI {
|
|||
this.getTaskById(args.taskId)
|
||||
);
|
||||
if (!originalTask) {
|
||||
return { success: false, error: "Task not found" };
|
||||
return {success: false, error: "Task not found"};
|
||||
}
|
||||
|
||||
// Ensure it's a Canvas task
|
||||
if (!CanvasTaskUpdater.isCanvasTask(originalTask)) {
|
||||
return { success: false, error: "Task is not a Canvas task" };
|
||||
return {success: false, error: "Task is not a Canvas task"};
|
||||
}
|
||||
|
||||
// Create updated task object (deep-merge metadata to preserve unchanged fields)
|
||||
|
|
@ -1628,7 +1603,7 @@ export class WriteAPI {
|
|||
|
||||
if (result.success) {
|
||||
// Emit task updated event for dataflow
|
||||
emit(this.app, Events.TASK_UPDATED, { task: updatedTask });
|
||||
emit(this.app, Events.TASK_UPDATED, {task: updatedTask});
|
||||
|
||||
// Trigger task-completed event if task was just completed
|
||||
if (
|
||||
|
|
@ -1641,13 +1616,13 @@ export class WriteAPI {
|
|||
);
|
||||
}
|
||||
|
||||
return { success: true, task: updatedTask };
|
||||
return {success: true, task: updatedTask};
|
||||
} else {
|
||||
return { success: false, error: result.error };
|
||||
return {success: false, error: result.error};
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("WriteAPI: Error updating Canvas task:", error);
|
||||
return { success: false, error: String(error) };
|
||||
return {success: false, error: String(error)};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1660,12 +1635,12 @@ export class WriteAPI {
|
|||
try {
|
||||
const task = await Promise.resolve(this.getTaskById(args.taskId));
|
||||
if (!task) {
|
||||
return { success: false, error: "Task not found" };
|
||||
return {success: false, error: "Task not found"};
|
||||
}
|
||||
|
||||
// Ensure it's a Canvas task
|
||||
if (!CanvasTaskUpdater.isCanvasTask(task)) {
|
||||
return { success: false, error: "Task is not a Canvas task" };
|
||||
return {success: false, error: "Task is not a Canvas task"};
|
||||
}
|
||||
|
||||
// Collect all tasks to delete
|
||||
|
|
@ -1698,7 +1673,7 @@ export class WriteAPI {
|
|||
return result;
|
||||
} catch (error) {
|
||||
console.error("WriteAPI: Error deleting Canvas task:", error);
|
||||
return { success: false, error: String(error) };
|
||||
return {success: false, error: String(error)};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1714,12 +1689,12 @@ export class WriteAPI {
|
|||
try {
|
||||
const task = await Promise.resolve(this.getTaskById(args.taskId));
|
||||
if (!task) {
|
||||
return { success: false, error: "Task not found" };
|
||||
return {success: false, error: "Task not found"};
|
||||
}
|
||||
|
||||
// Ensure it's a Canvas task
|
||||
if (!CanvasTaskUpdater.isCanvasTask(task)) {
|
||||
return { success: false, error: "Task is not a Canvas task" };
|
||||
return {success: false, error: "Task is not a Canvas task"};
|
||||
}
|
||||
|
||||
// Use CanvasTaskUpdater to move the task
|
||||
|
|
@ -1733,7 +1708,7 @@ export class WriteAPI {
|
|||
return result;
|
||||
} catch (error) {
|
||||
console.error("WriteAPI: Error moving Canvas task:", error);
|
||||
return { success: false, error: String(error) };
|
||||
return {success: false, error: String(error)};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1750,12 +1725,12 @@ export class WriteAPI {
|
|||
try {
|
||||
const task = await Promise.resolve(this.getTaskById(args.taskId));
|
||||
if (!task) {
|
||||
return { success: false, error: "Task not found" };
|
||||
return {success: false, error: "Task not found"};
|
||||
}
|
||||
|
||||
// Ensure it's a Canvas task
|
||||
if (!CanvasTaskUpdater.isCanvasTask(task)) {
|
||||
return { success: false, error: "Task is not a Canvas task" };
|
||||
return {success: false, error: "Task is not a Canvas task"};
|
||||
}
|
||||
|
||||
// Use CanvasTaskUpdater to duplicate the task
|
||||
|
|
@ -1770,7 +1745,7 @@ export class WriteAPI {
|
|||
return result;
|
||||
} catch (error) {
|
||||
console.error("WriteAPI: Error duplicating Canvas task:", error);
|
||||
return { success: false, error: String(error) };
|
||||
return {success: false, error: String(error)};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1809,7 +1784,7 @@ export class WriteAPI {
|
|||
return result;
|
||||
} catch (error) {
|
||||
console.error("WriteAPI: Error adding task to Canvas node:", error);
|
||||
return { success: false, error: String(error) };
|
||||
return {success: false, error: String(error)};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,494 +3,545 @@ import { DateInheritanceAugmentor } from "./DateInheritanceAugmentor";
|
|||
import { App, Vault, MetadataCache } from "obsidian";
|
||||
|
||||
export interface AugmentContext {
|
||||
filePath: string;
|
||||
fileMeta?: Record<string, any>;
|
||||
projectName?: string;
|
||||
projectMeta?: Record<string, any>;
|
||||
tasks: Task[];
|
||||
filePath: string;
|
||||
fileMeta?: Record<string, any>;
|
||||
projectName?: string;
|
||||
projectMeta?: Record<string, any>;
|
||||
tasks: Task[];
|
||||
}
|
||||
|
||||
export interface FileContext {
|
||||
filePath: string;
|
||||
fileMeta?: Record<string, any>;
|
||||
project?: { name?: string; data?: Record<string, any> } | null;
|
||||
filePath: string;
|
||||
fileMeta?: Record<string, any>;
|
||||
project?: { name?: string; data?: Record<string, any> } | null;
|
||||
}
|
||||
|
||||
export interface InheritanceStrategy {
|
||||
// Priority order: task > file > project > default
|
||||
scalarPriority: ("task" | "file" | "project" | "default")[];
|
||||
// For arrays: merge and deduplicate with stable ordering
|
||||
arrayMergeStrategy: "task-first" | "file-first" | "project-first";
|
||||
// Special handling for specific fields
|
||||
statusCompletionSource: "task-only" | "allow-inheritance";
|
||||
recurrenceSource: "task-explicit" | "allow-inheritance";
|
||||
// Per-key inheritance control for subtasks
|
||||
subtaskInheritance: Record<string, boolean>;
|
||||
// Priority order: task > file > project > default
|
||||
scalarPriority: ("task" | "file" | "project" | "default")[];
|
||||
// For arrays: merge and deduplicate with stable ordering
|
||||
arrayMergeStrategy: "task-first" | "file-first" | "project-first";
|
||||
// Special handling for specific fields
|
||||
statusCompletionSource: "task-only" | "allow-inheritance";
|
||||
recurrenceSource: "task-explicit" | "allow-inheritance";
|
||||
// Per-key inheritance control for subtasks
|
||||
subtaskInheritance: Record<string, boolean>;
|
||||
}
|
||||
|
||||
/**
|
||||
* TaskAugmentor - Complete inheritance and augmentation implementation
|
||||
*
|
||||
*
|
||||
* Implements the full inheritance strategy as specified in the refactor plan:
|
||||
* - Scalar fields: task explicit > file > project > default
|
||||
* - Arrays: merge and deduplicate (preserving stable order)
|
||||
* - Status/completion: only from task level
|
||||
* - Recurrence: task explicit priority
|
||||
* - Recurrence: task explicit priority
|
||||
* - Subtask inheritance: per-key control based on configuration
|
||||
*/
|
||||
export class Augmentor {
|
||||
private strategy: InheritanceStrategy;
|
||||
private dateInheritanceAugmentor?: DateInheritanceAugmentor;
|
||||
private strategy: InheritanceStrategy;
|
||||
private dateInheritanceAugmentor?: DateInheritanceAugmentor;
|
||||
// Respect plugin setting: file frontmatter inheritance toggle
|
||||
private fileFrontmatterInheritanceEnabled: boolean = true;
|
||||
|
||||
constructor(options?: {
|
||||
inherit?: Record<string, "task" | "file" | "project" | "merge-array">;
|
||||
strategy?: Partial<InheritanceStrategy>;
|
||||
app?: App;
|
||||
vault?: Vault;
|
||||
metadataCache?: MetadataCache;
|
||||
}) {
|
||||
// Default strategy based on refactor plan requirements
|
||||
this.strategy = {
|
||||
scalarPriority: ["task", "file", "project", "default"],
|
||||
arrayMergeStrategy: "task-first",
|
||||
statusCompletionSource: "task-only",
|
||||
recurrenceSource: "task-explicit",
|
||||
subtaskInheritance: {
|
||||
// Default: most fields inherit, sensitive fields don't
|
||||
tags: true,
|
||||
project: true,
|
||||
priority: true,
|
||||
dueDate: false,
|
||||
startDate: false,
|
||||
scheduledDate: false,
|
||||
completed: false,
|
||||
status: false,
|
||||
recurrence: false,
|
||||
onCompletion: false
|
||||
},
|
||||
...options?.strategy
|
||||
};
|
||||
constructor(options?: {
|
||||
inherit?: Record<string, "task" | "file" | "project" | "merge-array">;
|
||||
strategy?: Partial<InheritanceStrategy>;
|
||||
app?: App;
|
||||
vault?: Vault;
|
||||
metadataCache?: MetadataCache;
|
||||
}) {
|
||||
// Default strategy based on refactor plan requirements
|
||||
this.strategy = {
|
||||
scalarPriority: ["task", "file", "project", "default"],
|
||||
arrayMergeStrategy: "task-first",
|
||||
statusCompletionSource: "task-only",
|
||||
recurrenceSource: "task-explicit",
|
||||
subtaskInheritance: {
|
||||
// Default: most fields inherit, sensitive fields don't
|
||||
tags: true,
|
||||
project: true,
|
||||
priority: true,
|
||||
dueDate: false,
|
||||
startDate: false,
|
||||
scheduledDate: false,
|
||||
completed: false,
|
||||
status: false,
|
||||
recurrence: false,
|
||||
onCompletion: false
|
||||
},
|
||||
...options?.strategy
|
||||
};
|
||||
|
||||
// Initialize date inheritance augmentor if Obsidian context is available
|
||||
if (options?.app && options?.vault && options?.metadataCache) {
|
||||
this.dateInheritanceAugmentor = new DateInheritanceAugmentor(
|
||||
options.app,
|
||||
options.vault,
|
||||
options.metadataCache
|
||||
);
|
||||
}
|
||||
}
|
||||
// Initialize date inheritance augmentor if Obsidian context is available
|
||||
if (options?.app && options?.vault && options?.metadataCache) {
|
||||
this.dateInheritanceAugmentor = new DateInheritanceAugmentor(
|
||||
options.app,
|
||||
options.vault,
|
||||
options.metadataCache
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Main merge method with enhanced context support
|
||||
*/
|
||||
async merge(ctx: AugmentContext): Promise<Task[]> {
|
||||
// First apply standard augmentation
|
||||
let augmentedTasks = ctx.tasks.map(task => this.augmentTask(task, ctx));
|
||||
/**
|
||||
* Update settings from plugin to control inheritance behavior
|
||||
*/
|
||||
public updateSettings(settings: Partial<{
|
||||
fileMetadataInheritance?: { enabled?: boolean; inheritFromFrontmatter?: boolean }
|
||||
}>): void {
|
||||
const f = settings.fileMetadataInheritance;
|
||||
if (f) {
|
||||
this.fileFrontmatterInheritanceEnabled = !!(f.enabled && f.inheritFromFrontmatter);
|
||||
}
|
||||
}
|
||||
|
||||
// Then apply date inheritance for time-only expressions if available
|
||||
if (this.dateInheritanceAugmentor) {
|
||||
try {
|
||||
augmentedTasks = await this.dateInheritanceAugmentor.augmentTasksWithDateInheritance(
|
||||
augmentedTasks,
|
||||
ctx.filePath
|
||||
);
|
||||
} catch (error) {
|
||||
console.warn('[Augmentor] Date inheritance augmentation failed:', error);
|
||||
// Continue with standard augmentation if date inheritance fails
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Main merge method with enhanced context support
|
||||
*/
|
||||
async merge(ctx: AugmentContext): Promise<Task[]> {
|
||||
// First apply standard augmentation
|
||||
let augmentedTasks = ctx.tasks.map(task => this.augmentTask(task, ctx));
|
||||
|
||||
return augmentedTasks;
|
||||
}
|
||||
// Then apply date inheritance for time-only expressions if available
|
||||
if (this.dateInheritanceAugmentor) {
|
||||
try {
|
||||
augmentedTasks = await this.dateInheritanceAugmentor.augmentTasksWithDateInheritance(
|
||||
augmentedTasks,
|
||||
ctx.filePath
|
||||
);
|
||||
} catch (error) {
|
||||
console.warn('[Augmentor] Date inheritance augmentation failed:', error);
|
||||
// Continue with standard augmentation if date inheritance fails
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Legacy merge method for backward compatibility
|
||||
*/
|
||||
mergeCompat(ctx: FileContext, tasks: Task[]): Task[] {
|
||||
const augmentCtx: AugmentContext = {
|
||||
filePath: ctx.filePath,
|
||||
fileMeta: ctx.fileMeta,
|
||||
projectName: ctx.project?.name,
|
||||
projectMeta: ctx.project?.data,
|
||||
tasks
|
||||
};
|
||||
|
||||
return tasks.map(task => this.augmentTask(task, augmentCtx));
|
||||
}
|
||||
return augmentedTasks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Augment a single task with file and project metadata
|
||||
*/
|
||||
private augmentTask(task: Task, ctx: AugmentContext): Task {
|
||||
const originalMetadata = task.metadata || {};
|
||||
const enhancedMetadata = { ...originalMetadata };
|
||||
/**
|
||||
* Legacy merge method for backward compatibility
|
||||
*/
|
||||
mergeCompat(ctx: FileContext, tasks: Task[]): Task[] {
|
||||
const augmentCtx: AugmentContext = {
|
||||
filePath: ctx.filePath,
|
||||
fileMeta: ctx.fileMeta,
|
||||
projectName: ctx.project?.name,
|
||||
projectMeta: ctx.project?.data,
|
||||
tasks
|
||||
};
|
||||
|
||||
// Special handling for priority: check both task.priority and metadata.priority
|
||||
// Priority might be at task root level (from parser) or in metadata
|
||||
// IMPORTANT: Once priority is set, it should NOT be overridden by inheritance
|
||||
|
||||
// Debug logging for priority processing
|
||||
const debug = process.env.NODE_ENV === 'development';
|
||||
if (debug && (enhancedMetadata.priority !== undefined || (task as any).priority !== undefined)) {
|
||||
console.log('[Augmentor] Priority processing:', {
|
||||
metadataPriority: enhancedMetadata.priority,
|
||||
taskPriority: (task as any).priority,
|
||||
filePath: ctx.filePath
|
||||
});
|
||||
}
|
||||
|
||||
// First, ensure we have the priority from task-level if it exists
|
||||
if ((enhancedMetadata.priority === undefined || enhancedMetadata.priority === null) &&
|
||||
(task as any).priority !== undefined && (task as any).priority !== null) {
|
||||
enhancedMetadata.priority = (task as any).priority;
|
||||
}
|
||||
|
||||
// Ensure priority is properly converted to numeric format if it exists
|
||||
// Clean up null values to undefined for consistency
|
||||
if (enhancedMetadata.priority === null) {
|
||||
enhancedMetadata.priority = undefined;
|
||||
} else if (enhancedMetadata.priority !== undefined) {
|
||||
const originalPriority = enhancedMetadata.priority;
|
||||
enhancedMetadata.priority = this.convertPriorityValue(enhancedMetadata.priority);
|
||||
|
||||
if (debug) {
|
||||
console.log('[Augmentor] Priority conversion:', {
|
||||
original: originalPriority,
|
||||
converted: enhancedMetadata.priority,
|
||||
filePath: ctx.filePath
|
||||
});
|
||||
}
|
||||
}
|
||||
return tasks.map(task => this.augmentTask(task, augmentCtx));
|
||||
}
|
||||
|
||||
// Apply inheritance for each metadata field
|
||||
this.applyScalarInheritance(enhancedMetadata, ctx);
|
||||
this.applyArrayInheritance(enhancedMetadata, ctx);
|
||||
this.applySpecialFieldRules(enhancedMetadata, ctx);
|
||||
this.applyProjectReference(enhancedMetadata, ctx);
|
||||
/**
|
||||
* Augment a single task with file and project metadata
|
||||
*/
|
||||
private augmentTask(task: Task, ctx: AugmentContext): Task {
|
||||
const originalMetadata = task.metadata || {};
|
||||
const enhancedMetadata = {...originalMetadata};
|
||||
|
||||
// Handle subtask inheritance if this is a parent task
|
||||
if (originalMetadata.children && Array.isArray(originalMetadata.children)) {
|
||||
this.applySubtaskInheritance(task, enhancedMetadata, ctx);
|
||||
}
|
||||
// Special handling for priority: check both task.priority and metadata.priority
|
||||
// Priority might be at task root level (from parser) or in metadata
|
||||
// IMPORTANT: Once priority is set, it should NOT be overridden by inheritance
|
||||
|
||||
return {
|
||||
...task,
|
||||
metadata: enhancedMetadata
|
||||
} as Task;
|
||||
}
|
||||
// Debug logging for priority processing
|
||||
const debug = process.env.NODE_ENV === 'development';
|
||||
if (debug && (enhancedMetadata.priority !== undefined || (task as any).priority !== undefined)) {
|
||||
console.log('[Augmentor] Priority processing:', {
|
||||
metadataPriority: enhancedMetadata.priority,
|
||||
taskPriority: (task as any).priority,
|
||||
filePath: ctx.filePath
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply scalar field inheritance: task > file > project > default
|
||||
*/
|
||||
private applyScalarInheritance(metadata: Record<string, any>, ctx: AugmentContext): void {
|
||||
const scalarFields = [
|
||||
'priority', 'context', 'area', 'estimatedTime', 'actualTime',
|
||||
'useAsDateType', 'heading'
|
||||
];
|
||||
// First, ensure we have the priority from task-level if it exists
|
||||
if ((enhancedMetadata.priority === undefined || enhancedMetadata.priority === null) &&
|
||||
(task as any).priority !== undefined && (task as any).priority !== null) {
|
||||
enhancedMetadata.priority = (task as any).priority;
|
||||
}
|
||||
|
||||
for (const field of scalarFields) {
|
||||
// Skip if task already has explicit value
|
||||
if (metadata[field] !== undefined && metadata[field] !== null) {
|
||||
continue;
|
||||
}
|
||||
// Ensure priority is properly converted to numeric format if it exists
|
||||
// Clean up null values to undefined for consistency
|
||||
if (enhancedMetadata.priority === null) {
|
||||
enhancedMetadata.priority = undefined;
|
||||
} else if (enhancedMetadata.priority !== undefined) {
|
||||
const originalPriority = enhancedMetadata.priority;
|
||||
enhancedMetadata.priority = this.convertPriorityValue(enhancedMetadata.priority);
|
||||
|
||||
// Special handling for priority - NEVER apply default value
|
||||
// Priority should only come from task itself, file, or project
|
||||
if (field === 'priority') {
|
||||
// Only check file and project sources, skip default
|
||||
for (const source of ['file', 'project']) {
|
||||
let value: any;
|
||||
|
||||
if (source === 'file') {
|
||||
value = ctx.fileMeta?.[field];
|
||||
} else if (source === 'project') {
|
||||
value = ctx.projectMeta?.[field];
|
||||
}
|
||||
if (debug) {
|
||||
console.log('[Augmentor] Priority conversion:', {
|
||||
original: originalPriority,
|
||||
converted: enhancedMetadata.priority,
|
||||
filePath: ctx.filePath
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (value !== undefined && value !== null) {
|
||||
// Convert priority value to numeric format
|
||||
metadata[field] = this.convertPriorityValue(value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// If no priority found, leave it undefined (don't set default)
|
||||
continue;
|
||||
}
|
||||
// Apply inheritance for each metadata field
|
||||
this.applyScalarInheritance(enhancedMetadata, ctx);
|
||||
this.applyArrayInheritance(enhancedMetadata, ctx);
|
||||
this.applySpecialFieldRules(enhancedMetadata, ctx);
|
||||
this.applyProjectReference(enhancedMetadata, ctx);
|
||||
|
||||
// Apply inheritance priority for other fields: file > project > default
|
||||
for (const source of this.strategy.scalarPriority.slice(1)) { // Skip 'task' since we checked above
|
||||
let value: any;
|
||||
|
||||
switch (source) {
|
||||
case 'file':
|
||||
value = ctx.fileMeta?.[field];
|
||||
break;
|
||||
case 'project':
|
||||
value = ctx.projectMeta?.[field];
|
||||
break;
|
||||
case 'default':
|
||||
value = this.getDefaultValue(field);
|
||||
break;
|
||||
}
|
||||
// Handle subtask inheritance if this is a parent task
|
||||
if (originalMetadata.children && Array.isArray(originalMetadata.children)) {
|
||||
this.applySubtaskInheritance(task, enhancedMetadata, ctx);
|
||||
}
|
||||
|
||||
if (value !== undefined && value !== null) {
|
||||
metadata[field] = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
...task,
|
||||
metadata: enhancedMetadata
|
||||
} as Task;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply array field inheritance with merge and deduplication
|
||||
*/
|
||||
private applyArrayInheritance(metadata: Record<string, any>, ctx: AugmentContext): void {
|
||||
const arrayFields = ['tags', 'dependsOn'];
|
||||
/**
|
||||
* Apply scalar field inheritance: task > file > project > default
|
||||
*/
|
||||
private applyScalarInheritance(metadata: Record<string, any>, ctx: AugmentContext): void {
|
||||
const scalarFields = [
|
||||
'priority', 'context', 'area', 'estimatedTime', 'actualTime',
|
||||
'useAsDateType', 'heading'
|
||||
];
|
||||
|
||||
for (const field of arrayFields) {
|
||||
const taskArray = Array.isArray(metadata[field]) ? metadata[field] : [];
|
||||
const fileArray = ctx.fileMeta && Array.isArray((ctx.fileMeta as any)[field]) ? (ctx.fileMeta as any)[field] : [];
|
||||
const projectArray = ctx.projectMeta && Array.isArray((ctx.projectMeta as any)[field]) ? (ctx.projectMeta as any)[field] : [];
|
||||
for (const field of scalarFields) {
|
||||
// Skip if task already has explicit value
|
||||
if (metadata[field] !== undefined && metadata[field] !== null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mergedArray: any[];
|
||||
// Special handling for priority - NEVER apply default value
|
||||
// Priority should only come from task itself, file, or project
|
||||
if (field === 'priority') {
|
||||
// Only check file and project sources, skip default
|
||||
for (const source of ['file', 'project']) {
|
||||
let value: any;
|
||||
|
||||
// Merge based on strategy
|
||||
switch (this.strategy.arrayMergeStrategy) {
|
||||
case 'task-first':
|
||||
mergedArray = [...taskArray, ...fileArray, ...projectArray];
|
||||
break;
|
||||
case 'file-first':
|
||||
mergedArray = [...fileArray, ...taskArray, ...projectArray];
|
||||
break;
|
||||
case 'project-first':
|
||||
mergedArray = [...projectArray, ...taskArray, ...fileArray];
|
||||
break;
|
||||
default:
|
||||
mergedArray = [...taskArray, ...fileArray, ...projectArray];
|
||||
}
|
||||
if (source === 'file') {
|
||||
value = ctx.fileMeta?.[field];
|
||||
} else if (source === 'project') {
|
||||
value = ctx.projectMeta?.[field];
|
||||
}
|
||||
|
||||
// Deduplicate while preserving order
|
||||
metadata[field] = Array.from(new Set(mergedArray));
|
||||
}
|
||||
}
|
||||
if (value !== undefined && value !== null) {
|
||||
// Convert priority value to numeric format
|
||||
metadata[field] = this.convertPriorityValue(value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// If no priority found, leave it undefined (don't set default)
|
||||
continue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply special field rules for status/completion and recurrence
|
||||
*/
|
||||
private applySpecialFieldRules(metadata: Record<string, any>, ctx: AugmentContext): void {
|
||||
// Status and completion: only from task level (never inherit)
|
||||
if (this.strategy.statusCompletionSource === 'task-only') {
|
||||
// These fields should only come from the task itself, never inherit
|
||||
// (No action needed as we don't override existing task values)
|
||||
}
|
||||
// Apply inheritance priority for other fields: file > project > default
|
||||
for (const source of this.strategy.scalarPriority.slice(1)) { // Skip 'task' since we checked above
|
||||
let value: any;
|
||||
|
||||
// Recurrence: task explicit priority
|
||||
if (this.strategy.recurrenceSource === 'task-explicit') {
|
||||
// Only use recurrence if explicitly set on task
|
||||
if (!metadata.recurrence) {
|
||||
// Don't inherit recurrence from file or project
|
||||
delete metadata.recurrence;
|
||||
}
|
||||
}
|
||||
switch (source) {
|
||||
case 'file':
|
||||
if (!this.fileFrontmatterInheritanceEnabled) {
|
||||
continue; // Skip applying file-level values when inheritance is disabled
|
||||
}
|
||||
value = ctx.fileMeta?.[field];
|
||||
break;
|
||||
case 'project':
|
||||
value = ctx.projectMeta?.[field];
|
||||
break;
|
||||
case 'default':
|
||||
value = this.getDefaultValue(field);
|
||||
break;
|
||||
}
|
||||
|
||||
// Date fields: inherit only if not already set
|
||||
const dateFields = ['dueDate', 'startDate', 'scheduledDate', 'createdDate'];
|
||||
for (const field of dateFields) {
|
||||
if (metadata[field] === undefined || metadata[field] === null) {
|
||||
// Try file first, then project
|
||||
const fileValue = ctx.fileMeta?.[field];
|
||||
const projectValue = ctx.projectMeta?.[field];
|
||||
|
||||
if (fileValue !== undefined && fileValue !== null) {
|
||||
metadata[field] = fileValue;
|
||||
} else if (projectValue !== undefined && projectValue !== null) {
|
||||
metadata[field] = projectValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (value !== undefined && value !== null) {
|
||||
metadata[field] = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply TgProject reference
|
||||
*/
|
||||
private applyProjectReference(metadata: Record<string, any>, ctx: AugmentContext): void {
|
||||
// Set project name if not already set
|
||||
if (!metadata.project && ctx.projectName) {
|
||||
metadata.project = ctx.projectName;
|
||||
}
|
||||
/**
|
||||
* Apply array field inheritance with merge and deduplication
|
||||
*/
|
||||
private applyArrayInheritance(metadata: Record<string, any>, ctx: AugmentContext): void {
|
||||
const arrayFields = ['tags', 'dependsOn'];
|
||||
|
||||
// Set TgProject if project metadata is available
|
||||
// The tgProject should come from ProjectResolver which includes all necessary fields
|
||||
if (ctx.projectMeta) {
|
||||
// Check if we have a tgProject object in the project metadata
|
||||
if (ctx.projectMeta.tgProject) {
|
||||
metadata.tgProject = ctx.projectMeta.tgProject;
|
||||
} else if (ctx.projectName) {
|
||||
// Fallback: construct from available data
|
||||
metadata.tgProject = {
|
||||
type: ctx.projectMeta.type || 'metadata',
|
||||
name: ctx.projectName,
|
||||
source: ctx.projectMeta.source || ctx.projectMeta.configSource || 'unknown',
|
||||
readonly: ctx.projectMeta.readonly || false
|
||||
} as TgProject;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const field of arrayFields) {
|
||||
const taskArray = Array.isArray(metadata[field]) ? metadata[field] : [];
|
||||
const fileArrayRaw = ctx.fileMeta && Array.isArray((ctx.fileMeta as any)[field]) ? (ctx.fileMeta as any)[field] : [];
|
||||
const fileArray = this.fileFrontmatterInheritanceEnabled ? fileArrayRaw : [];
|
||||
const projectArray = ctx.projectMeta && Array.isArray((ctx.projectMeta as any)[field]) ? (ctx.projectMeta as any)[field] : [];
|
||||
|
||||
/**
|
||||
* Apply subtask inheritance based on per-key control
|
||||
*/
|
||||
private applySubtaskInheritance(parentTask: Task, parentMetadata: Record<string, any>, ctx: AugmentContext): void {
|
||||
// This would typically involve finding child tasks and applying inheritance
|
||||
// For now, we'll store the inheritance rules on the parent for child processing
|
||||
parentMetadata._subtaskInheritanceRules = this.strategy.subtaskInheritance;
|
||||
}
|
||||
// Normalize tags consistently (ensure leading #) before merging/dedup
|
||||
const normalizeIfTags = (arr: any[]) => {
|
||||
if (field !== 'tags') return arr;
|
||||
return arr
|
||||
.filter((t) => typeof t === 'string' && t.trim().length > 0)
|
||||
.map((t: string) => this.normalizeTag(t));
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert priority value to consistent numeric format
|
||||
*/
|
||||
private convertPriorityValue(value: any): number | undefined {
|
||||
if (value === undefined || value === null || value === "") {
|
||||
return undefined;
|
||||
}
|
||||
// If user disabled file frontmatter inheritance, do not inherit tags from file or project
|
||||
if (field === 'tags' && !this.fileFrontmatterInheritanceEnabled) {
|
||||
try {
|
||||
console.debug('[Augmentor][Tags] Inheritance disabled. Keeping only task tags.', {
|
||||
taskTags: normalizeIfTags(taskArray)
|
||||
});
|
||||
} catch {
|
||||
}
|
||||
metadata[field] = normalizeIfTags(taskArray);
|
||||
continue;
|
||||
}
|
||||
|
||||
// If it's already a number, return it
|
||||
if (typeof value === "number") {
|
||||
return value;
|
||||
}
|
||||
const taskArrNorm = normalizeIfTags(taskArray);
|
||||
const fileArrNorm = normalizeIfTags(fileArray);
|
||||
const projectArrNorm = normalizeIfTags(projectArray);
|
||||
|
||||
// If it's a string, try to convert
|
||||
const strValue = String(value);
|
||||
|
||||
// Priority mapping for text and emoji values
|
||||
const priorityMap: Record<string, number> = {
|
||||
// Text priorities
|
||||
highest: 5,
|
||||
high: 4,
|
||||
medium: 3,
|
||||
low: 2,
|
||||
lowest: 1,
|
||||
urgent: 5,
|
||||
critical: 5,
|
||||
important: 4,
|
||||
normal: 3,
|
||||
moderate: 3,
|
||||
minor: 2,
|
||||
trivial: 1,
|
||||
// Emoji priorities (Tasks plugin compatible)
|
||||
"🔺": 5,
|
||||
"⏫": 4,
|
||||
"🔼": 3,
|
||||
"🔽": 2,
|
||||
"⏬️": 1,
|
||||
"⏬": 1,
|
||||
};
|
||||
let mergedArray: any[];
|
||||
|
||||
// Try numeric conversion first
|
||||
const numericValue = parseInt(strValue, 10);
|
||||
if (!isNaN(numericValue) && numericValue >= 1 && numericValue <= 5) {
|
||||
return numericValue;
|
||||
}
|
||||
// Merge based on strategy
|
||||
switch (this.strategy.arrayMergeStrategy) {
|
||||
case 'task-first':
|
||||
mergedArray = [...taskArrNorm, ...fileArrNorm, ...projectArrNorm];
|
||||
break;
|
||||
case 'file-first':
|
||||
mergedArray = [...fileArrNorm, ...taskArrNorm, ...projectArrNorm];
|
||||
break;
|
||||
case 'project-first':
|
||||
mergedArray = [...projectArrNorm, ...taskArrNorm, ...fileArrNorm];
|
||||
break;
|
||||
default:
|
||||
mergedArray = [...taskArrNorm, ...fileArrNorm, ...projectArrNorm];
|
||||
}
|
||||
|
||||
// Try priority mapping (including emojis)
|
||||
const mappedPriority = priorityMap[strValue.toLowerCase()] || priorityMap[strValue];
|
||||
if (mappedPriority !== undefined) {
|
||||
return mappedPriority;
|
||||
}
|
||||
// Deduplicate while preserving order
|
||||
const deduped = Array.from(new Set(mergedArray));
|
||||
metadata[field] = deduped;
|
||||
}
|
||||
}
|
||||
|
||||
// If we can't convert, return undefined to avoid setting invalid values
|
||||
return undefined;
|
||||
}
|
||||
// Normalize a tag to include leading # and trim whitespace
|
||||
private normalizeTag(tag: any): string {
|
||||
if (typeof tag !== 'string') return tag;
|
||||
const trimmed = tag.trim();
|
||||
if (!trimmed) return trimmed;
|
||||
return trimmed.startsWith('#') ? trimmed : `#${trimmed}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default value for a field
|
||||
*/
|
||||
private getDefaultValue(field: string): any {
|
||||
const defaults: Record<string, any> = {
|
||||
// Don't set default priority for now - it should come from parser
|
||||
// If we need to add it back, we should check if task already has priority elsewhere
|
||||
tags: [],
|
||||
dependsOn: [],
|
||||
estimatedTime: undefined,
|
||||
actualTime: undefined,
|
||||
useAsDateType: 'due'
|
||||
};
|
||||
/**
|
||||
* Apply special field rules for status/completion and recurrence
|
||||
*/
|
||||
private applySpecialFieldRules(metadata: Record<string, any>, ctx: AugmentContext): void {
|
||||
// Status and completion: only from task level (never inherit)
|
||||
if (this.strategy.statusCompletionSource === 'task-only') {
|
||||
// These fields should only come from the task itself, never inherit
|
||||
// (No action needed as we don't override existing task values)
|
||||
}
|
||||
|
||||
return defaults[field];
|
||||
}
|
||||
// Recurrence: task explicit priority
|
||||
if (this.strategy.recurrenceSource === 'task-explicit') {
|
||||
// Only use recurrence if explicitly set on task
|
||||
if (!metadata.recurrence) {
|
||||
// Don't inherit recurrence from file or project
|
||||
delete metadata.recurrence;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update inheritance strategy
|
||||
*/
|
||||
updateStrategy(strategy: Partial<InheritanceStrategy>): void {
|
||||
this.strategy = { ...this.strategy, ...strategy };
|
||||
}
|
||||
// Date fields: inherit only if not already set
|
||||
const dateFields = ['dueDate', 'startDate', 'scheduledDate', 'createdDate'];
|
||||
for (const field of dateFields) {
|
||||
if (metadata[field] === undefined || metadata[field] === null) {
|
||||
// Try file first, then project
|
||||
const fileValue = ctx.fileMeta?.[field];
|
||||
const projectValue = ctx.projectMeta?.[field];
|
||||
|
||||
/**
|
||||
* Get current inheritance strategy
|
||||
*/
|
||||
getStrategy(): InheritanceStrategy {
|
||||
return { ...this.strategy };
|
||||
}
|
||||
if (fileValue !== undefined && fileValue !== null) {
|
||||
metadata[field] = fileValue;
|
||||
} else if (projectValue !== undefined && projectValue !== null) {
|
||||
metadata[field] = projectValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process inheritance for a specific field type
|
||||
*/
|
||||
processFieldInheritance(
|
||||
field: string,
|
||||
taskValue: any,
|
||||
fileValue: any,
|
||||
projectValue: any
|
||||
): any {
|
||||
// Handle arrays specially
|
||||
if (Array.isArray(taskValue) || Array.isArray(fileValue) || Array.isArray(projectValue)) {
|
||||
const taskArray = Array.isArray(taskValue) ? taskValue : [];
|
||||
const fileArray = Array.isArray(fileValue) ? fileValue : [];
|
||||
const projectArray = Array.isArray(projectValue) ? projectValue : [];
|
||||
|
||||
let merged: any[];
|
||||
switch (this.strategy.arrayMergeStrategy) {
|
||||
case 'task-first':
|
||||
merged = [...taskArray, ...fileArray, ...projectArray];
|
||||
break;
|
||||
case 'file-first':
|
||||
merged = [...fileArray, ...taskArray, ...projectArray];
|
||||
break;
|
||||
case 'project-first':
|
||||
merged = [...projectArray, ...taskArray, ...fileArray];
|
||||
break;
|
||||
default:
|
||||
merged = [...taskArray, ...fileArray, ...projectArray];
|
||||
}
|
||||
|
||||
return Array.from(new Set(merged));
|
||||
}
|
||||
/**
|
||||
* Apply TgProject reference
|
||||
*/
|
||||
private applyProjectReference(metadata: Record<string, any>, ctx: AugmentContext): void {
|
||||
// Set project name if not already set
|
||||
if (!metadata.project && ctx.projectName) {
|
||||
metadata.project = ctx.projectName;
|
||||
}
|
||||
|
||||
// Handle scalars with priority order
|
||||
for (const source of this.strategy.scalarPriority) {
|
||||
let value: any;
|
||||
switch (source) {
|
||||
case 'task':
|
||||
value = taskValue;
|
||||
break;
|
||||
case 'file':
|
||||
value = fileValue;
|
||||
break;
|
||||
case 'project':
|
||||
value = projectValue;
|
||||
break;
|
||||
case 'default':
|
||||
value = this.getDefaultValue(field);
|
||||
break;
|
||||
}
|
||||
// Set TgProject if project metadata is available
|
||||
// The tgProject should come from ProjectResolver which includes all necessary fields
|
||||
if (ctx.projectMeta) {
|
||||
// Check if we have a tgProject object in the project metadata
|
||||
if (ctx.projectMeta.tgProject) {
|
||||
metadata.tgProject = ctx.projectMeta.tgProject;
|
||||
} else if (ctx.projectName) {
|
||||
// Fallback: construct from available data
|
||||
metadata.tgProject = {
|
||||
type: ctx.projectMeta.type || 'metadata',
|
||||
name: ctx.projectName,
|
||||
source: ctx.projectMeta.source || ctx.projectMeta.configSource || 'unknown',
|
||||
readonly: ctx.projectMeta.readonly || false
|
||||
} as TgProject;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (value !== undefined && value !== null) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Apply subtask inheritance based on per-key control
|
||||
*/
|
||||
private applySubtaskInheritance(parentTask: Task, parentMetadata: Record<string, any>, ctx: AugmentContext): void {
|
||||
// This would typically involve finding child tasks and applying inheritance
|
||||
// For now, we'll store the inheritance rules on the parent for child processing
|
||||
parentMetadata._subtaskInheritanceRules = this.strategy.subtaskInheritance;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
/**
|
||||
* Convert priority value to consistent numeric format
|
||||
*/
|
||||
private convertPriorityValue(value: any): number | undefined {
|
||||
if (value === undefined || value === null || value === "") {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// If it's already a number, return it
|
||||
if (typeof value === "number") {
|
||||
return value;
|
||||
}
|
||||
|
||||
// If it's a string, try to convert
|
||||
const strValue = String(value);
|
||||
|
||||
// Priority mapping for text and emoji values
|
||||
const priorityMap: Record<string, number> = {
|
||||
// Text priorities
|
||||
highest: 5,
|
||||
high: 4,
|
||||
medium: 3,
|
||||
low: 2,
|
||||
lowest: 1,
|
||||
urgent: 5,
|
||||
critical: 5,
|
||||
important: 4,
|
||||
normal: 3,
|
||||
moderate: 3,
|
||||
minor: 2,
|
||||
trivial: 1,
|
||||
// Emoji priorities (Tasks plugin compatible)
|
||||
"🔺": 5,
|
||||
"⏫": 4,
|
||||
"🔼": 3,
|
||||
"🔽": 2,
|
||||
"⏬️": 1,
|
||||
"⏬": 1,
|
||||
};
|
||||
|
||||
// Try numeric conversion first
|
||||
const numericValue = parseInt(strValue, 10);
|
||||
if (!isNaN(numericValue) && numericValue >= 1 && numericValue <= 5) {
|
||||
return numericValue;
|
||||
}
|
||||
|
||||
// Try priority mapping (including emojis)
|
||||
const mappedPriority = priorityMap[strValue.toLowerCase()] || priorityMap[strValue];
|
||||
if (mappedPriority !== undefined) {
|
||||
return mappedPriority;
|
||||
}
|
||||
|
||||
// If we can't convert, return undefined to avoid setting invalid values
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default value for a field
|
||||
*/
|
||||
private getDefaultValue(field: string): any {
|
||||
const defaults: Record<string, any> = {
|
||||
// Don't set default priority for now - it should come from parser
|
||||
// If we need to add it back, we should check if task already has priority elsewhere
|
||||
tags: [],
|
||||
dependsOn: [],
|
||||
estimatedTime: undefined,
|
||||
actualTime: undefined,
|
||||
useAsDateType: 'due'
|
||||
};
|
||||
|
||||
return defaults[field];
|
||||
}
|
||||
|
||||
/**
|
||||
* Update inheritance strategy
|
||||
*/
|
||||
updateStrategy(strategy: Partial<InheritanceStrategy>): void {
|
||||
this.strategy = {...this.strategy, ...strategy};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current inheritance strategy
|
||||
*/
|
||||
getStrategy(): InheritanceStrategy {
|
||||
return {...this.strategy};
|
||||
}
|
||||
|
||||
/**
|
||||
* Process inheritance for a specific field type
|
||||
*/
|
||||
processFieldInheritance(
|
||||
field: string,
|
||||
taskValue: any,
|
||||
fileValue: any,
|
||||
projectValue: any
|
||||
): any {
|
||||
// Handle arrays specially
|
||||
if (Array.isArray(taskValue) || Array.isArray(fileValue) || Array.isArray(projectValue)) {
|
||||
const taskArray = Array.isArray(taskValue) ? taskValue : [];
|
||||
const fileArray = Array.isArray(fileValue) ? fileValue : [];
|
||||
const projectArray = Array.isArray(projectValue) ? projectValue : [];
|
||||
|
||||
let merged: any[];
|
||||
switch (this.strategy.arrayMergeStrategy) {
|
||||
case 'task-first':
|
||||
merged = [...taskArray, ...fileArray, ...projectArray];
|
||||
break;
|
||||
case 'file-first':
|
||||
merged = [...fileArray, ...taskArray, ...projectArray];
|
||||
break;
|
||||
case 'project-first':
|
||||
merged = [...projectArray, ...taskArray, ...fileArray];
|
||||
break;
|
||||
default:
|
||||
merged = [...taskArray, ...fileArray, ...projectArray];
|
||||
}
|
||||
|
||||
return Array.from(new Set(merged));
|
||||
}
|
||||
|
||||
// Handle scalars with priority order
|
||||
for (const source of this.strategy.scalarPriority) {
|
||||
let value: any;
|
||||
switch (source) {
|
||||
case 'task':
|
||||
value = taskValue;
|
||||
break;
|
||||
case 'file':
|
||||
value = fileValue;
|
||||
break;
|
||||
case 'project':
|
||||
value = projectValue;
|
||||
break;
|
||||
case 'default':
|
||||
value = this.getDefaultValue(field);
|
||||
break;
|
||||
}
|
||||
|
||||
if (value !== undefined && value !== null) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ export class MarkdownTaskParser {
|
|||
statusMapping: Record<string, string>,
|
||||
timeParsingService?: TimeParsingService
|
||||
): MarkdownTaskParser {
|
||||
const newConfig = { ...config, statusMapping };
|
||||
const newConfig = {...config, statusMapping};
|
||||
return new MarkdownTaskParser(newConfig, timeParsingService);
|
||||
}
|
||||
|
||||
|
|
@ -177,10 +177,10 @@ export class MarkdownTaskParser {
|
|||
const [comment, linesToSkip] =
|
||||
this.config.parseComments && i + 1 < lines.length
|
||||
? this.extractMultilineComment(
|
||||
lines,
|
||||
i + 1,
|
||||
actualSpaces
|
||||
)
|
||||
lines,
|
||||
i + 1,
|
||||
actualSpaces
|
||||
)
|
||||
: [undefined, 0];
|
||||
|
||||
i += linesToSkip;
|
||||
|
|
@ -471,7 +471,7 @@ export class MarkdownTaskParser {
|
|||
this.config.specialTagPrefixes[prefix] ??
|
||||
this.config.specialTagPrefixes[
|
||||
prefix.toLowerCase()
|
||||
];
|
||||
];
|
||||
console.debug("[TPB] Tag parse", {
|
||||
tag,
|
||||
prefix,
|
||||
|
|
@ -481,7 +481,7 @@ export class MarkdownTaskParser {
|
|||
if (
|
||||
metadataKey &&
|
||||
this.config.metadataParseMode !==
|
||||
MetadataParseMode.None
|
||||
MetadataParseMode.None
|
||||
) {
|
||||
metadata[metadataKey] = value;
|
||||
} else {
|
||||
|
|
@ -781,7 +781,7 @@ export class MarkdownTaskParser {
|
|||
specialTagPrefixes: Object.keys(this.config.specialTagPrefixes || {}),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
const before = content.substring(0, start);
|
||||
const after = content.substring(end + 1);
|
||||
return [key, value, before + after];
|
||||
|
|
@ -801,7 +801,7 @@ export class MarkdownTaskParser {
|
|||
const pos = content.indexOf(emoji);
|
||||
if (pos !== -1) {
|
||||
if (!earliestEmoji || pos < earliestEmoji.pos) {
|
||||
earliestEmoji = { pos, emoji, key };
|
||||
earliestEmoji = {pos, emoji, key};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1211,11 +1211,11 @@ export class MarkdownTaskParser {
|
|||
this.config.specialTagPrefixes[prefix] ??
|
||||
this.config.specialTagPrefixes[
|
||||
prefix.toLowerCase()
|
||||
];
|
||||
];
|
||||
if (
|
||||
metadataKey &&
|
||||
this.config.metadataParseMode !==
|
||||
MetadataParseMode.None
|
||||
MetadataParseMode.None
|
||||
) {
|
||||
metadata[metadataKey] = value;
|
||||
} else {
|
||||
|
|
@ -1294,7 +1294,7 @@ export class MarkdownTaskParser {
|
|||
);
|
||||
}
|
||||
|
||||
this.indentStack.push({ taskId, indentLevel, actualSpaces });
|
||||
this.indentStack.push({taskId, indentLevel, actualSpaces});
|
||||
}
|
||||
|
||||
private getStatusFromMapping(rawStatus: string): string | undefined {
|
||||
|
|
@ -1494,9 +1494,9 @@ export class MarkdownTaskParser {
|
|||
id: enhancedTask.metadata.id,
|
||||
dependsOn: enhancedTask.metadata.dependsOn
|
||||
? enhancedTask.metadata.dependsOn
|
||||
.split(",")
|
||||
.map((id) => id.trim())
|
||||
.filter((id) => id.length > 0)
|
||||
.split(",")
|
||||
.map((id) => id.trim())
|
||||
.filter((id) => id.length > 0)
|
||||
: undefined,
|
||||
onCompletion: enhancedTask.metadata.onCompletion,
|
||||
// Legacy compatibility fields that should remain in metadata
|
||||
|
|
@ -1504,8 +1504,8 @@ export class MarkdownTaskParser {
|
|||
heading: Array.isArray(enhancedTask.heading)
|
||||
? enhancedTask.heading
|
||||
: enhancedTask.heading
|
||||
? [enhancedTask.heading]
|
||||
: [],
|
||||
? [enhancedTask.heading]
|
||||
: [],
|
||||
parent: enhancedTask.parentId,
|
||||
tgProject: enhancedTask.tgProject,
|
||||
},
|
||||
|
|
@ -1696,7 +1696,13 @@ export class MarkdownTaskParser {
|
|||
}
|
||||
|
||||
/**
|
||||
* Inherit metadata from file frontmatter and project configuration
|
||||
* LEGACY (pre-dataflow): Inherit metadata from file frontmatter and project configuration
|
||||
*
|
||||
* In the new dataflow architecture, inheritance is handled exclusively by Augmentor.
|
||||
* This method remains for backward compatibility and is effectively disabled when
|
||||
* fileMetadataInheritance.enabled is false (returns {}). When enabled, Parser may still
|
||||
* perform minimal, legacy-compatible merging, but authoritative merging should be done
|
||||
* in Augmentor.merge().
|
||||
*/
|
||||
private inheritFileMetadata(
|
||||
taskMetadata: Record<string, string>,
|
||||
|
|
@ -1756,7 +1762,7 @@ export class MarkdownTaskParser {
|
|||
};
|
||||
|
||||
// Always convert priority values in task metadata, even if inheritance is disabled
|
||||
const inherited = { ...taskMetadata };
|
||||
const inherited = {...taskMetadata};
|
||||
if (inherited.priority !== undefined) {
|
||||
inherited.priority = convertPriorityValue(inherited.priority);
|
||||
}
|
||||
|
|
@ -1764,11 +1770,14 @@ export class MarkdownTaskParser {
|
|||
// Early return if enhanced project features are disabled
|
||||
// Check if file metadata inheritance is enabled
|
||||
if (!this.config.fileMetadataInheritance?.enabled) {
|
||||
return inherited;
|
||||
// Parser should not perform file-level inheritance when disabled
|
||||
// Leave any file/frontmatter merging to Augmentor when enabled
|
||||
return {};
|
||||
}
|
||||
|
||||
// Check if frontmatter inheritance is enabled
|
||||
if (!this.config.fileMetadataInheritance?.inheritFromFrontmatter) {
|
||||
// Legacy behavior: return task-only metadata
|
||||
return inherited;
|
||||
}
|
||||
|
||||
|
|
@ -1778,6 +1787,7 @@ export class MarkdownTaskParser {
|
|||
!this.config.fileMetadataInheritance
|
||||
?.inheritFromFrontmatterForSubtasks
|
||||
) {
|
||||
// Legacy behavior: do not inherit for subtasks
|
||||
return inherited;
|
||||
}
|
||||
|
||||
|
|
@ -1806,7 +1816,7 @@ export class MarkdownTaskParser {
|
|||
"metadata", // Prevent recursive metadata inheritance
|
||||
]);
|
||||
|
||||
// Inherit from file metadata (frontmatter) if available
|
||||
// LEGACY: Inherit from file metadata (frontmatter) if available
|
||||
if (this.fileMetadata) {
|
||||
// Map configured frontmatter project key to standard 'project'
|
||||
try {
|
||||
|
|
@ -1817,7 +1827,7 @@ export class MarkdownTaskParser {
|
|||
this.fileMetadata[configuredProjectKey] !== undefined &&
|
||||
this.fileMetadata[configuredProjectKey] !== null &&
|
||||
String(this.fileMetadata[configuredProjectKey]).trim() !==
|
||||
""
|
||||
""
|
||||
) {
|
||||
if (
|
||||
inherited.project === undefined ||
|
||||
|
|
@ -1829,7 +1839,8 @@ export class MarkdownTaskParser {
|
|||
).trim();
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
} catch {
|
||||
}
|
||||
|
||||
for (const [key, value] of Object.entries(this.fileMetadata)) {
|
||||
// Special handling for tags field
|
||||
|
|
@ -1896,7 +1907,7 @@ export class MarkdownTaskParser {
|
|||
}
|
||||
}
|
||||
|
||||
// Inherit from project configuration data if available
|
||||
// LEGACY: Inherit from project configuration data if available
|
||||
if (this.projectConfigCache) {
|
||||
for (const [key, value] of Object.entries(
|
||||
this.projectConfigCache
|
||||
|
|
@ -1980,6 +1991,6 @@ export class ConfigurableTaskParser extends MarkdownTaskParser {
|
|||
},
|
||||
};
|
||||
|
||||
super({ ...defaultConfig, ...config }, timeParsingService);
|
||||
super({...defaultConfig, ...config}, timeParsingService);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import type {
|
|||
FileSourceConfiguration,
|
||||
FileSourceTaskMetadata,
|
||||
FileSourceStats,
|
||||
UpdateDecision,
|
||||
FileTaskCache,
|
||||
RecognitionStrategy,
|
||||
PathRecognitionConfig,
|
||||
|
|
@ -198,16 +197,6 @@ export class FileSource {
|
|||
return;
|
||||
}
|
||||
|
||||
// Apply centralized file filter manager for file-scope rules
|
||||
if (this.fileFilterManager) {
|
||||
const include = this.fileFilterManager.shouldIncludePath(
|
||||
filePath,
|
||||
"file"
|
||||
);
|
||||
|
||||
if (!include) return;
|
||||
}
|
||||
|
||||
const shouldBeTask = await this.shouldCreateFileTask(filePath);
|
||||
const existingCache = this.fileTaskCache.get(filePath);
|
||||
const wasTask = existingCache?.fileTaskExists ?? false;
|
||||
|
|
@ -229,6 +218,7 @@ export class FileSource {
|
|||
* Check if a file should be treated as a task
|
||||
*/
|
||||
async shouldCreateFileTask(filePath: string): Promise<boolean> {
|
||||
// Fast reject non-relevant files before any IO
|
||||
if (!this.isRelevantFile(filePath)) return false;
|
||||
|
||||
const file = this.app.vault.getAbstractFileByPath(filePath) as TFile;
|
||||
|
|
@ -261,7 +251,7 @@ export class FileSource {
|
|||
fileCache: CachedMetadata | null
|
||||
): boolean {
|
||||
const config = this.config.getConfig();
|
||||
const { recognitionStrategies } = config;
|
||||
const {recognitionStrategies} = config;
|
||||
|
||||
// Check metadata strategy
|
||||
if (recognitionStrategies.metadata.enabled) {
|
||||
|
|
@ -333,7 +323,7 @@ export class FileSource {
|
|||
): boolean {
|
||||
if (!fileCache?.frontmatter) return false;
|
||||
|
||||
const { taskFields, requireAllFields } = config;
|
||||
const {taskFields, requireAllFields} = config;
|
||||
const frontmatter = fileCache.frontmatter;
|
||||
|
||||
const matchingFields = taskFields.filter(
|
||||
|
|
@ -360,7 +350,7 @@ export class FileSource {
|
|||
): boolean {
|
||||
if (!fileCache?.tags) return false;
|
||||
|
||||
const { taskTags, matchMode } = config;
|
||||
const {taskTags, matchMode} = config;
|
||||
const fileTags = fileCache.tags.map((tag) => tag.tag);
|
||||
|
||||
return taskTags.some((taskTag: string) => {
|
||||
|
|
@ -680,7 +670,7 @@ export class FileSource {
|
|||
config.recognitionStrategies.metadata
|
||||
)
|
||||
) {
|
||||
return { name: "metadata", criteria: "frontmatter" };
|
||||
return {name: "metadata", criteria: "frontmatter"};
|
||||
}
|
||||
|
||||
if (
|
||||
|
|
@ -692,7 +682,7 @@ export class FileSource {
|
|||
config.recognitionStrategies.tags
|
||||
)
|
||||
) {
|
||||
return { name: "tag", criteria: "file-tags" };
|
||||
return {name: "tag", criteria: "file-tags"};
|
||||
}
|
||||
|
||||
// Check path strategy
|
||||
|
|
@ -1016,19 +1006,7 @@ export class FileSource {
|
|||
* Check if file is relevant for processing
|
||||
*/
|
||||
private isRelevantFile(filePath: string): boolean {
|
||||
// Use centralized FileFilterManager for 'file' scope filtering if available
|
||||
if (this.fileFilterManager) {
|
||||
const include = this.fileFilterManager.shouldIncludePath(
|
||||
filePath,
|
||||
"file"
|
||||
);
|
||||
console.log(
|
||||
"[FileSource] isRelevantFile filter check (file-scope)",
|
||||
{ filePath, include }
|
||||
);
|
||||
if (!include) return false;
|
||||
}
|
||||
|
||||
// Fast-path filters first
|
||||
// Only process markdown files for now (additional file type support can be added later)
|
||||
if (!filePath.endsWith(".md")) {
|
||||
return false;
|
||||
|
|
@ -1039,6 +1017,15 @@ export class FileSource {
|
|||
return false;
|
||||
}
|
||||
|
||||
// Apply centralized FileFilterManager for 'file' scope filtering if available
|
||||
if (this.fileFilterManager) {
|
||||
const include = this.fileFilterManager.shouldIncludePath(
|
||||
filePath,
|
||||
"file"
|
||||
);
|
||||
if (!include) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -1138,7 +1125,7 @@ export class FileSource {
|
|||
* Get current statistics
|
||||
*/
|
||||
getStats(): FileSourceStats {
|
||||
return { ...this.stats };
|
||||
return {...this.stats};
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1185,8 +1172,6 @@ export class FileSource {
|
|||
async refresh(): Promise<void> {
|
||||
if (!this.isInitialized || !this.config.isEnabled()) return;
|
||||
|
||||
console.log("[FileSource] Manual refresh triggered");
|
||||
|
||||
// Clear cache and re-scan
|
||||
this.fileTaskCache.clear();
|
||||
this.stats.trackedFileCount = 0;
|
||||
|
|
@ -1227,7 +1212,7 @@ export class FileSource {
|
|||
this.stats = {
|
||||
initialized: false,
|
||||
trackedFileCount: 0,
|
||||
recognitionBreakdown: { metadata: 0, tag: 0, template: 0, path: 0 },
|
||||
recognitionBreakdown: {metadata: 0, tag: 0, template: 0, path: 0},
|
||||
lastUpdate: 0,
|
||||
lastUpdateSeq: 0,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1622,6 +1622,10 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
async loadSettings() {
|
||||
const savedData = await this.loadData();
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, savedData);
|
||||
try {
|
||||
console.debug('[Plugin][loadSettings] fileMetadataInheritance (raw):', savedData?.fileMetadataInheritance);
|
||||
console.debug('[Plugin][loadSettings] fileMetadataInheritance (effective):', this.settings.fileMetadataInheritance);
|
||||
} catch {}
|
||||
|
||||
// Migrate old inheritance settings to new structure
|
||||
this.migrateInheritanceSettings(savedData);
|
||||
|
|
@ -1658,6 +1662,9 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
}
|
||||
|
||||
async saveSettings() {
|
||||
try {
|
||||
console.debug('[Plugin][saveSettings] fileMetadataInheritance:', this.settings?.fileMetadataInheritance);
|
||||
} catch {}
|
||||
await this.saveData(this.settings);
|
||||
}
|
||||
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue