feat(filesource): add status mapping between checkboxes and file metadata

- Implement bidirectional status mapping in FileSourceConfig with auto-detection from task status settings
- Add sync button in FileSourceSettings to populate mapping from checkbox configuration
- Enhance WriteAPI to convert between task symbols and metadata values during updates
- Fix preferFrontmatterTitle logic to properly respect user preference across content sources
- Add comprehensive logging for status mapping operations
- Update FileTaskManager to handle status updates with proper symbol/metadata conversion
- Ensure status changes persist correctly in both frontmatter and inline formats

BREAKING CHANGE: FileSource status updates now require proper mapping configuration for non-standard status values
This commit is contained in:
Quorafind 2025-08-25 20:30:18 +08:00
parent 52573bfcd0
commit 9f671ab3d7
9 changed files with 419 additions and 57 deletions

View file

@ -70,7 +70,7 @@ export class StatusComponent extends Component {
// Create checkbox-like element for the status
interactiveElement = createTaskCheckbox(
status.text,
this.task,
{ ...this.task, status: status.text } as any,
statusEl
);
}

View file

@ -5,7 +5,7 @@
* and treated as tasks with various strategies and options.
*/
import { Setting } from "obsidian";
import { Setting, Notice } from "obsidian";
import type TaskProgressBarPlugin from "../../index";
import type { FileSourceConfiguration } from "../../types/file-source";
import { t } from "../../translations/helper";
@ -716,6 +716,64 @@ function createStatusMappingSection(
);
if (config.statusMapping && config.statusMapping.enabled) {
// Sync mapping from Task Status Settings
new Setting(containerEl)
.setName(t("Sync from Task Status Settings"))
.setDesc(t("Populate FileSource status mapping from your checkbox status configuration"))
.addButton((button) =>
button
.setButtonText(t("Sync now"))
.setCta()
.onClick(async () => {
try {
const orchestrator = (plugin as any).dataflowOrchestrator;
if (orchestrator?.updateSettings) {
// Delegate to orchestrator so in-memory FileSource mapping syncs immediately
orchestrator.updateSettings(plugin.settings);
new Notice(t("FileSource status mapping synced"));
} else {
// Fallback: derive symbol->metadata mapping from Task Status settings
const taskStatuses = (plugin.settings.taskStatuses || {}) as Record<string, string>;
const symbolToType: Record<string, string> = {};
for (const [type, symbols] of Object.entries(taskStatuses)) {
const list = String(symbols).split("|").filter(Boolean);
for (const sym of list) {
if (sym === "/>") { symbolToType["/"] = type; symbolToType[">"] = type; continue; }
if (sym.length === 1) symbolToType[sym] = type; else {
for (const ch of sym) symbolToType[ch] = type;
}
}
}
const typeToMetadata: Record<string, string> = {
completed: "completed",
inProgress: "in-progress",
planned: "planned",
abandoned: "cancelled",
notStarted: "not-started",
};
plugin.settings.fileSource.statusMapping = plugin.settings.fileSource.statusMapping || {
enabled: true,
metadataToSymbol: {},
symbolToMetadata: {},
autoDetect: true,
caseSensitive: false,
};
plugin.settings.fileSource.statusMapping.symbolToMetadata = {};
for (const [symbol, type] of Object.entries(symbolToType)) {
const md = typeToMetadata[type];
if (md) plugin.settings.fileSource.statusMapping.symbolToMetadata[symbol] = md;
}
await plugin.saveSettings();
new Notice(t("FileSource status mapping synced"));
}
} catch (e) {
console.error("Failed to sync FileSource status mapping:", e);
new Notice(t("Failed to sync mapping"));
}
})
);
new Setting(containerEl)
.setName(t("Case Sensitive Matching"))
.setDesc(t("Enable case-sensitive matching for status values"))
@ -870,7 +928,7 @@ function createPerformanceSection(
// Note: Worker Processing setting has been moved to IndexSettingsTab.ts > Performance Configuration section
// This avoids duplication and provides centralized control for all worker processing
new Setting(containerEl)
.setName(t("Cache TTL"))
.setDesc(t("Time-to-live for cached results in milliseconds (default: 300000 = 5 minutes)"))

View file

@ -55,6 +55,27 @@ export function getStatusText(
return statusTextMap[status as keyof typeof statusTextMap] || "No status";
}
function mapTextStatusToSymbol(status: string): string {
if (!status) return " ";
if (status.length === 1) return status; // already a symbol mark
const map: Record<string, string> = {
"completed": "x",
"done": "x",
"finished": "x",
"in-progress": "/",
"in progress": "/",
"doing": "/",
"planned": "?",
"todo": "?",
"cancelled": "-",
"canceled": "-",
"not-started": " ",
"not started": " ",
};
const key = status.toLowerCase();
return map[key] ?? status;
}
export function createTaskCheckbox(
status: string,
task: Task,
@ -64,10 +85,9 @@ export function createTaskCheckbox(
cls: "task-list-item-checkbox",
type: "checkbox",
});
checkbox.dataset.task = status;
if (status !== " ") {
checkbox.checked = true;
}
const symbol = mapTextStatusToSymbol(status);
checkbox.dataset.task = symbol;
checkbox.checked = symbol !== " ";
return checkbox;
}

View file

@ -138,6 +138,12 @@ export class DataflowOrchestrator {
this.plugin.settings.fileSource,
this.fileFilterManager
);
// Keep FileSource status mapping in sync with Task Status settings
try {
this.fileSource.syncStatusMappingFromSettings(this.plugin.settings.taskStatuses);
} catch (e) {
console.warn("[DataflowOrchestrator] Failed to sync FileSource status mapping on init", e);
}
}
}
@ -612,6 +618,14 @@ export class DataflowOrchestrator {
this.fileFilterManager
);
this.fileSource.initialize();
// Sync status mapping from Task Status settings on creation
try {
if (settings?.taskStatuses) {
this.fileSource.syncStatusMappingFromSettings(settings.taskStatuses);
}
} catch (e) {
console.warn("[DataflowOrchestrator] Failed to sync FileSource status mapping on settings create", e);
}
} else if (!settings?.fileSource?.enabled && this.fileSource) {
// Disable FileSource if it exists but is disabled
this.fileSource.cleanup();
@ -621,6 +635,15 @@ export class DataflowOrchestrator {
this.fileSource.updateConfig(settings.fileSource);
}
// Always try syncing status mapping when settings update and FileSource is active
if (this.fileSource && settings?.taskStatuses) {
try {
this.fileSource.syncStatusMappingFromSettings(settings.taskStatuses);
} catch (e) {
console.warn("[DataflowOrchestrator] Failed to sync FileSource status mapping on settings update", e);
}
}
// Update FileFilterManager
if (settings?.fileFilter) {
if (!this.fileFilterManager) {

View file

@ -293,20 +293,166 @@ export class WriteAPI {
const preferFrontmatterTitle = cfg?.preferFrontmatterTitle ?? true;
const customContentField = (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;
const hasFrontmatterUpdates = (
updates.status !== undefined ||
updates.completed !== undefined ||
md.priority !== undefined ||
md.tags !== undefined ||
md.project !== undefined ||
md.context !== undefined ||
md.area !== undefined ||
md.dueDate !== undefined ||
md.startDate !== undefined ||
md.scheduledDate !== undefined
);
if (hasFrontmatterUpdates) {
// Announce start of a write operation for frontmatter updates
emit(this.app, Events.WRITE_OPERATION_START, { path: file.path, taskId });
const formatDate = (val: any): any => {
if (val === undefined || val === null) return val;
if (typeof val === "number") {
// Write as YYYY-MM-DD for frontmatter consistency
return new Date(val).toISOString().split("T")[0];
}
if (val instanceof Date) {
return val.toISOString().split("T")[0];
}
return val; // assume already a string
};
await this.app.fileManager.processFrontMatter(file, (fm) => {
// Top-level status/completed
if (updates.status !== undefined) {
// If status mapping is enabled, prefer writing human-readable metadata value;
// otherwise write the raw symbol.
const fsMapping = this.plugin.settings?.fileSource?.statusMapping;
let statusToWrite: string = updates.status as any;
if (fsMapping?.enabled) {
// Try explicit symbol->metadata mapping first
const mapped = fsMapping.symbolToMetadata?.[statusToWrite];
if (mapped) {
statusToWrite = mapped;
console.log("[WriteAPI][FileSource] mapped via symbolToMetadata", { mapped });
} else {
console.log("[WriteAPI][FileSource] fallback mapping from taskStatuses", this.plugin.settings?.taskStatuses);
// Derive from Task Status settings as a fallback
const taskStatuses = (this.plugin.settings?.taskStatuses || {}) as Record<string, string>;
const listByType = Object.entries(taskStatuses).map(([type, symbols]) => ({ type, symbols: String(symbols) }));
for (const entry of listByType) {
const parts = entry.symbols.split("|").filter(Boolean);
for (const sym of parts) {
if (sym === statusToWrite || (sym.length > 1 && sym.includes(statusToWrite))) {
// Map types to canonical metadata values
const typeToMetadata: Record<string, string> = {
completed: "completed",
inProgress: "in-progress",
planned: "planned",
abandoned: "cancelled",
notStarted: "not-started",
};
const md = typeToMetadata[entry.type];
if (md) {
statusToWrite = md;
console.log("[WriteAPI][FileSource] mapped via taskStatuses", { type: entry.type, md });
}
break;
}
}
if (statusToWrite !== (updates.status as any)) break;
}
}
}
(fm as any).status = statusToWrite;
}
if (updates.completed !== undefined) {
(fm as any).completed = updates.completed;
}
// Metadata fields
if (md.priority !== undefined) {
(fm as any).priority = md.priority;
console.log("[WriteAPI][FileSource] wrote fm.priority", { priority: md.priority });
}
if (md.tags !== undefined) {
(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 });
}
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;
console.log("[WriteAPI][FileSource] wrote fm.area", { area: md.area });
}
if (md.dueDate !== undefined) {
(fm as any).dueDate = formatDate(md.dueDate);
console.log("[WriteAPI][FileSource] wrote fm.dueDate", { dueDate: (fm as any).dueDate });
}
if (md.startDate !== undefined) {
(fm as any).startDate = formatDate(md.startDate);
console.log("[WriteAPI][FileSource] wrote fm.startDate", { startDate: (fm as any).startDate });
}
if (md.scheduledDate !== undefined) {
(fm as any).scheduledDate = formatDate(md.scheduledDate);
console.log("[WriteAPI][FileSource] wrote fm.scheduledDate", { scheduledDate: (fm as any).scheduledDate });
}
});
// Announce completion of frontmatter write operation
emit(this.app, Events.WRITE_OPERATION_COMPLETE, { path: file.path, taskId });
}
} catch (error) {
console.error("WriteAPI: Error updating file-source task frontmatter:", error);
return { success: false, error: String(error) };
}
// Handle content/title change
if (updates.content && updates.content !== originalTask.content) {
const shouldWriteContent = typeof updates.content === "string";
console.log("[WriteAPI][FileSource] content change gate", {
originalContent: originalTask.content,
updatesContent: updates.content,
shouldWriteContent
});
if (shouldWriteContent) {
try {
// Announce start of a write operation
emit(this.app, Events.WRITE_OPERATION_START, { path: file.path, taskId });
console.log("[WriteAPI][FileSource] content branch", { contentSource, preferFrontmatterTitle, customContentField });
switch (contentSource) {
case "title": {
if (preferFrontmatterTitle) {
await this.app.fileManager.processFrontMatter(file, (fm) => {
(fm as any).title = updates.content;
});
console.log("[WriteAPI][FileSource] wrote fm.title (branch: title)", { 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 });
} else {
newFilePath = await this.renameFile(file, updates.content!);
console.log("[WriteAPI][FileSource] renamed file (branch: title)", { newFilePath });
}
break;
}
@ -319,14 +465,35 @@ export class WriteAPI {
await this.app.fileManager.processFrontMatter(file, (fm) => {
(fm as any)[customContentField] = updates.content;
});
console.log("[WriteAPI][FileSource] wrote fm[customContentField] (branch: custom)", { field: customContentField, value: updates.content });
const cacheAfter = this.app.metadataCache.getFileCache(file);
console.log("[WriteAPI][FileSource] cache fm[customContentField] after write (branch: custom)", { field: customContentField, value: cacheAfter?.frontmatter?.[customContentField] });
} else if (preferFrontmatterTitle) {
await this.app.fileManager.processFrontMatter(file, (fm) => {
(fm as any).title = updates.content;
});
console.log("[WriteAPI][FileSource] wrote fm.title (branch: custom fallback)", { 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 });
} else {
newFilePath = await this.renameFile(file, updates.content!);
console.log("[WriteAPI][FileSource] renamed file (branch: custom fallback)", { newFilePath });
}
break;
}
case "filename":
default: {
newFilePath = await this.renameFile(file, updates.content!);
if (preferFrontmatterTitle) {
await this.app.fileManager.processFrontMatter(file, (fm) => {
(fm as any).title = updates.content;
});
console.log("[WriteAPI][FileSource] wrote fm.title (branch: filename/default)", { 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 });
} else {
newFilePath = await this.renameFile(file, updates.content!);
console.log("[WriteAPI][FileSource] renamed file (branch: filename/default)", { newFilePath });
}
break;
}
}

View file

@ -339,7 +339,7 @@ export class FileSource {
// Check frontmatter template references
if (config.checkTemplateMetadata && fileCache?.frontmatter) {
const frontmatter = fileCache.frontmatter;
return frontmatter.template === templatePath ||
return frontmatter.template === templatePath ||
frontmatter.templateFile === templatePath ||
frontmatter.templatePath === templatePath;
}
@ -576,7 +576,7 @@ export class FileSource {
// Check if file matches any template path
const matchesTemplate = templateConfig.templatePaths.some(templatePath => {
// Simple path matching - could be enhanced with more sophisticated matching
return filePath.includes(templatePath) ||
return filePath.includes(templatePath) ||
(fileCache?.frontmatter?.template === templatePath) ||
(fileCache?.frontmatter?.templateFile === templatePath);
});
@ -603,23 +603,41 @@ export class FileSource {
switch (config.contentSource) {
case 'filename':
// If user prefers frontmatter title, show it over filename
if (config.preferFrontmatterTitle && fileCache?.frontmatter?.title) {
return fileCache.frontmatter.title as string;
}
return config.stripExtension ? fileNameWithoutExt : fileName;
case 'title':
// Always prefer frontmatter title if available, fallback to filename without extension
return fileCache?.frontmatter?.title || fileNameWithoutExt;
return (fileCache?.frontmatter?.title as string) || fileNameWithoutExt;
case 'h1':
const h1 = fileCache?.headings?.find(h => h.level === 1);
return h1?.heading || fileNameWithoutExt;
return (h1?.heading as string) || fileNameWithoutExt;
case 'custom':
if (config.customContentField && fileCache?.frontmatter) {
return fileCache.frontmatter[config.customContentField] || fileNameWithoutExt;
const val = fileCache.frontmatter[config.customContentField];
if (val) return val as string;
// If custom field not present, optionally prefer frontmatter title
if (config.preferFrontmatterTitle && fileCache.frontmatter.title) {
return fileCache.frontmatter.title as string;
}
return fileNameWithoutExt;
}
// No custom field specified: optionally prefer frontmatter title
if (config.preferFrontmatterTitle && fileCache?.frontmatter?.title) {
return fileCache.frontmatter.title as string;
}
return fileNameWithoutExt;
default:
// Default to respecting preferFrontmatterTitle when available
if (config.preferFrontmatterTitle && fileCache?.frontmatter?.title) {
return fileCache.frontmatter.title as string;
}
return config.stripExtension ? fileNameWithoutExt : fileName;
}
}
@ -636,18 +654,34 @@ export class FileSource {
const config = this.config.getConfig();
const frontmatter = fileCache?.frontmatter || {};
// Get status from frontmatter and map it to symbol if needed
let status = frontmatter.status || config.fileTaskProperties.defaultStatus;
// Apply status mapping if enabled
if (config.statusMapping && config.statusMapping.enabled && frontmatter.status) {
// Try to map the metadata value to a symbol
const mappedStatus = this.config.mapMetadataToSymbol(frontmatter.status);
if (mappedStatus !== frontmatter.status) {
// Mapping was successful
status = mappedStatus;
console.log(`[FileSource] Mapped status '${frontmatter.status}' to '${mappedStatus}' for ${filePath}`);
// Derive status from frontmatter and eagerly map textual metadata to a symbol
const rawStatus = frontmatter.status ?? "";
const toSymbol = (val: string): string => {
if (!val) return config.fileTaskProperties.defaultStatus;
// Already a single-character mark
if (val.length === 1) return val;
const sm = this.config.getConfig().statusMapping;
const target = sm.caseSensitive ? val : String(val).toLowerCase();
// Try configured metadata->symbol table first
for (const [k, sym] of Object.entries(sm.metadataToSymbol || {})) {
const key = sm.caseSensitive ? k : k.toLowerCase();
if (key === target) return sym;
}
// Fallback to common defaults to be robust
const defaults: Record<string, string> = {
"completed": "x", "done": "x", "finished": "x",
"in-progress": "/", "in progress": "/", "doing": "/",
"planned": "?", "todo": "?",
"cancelled": "-", "canceled": "-",
"not-started": " ", "not started": " ",
};
const norm = String(val).toLowerCase();
if (defaults[norm] !== undefined) return defaults[norm];
return config.fileTaskProperties.defaultStatus;
};
let status = rawStatus ? toSymbol(rawStatus) : config.fileTaskProperties.defaultStatus;
if (rawStatus && status !== rawStatus) {
console.log(`[FileSource] Mapped status '${rawStatus}' to '${status}' for ${filePath}`);
}
// Extract standard task metadata
@ -897,6 +931,17 @@ export class FileSource {
this.config.updateConfig(config);
}
/**
* Sync FileSource status mapping from plugin TaskStatus settings
*/
public syncStatusMappingFromSettings(taskStatuses: Record<string, string>): void {
try {
this.config.syncWithTaskStatuses(taskStatuses);
} catch (e) {
console.warn("[FileSource] Failed to sync status mapping from settings", e);
}
}
/**
* Alias for updateConfiguration to match expected interface
*/

View file

@ -432,30 +432,36 @@ export class FileSourceConfig {
if (!this.config.statusMapping.autoDetect) {
return;
}
// Extract symbols from task status configuration
const symbolToType: Record<string, string> = {};
const typeToSymbols: Record<string, string[]> = {};
for (const [type, symbols] of Object.entries(taskStatuses)) {
const symbolList = symbols.split('|').filter(s => s);
typeToSymbols[type] = typeToSymbols[type] || [];
for (const symbol of symbolList) {
// Handle potential pattern like '/>' being split into '/' and '>'
if (symbol.length === 1 || symbol === '/>') {
if (symbol === '/>') {
symbolToType['/'] = type;
symbolToType['>'] = type;
typeToSymbols[type].push('/');
typeToSymbols[type].push('>');
} else {
symbolToType[symbol] = type;
typeToSymbols[type].push(symbol);
}
} else {
// For multi-character symbols, add each character separately
for (const char of symbol) {
symbolToType[char] = type;
typeToSymbols[type].push(char);
}
}
}
}
// Update symbol to metadata mappings based on type
const typeToMetadata: Record<string, string> = {
'completed': 'completed',
@ -464,16 +470,32 @@ export class FileSourceConfig {
'abandoned': 'cancelled',
'notStarted': 'not-started'
};
for (const [symbol, type] of Object.entries(symbolToType)) {
if (typeToMetadata[type]) {
this.config.statusMapping.symbolToMetadata[symbol] = typeToMetadata[type];
}
}
// Also update metadata->symbol so metadata strings map back to the user's preferred symbol
const preferredFallback: Record<string, string> = {
completed: 'x',
inProgress: '/',
planned: '?',
abandoned: '-',
notStarted: ' '
};
for (const [type, mdValue] of Object.entries(typeToMetadata)) {
const symbols = typeToSymbols[type] || [];
const preferred = symbols[0] || preferredFallback[type];
if (mdValue && preferred !== undefined) {
this.config.statusMapping.metadataToSymbol[mdValue] = preferred;
}
}
this.notifyListeners();
}
/**
* Create a configuration preset for common use cases
*/

View file

@ -326,14 +326,6 @@ export default class TaskProgressBarPlugin extends Plugin {
})
);
// Add a ribbon icon for opening the TaskView
this.addRibbonIcon(
"task-genius",
t("Open Task Genius view"),
() => {
this.activateTaskView();
}
);
// Add a command to open the TaskView
this.addCommand({
id: "open-task-genius-view",
@ -368,6 +360,16 @@ export default class TaskProgressBarPlugin extends Plugin {
addIcon("abandoned", getStatusIcon("abandoned"));
addIcon("notStarted", getStatusIcon("notStarted"));
// Add a ribbon icon for opening the TaskView
this.addRibbonIcon(
"task-genius",
t("Open Task Genius view"),
() => {
this.activateTaskView();
}
);
// Initialize dataflow orchestrator (primary architecture)
try {
console.log(

View file

@ -217,17 +217,21 @@ export class FileTaskManagerImpl implements FileTaskManager {
*/
fileTaskToPropertyUpdates(
task: FileTask,
mapping: FileTaskPropertyMapping = DEFAULT_FILE_TASK_MAPPING
mapping: FileTaskPropertyMapping = DEFAULT_FILE_TASK_MAPPING,
excludeContent: boolean = false
): Record<string, any> {
const updates: Record<string, any> = {};
// Update content property based on configuration
const config = this.fileSourceConfig?.fileTaskProperties;
if (config?.contentSource && config.contentSource !== 'filename') {
// Only update content property if it's not handled by file renaming
const shouldUpdateProperty = this.shouldUpdateContentProperty(config);
if (shouldUpdateProperty) {
updates[mapping.contentProperty] = task.content;
// Skip content if it was already handled separately (e.g., in handleContentUpdate)
if (!excludeContent) {
const config = this.fileSourceConfig?.fileTaskProperties;
if (config?.contentSource && config.contentSource !== 'filename') {
// Only update content property if it's not handled by file renaming
const shouldUpdateProperty = this.shouldUpdateContentProperty(config);
if (shouldUpdateProperty) {
updates[mapping.contentProperty] = task.content;
}
}
}
// Note: If contentSource is 'filename', content updates are handled by file renaming
@ -317,10 +321,12 @@ export class FileTaskManagerImpl implements FileTaskManager {
): Promise<void> {
// Merge updates into the task
const updatedTask = { ...task, ...updates };
let contentHandledSeparately = false;
// Handle content changes - re-extract time components if content changed
if (updates.content && updates.content !== task.content) {
await this.handleContentUpdate(task, updates.content);
contentHandledSeparately = true;
// Re-extract time components from updated content
const enhancedMetadata = this.extractTimeComponents(updates.content);
@ -352,8 +358,12 @@ export class FileTaskManagerImpl implements FileTaskManager {
}
}
// Convert to property updates (excluding content which is handled separately)
const propertyUpdates = this.fileTaskToPropertyUpdates(updatedTask);
// Convert to property updates (excluding content if it was handled separately)
const propertyUpdates = this.fileTaskToPropertyUpdates(
updatedTask,
DEFAULT_FILE_TASK_MAPPING,
contentHandledSeparately
);
console.log(
`[FileTaskManager] Updating file task ${task.content} with properties:`,
@ -363,7 +373,12 @@ export class FileTaskManagerImpl implements FileTaskManager {
// Update properties through the source entry
for (const [key, value] of Object.entries(propertyUpdates)) {
try {
task.sourceEntry.updateProperty(key, value);
// Note: updateProperty might be async, so we await it
if (typeof task.sourceEntry.updateProperty === 'function') {
await Promise.resolve(task.sourceEntry.updateProperty(key, value));
} else {
console.error(`updateProperty method not available on source entry for key: ${key}`);
}
} catch (error) {
console.error(`Failed to update property ${key}:`, error);
}
@ -445,10 +460,15 @@ export class FileTaskManagerImpl implements FileTaskManager {
): Promise<void> {
try {
// Update the title property in frontmatter through the source entry
task.sourceEntry.updateProperty('title', newTitle);
console.log(
`[FileTaskManager] Updated frontmatter title for ${task.filePath} to: ${newTitle}`
);
// Note: updateProperty might be async, so we await it
if (typeof task.sourceEntry.updateProperty === 'function') {
await Promise.resolve(task.sourceEntry.updateProperty('title', newTitle));
console.log(
`[FileTaskManager] Updated frontmatter title for ${task.filePath} to: ${newTitle}`
);
} else {
throw new Error('updateProperty method not available on source entry');
}
} catch (error) {
console.error(`[FileTaskManager] Failed to update frontmatter title:`, error);
// Fallback to file renaming if frontmatter update fails
@ -520,10 +540,15 @@ export class FileTaskManagerImpl implements FileTaskManager {
): Promise<void> {
try {
// Update the custom field in frontmatter through the source entry
task.sourceEntry.updateProperty(fieldName, newContent);
console.log(
`[FileTaskManager] Updated custom field '${fieldName}' for ${task.filePath} to: ${newContent}`
);
// Note: updateProperty might be async, so we await it
if (typeof task.sourceEntry.updateProperty === 'function') {
await Promise.resolve(task.sourceEntry.updateProperty(fieldName, newContent));
console.log(
`[FileTaskManager] Updated custom field '${fieldName}' for ${task.filePath} to: ${newContent}`
);
} else {
throw new Error('updateProperty method not available on source entry');
}
} catch (error) {
console.error(`[FileTaskManager] Failed to update custom field '${fieldName}':`, error);
}