diff --git a/src/__tests__/FileMetadataInheritance.test.ts b/src/__tests__/FileMetadataInheritance.test.ts index 5290029a..8643387d 100644 --- a/src/__tests__/FileMetadataInheritance.test.ts +++ b/src/__tests__/FileMetadataInheritance.test.ts @@ -124,6 +124,22 @@ describe("File Metadata Inheritance", () => { // tags应该被继承,但不会覆盖非继承字段 }); + test("should use filename when inherited frontmatter project is true", () => { + const content = "- [ ] Task without explicit project"; + const fileMetadata = { + project: true, + }; + + const tasks = parser.parseLegacy( + content, + "Projects/My Awesome Project.md", + fileMetadata + ); + + expect(tasks).toHaveLength(1); + expect(tasks[0].metadata.project).toBe("My Awesome Project"); + }); + test("should work independently of project configuration", () => { // 项目配置为null,验证不会崩溃 mockPlugin.settings.projectConfig = null; @@ -515,4 +531,4 @@ describe("File Metadata Inheritance", () => { }); }); }); -}); \ No newline at end of file +}); diff --git a/src/__tests__/FileMetadataTaskParser.test.ts b/src/__tests__/FileMetadataTaskParser.test.ts index 8129980f..17313b84 100644 --- a/src/__tests__/FileMetadataTaskParser.test.ts +++ b/src/__tests__/FileMetadataTaskParser.test.ts @@ -199,6 +199,29 @@ describe("FileMetadataTaskParser", () => { expect(dueDateTask?.status).toBe(" "); // Due dates are typically incomplete }); + it("should use filename as project name when frontmatter project is true", () => { + const filePath = "Projects/My Awesome Project.md"; + const fileContent = "# Test File"; + const fileCache = { + frontmatter: { + title: "Test Task", + todo: true, + project: true, + }, + tags: [], + }; + + const result = parser.parseFileForTasks( + filePath, + fileContent, + fileCache as any + ); + + expect(result.errors).toHaveLength(0); + expect(result.tasks).toHaveLength(1); + expect(result.tasks[0].metadata.project).toBe("My Awesome Project"); + }); + it("should not create tasks when parsing is disabled", () => { const disabledConfig: FileParsingConfiguration = { enableFileMetadataParsing: false, diff --git a/src/__tests__/file-source/FileSource.basic.test.ts b/src/__tests__/file-source/FileSource.basic.test.ts index 6ad36671..53587794 100644 --- a/src/__tests__/file-source/FileSource.basic.test.ts +++ b/src/__tests__/file-source/FileSource.basic.test.ts @@ -331,6 +331,21 @@ describe('FileSource', () => { expect(task!.metadata.project).toBe('Test Project'); }); + it('should use filename as project name when frontmatter project is true', async () => { + const fileCache = { + frontmatter: { + dueDate: '2024-01-01', + project: true + } + }; + mockApp.metadataCache.getFileCache.mockReturnValue(fileCache); + + const task = await fileSource.createFileTask('Projects/My Awesome Project.md'); + + expect(task).toBeTruthy(); + expect(task!.metadata.project).toBe('My Awesome Project'); + }); + it('should use default status when not specified', async () => { const fileCache = { frontmatter: { @@ -436,4 +451,4 @@ describe('FileSource', () => { await expect(fileSource.refresh()).resolves.not.toThrow(); }); }); -}); \ No newline at end of file +}); diff --git a/src/dataflow/core/ConfigurableTaskParser.ts b/src/dataflow/core/ConfigurableTaskParser.ts index c897d871..64a71422 100644 --- a/src/dataflow/core/ConfigurableTaskParser.ts +++ b/src/dataflow/core/ConfigurableTaskParser.ts @@ -143,6 +143,7 @@ export class MarkdownTaskParser { const inheritedMetadata = this.inheritFileMetadata( metadata, isSubtask, + filePath, ); // Extract time components from task content using enhanced time parsing @@ -1736,6 +1737,7 @@ export class MarkdownTaskParser { private inheritFileMetadata( taskMetadata: Record, isSubtask: boolean = false, + filePath: string = "", ): Record { // Helper function to convert priority values to numbers const convertPriorityValue = (value: any): string => { @@ -1861,22 +1863,34 @@ export class MarkdownTaskParser { try { const configuredProjectKey = this.config.projectConfig?.metadataConfig?.metadataKey; + const configuredProjectValue = + configuredProjectKey !== undefined + ? this.fileMetadata[configuredProjectKey] + : undefined; if ( configuredProjectKey && - this.fileMetadata[configuredProjectKey] !== undefined && - this.fileMetadata[configuredProjectKey] !== null && - String( - this.fileMetadata[configuredProjectKey], - ).trim() !== "" + configuredProjectValue !== undefined && + configuredProjectValue !== null ) { if ( inherited.project === undefined || inherited.project === null || inherited.project === "" ) { - inherited.project = String( - this.fileMetadata[configuredProjectKey], - ).trim(); + if (configuredProjectValue === true) { + const fileName = + filePath.split("/").pop() || filePath; + inherited.project = fileName.replace( + /\.md$/i, + "", + ); + } else if ( + typeof configuredProjectValue === "string" && + configuredProjectValue.trim() !== "" + ) { + inherited.project = + configuredProjectValue.trim(); + } } } } catch {} @@ -1939,6 +1953,10 @@ export class MarkdownTaskParser { // Convert priority values to numbers before inheritance if (key === "priority") { inherited[key] = convertPriorityValue(value); + } else if (key === "project" && value === true) { + const fileName = + filePath.split("/").pop() || filePath; + inherited[key] = fileName.replace(/\.md$/i, ""); } else { inherited[key] = String(value); } diff --git a/src/dataflow/sources/FileSource.ts b/src/dataflow/sources/FileSource.ts index 118e6d19..2f5f41de 100644 --- a/src/dataflow/sources/FileSource.ts +++ b/src/dataflow/sources/FileSource.ts @@ -907,10 +907,13 @@ export class FileSource { // 2. Metadata mapping (e.g., custom_project mapped to project via metadataMappings) // 3. Tag extraction (#project/xxx) - lowest priority, only if frontmatter has nothing const projectVal = resolvedFrontmatter.project ?? projectFromTags; - const projectValue: string | undefined = - projectVal !== undefined && projectVal !== null - ? String(projectVal) - : undefined; + let projectValue: string | undefined; + if (projectVal === true) { + const fileName = filePath.split("/").pop() || filePath; + projectValue = fileName.replace(/\.md$/i, ""); + } else if (typeof projectVal === "string" && projectVal.trim()) { + projectValue = projectVal.trim(); + } const shouldStripProjectTags = !resolvedFrontmatter.project && diff --git a/src/parsers/file-metadata-parser.ts b/src/parsers/file-metadata-parser.ts index 9c78a8ec..7ffe732e 100644 --- a/src/parsers/file-metadata-parser.ts +++ b/src/parsers/file-metadata-parser.ts @@ -336,9 +336,16 @@ export class FileMetadataTaskParser { const detectedProject = this.detectProjectFromFile(filePath, frontmatter, fileCache); if (detectedProject) { metadata.project = detectedProject; - } else if (frontmatter.project) { + } else if (frontmatter.project === true) { + // Legacy boolean semantics: `project: true` means use filename + const fileName = filePath.split("/").pop() || filePath; + metadata.project = fileName.replace(/\.md$/i, ""); + } else if ( + typeof frontmatter.project === "string" && + frontmatter.project.trim() + ) { // Fallback to legacy project field - metadata.project = String(frontmatter.project); + metadata.project = frontmatter.project.trim(); } if (frontmatter.context) { metadata.context = String(frontmatter.context);