Skip to content

Commit 18e7528

Browse files
authored
Merge pull request #1448 from normenmueller/fix/projects-removal
fix: persist project removals
2 parents 6aac0c9 + 99d82e9 commit 18e7528

File tree

4 files changed

+66
-4
lines changed

4 files changed

+66
-4
lines changed

PR.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# fix/projects-removal
2+
3+
## Ensure project removals persist from the edit dialog
4+
5+
When removing project assignments in the task edit modal, the frontmatter now updates correctly (including fully removing the property when the list is emptied). We also normalize link comparisons so different link syntaxes (e.g., angle‑bracket markdown links) do not cause false change detection.
6+
7+
Examples (illustrative):
8+
9+
- Removing the last project now deletes the `projects` property from frontmatter.
10+
- `[Project](<path/to/Project.md>)` and `[[path/to/Project]]` compare as the same target for change detection.
11+
12+
## Changelog
13+
14+
- Normalize project link comparisons in the edit modal to handle angle‑bracket markdown links.
15+
- Persist empty project lists by explicitly removing the `projects` field from frontmatter.
16+
- Add a unit test to ensure empty project updates remove the property.
17+
18+
## Tests
19+
20+
- `./node_modules/.bin/jest tests/unit/services/TaskService.test.ts --runInBand`

src/modals/TaskEditModal.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import {
2525
} from "../utils/helpers";
2626
import { splitListPreservingLinksAndQuotes } from "../utils/stringSplit";
2727
import { ReminderContextMenu } from "../components/ReminderContextMenu";
28-
import { generateLinkWithDisplay } from "../utils/linkUtils";
28+
import { generateLinkWithDisplay, parseLinkToPath } from "../utils/linkUtils";
2929
import { EmbeddableMarkdownEditor } from "../editor/EmbeddableMarkdownEditor";
3030
import { ConfirmationModal } from "./ConfirmationModal";
3131

@@ -761,8 +761,23 @@ export class TaskEditModal extends TaskModal {
761761
const newProjects = splitListPreservingLinksAndQuotes(this.projects);
762762
const oldProjects = this.task.projects || [];
763763

764-
if (JSON.stringify(newProjects.sort()) !== JSON.stringify(oldProjects.sort())) {
765-
changes.projects = newProjects.length > 0 ? newProjects : undefined;
764+
const normalizeProjectList = (projects: string[]): string[] =>
765+
projects
766+
.map((project) => {
767+
if (!project || typeof project !== "string") return "";
768+
const trimmed = project.trim();
769+
if (!trimmed) return "";
770+
return parseLinkToPath(trimmed).trim();
771+
})
772+
.filter((project) => project.length > 0);
773+
774+
const normalizedNewProjects = normalizeProjectList(newProjects).sort();
775+
const normalizedOldProjects = normalizeProjectList(oldProjects).sort();
776+
777+
if (
778+
JSON.stringify(normalizedNewProjects) !== JSON.stringify(normalizedOldProjects)
779+
) {
780+
changes.projects = newProjects.length > 0 ? newProjects : [];
766781
}
767782

768783
// Parse and compare tags

src/services/TaskService.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1435,6 +1435,15 @@ export class TaskService {
14351435
delete frontmatter[this.plugin.fieldMapper.toUserField("scheduled")];
14361436
if (updates.hasOwnProperty("contexts") && updates.contexts === undefined)
14371437
delete frontmatter[this.plugin.fieldMapper.toUserField("contexts")];
1438+
if (updates.hasOwnProperty("projects")) {
1439+
const projectsField = this.plugin.fieldMapper.toUserField("projects");
1440+
const projectsToSet = Array.isArray(updates.projects) ? updates.projects : [];
1441+
if (projectsToSet.length > 0) {
1442+
frontmatter[projectsField] = projectsToSet;
1443+
} else {
1444+
delete frontmatter[projectsField];
1445+
}
1446+
}
14381447
if (updates.hasOwnProperty("timeEstimate") && updates.timeEstimate === undefined)
14391448
delete frontmatter[this.plugin.fieldMapper.toUserField("timeEstimate")];
14401449
if (updates.hasOwnProperty("completedDate") && updates.completedDate === undefined)

tests/unit/services/TaskService.test.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -987,6 +987,24 @@ describe('TaskService', () => {
987987
expect(mockPlugin.app.fileManager.processFrontMatter).toHaveBeenCalled();
988988
});
989989

990+
it('should remove projects when set to an empty array', async () => {
991+
const taskWithProjects = TaskFactory.createTask({
992+
projects: ['[[Project Alpha]]']
993+
});
994+
mockFile = new TFile(taskWithProjects.path);
995+
mockPlugin.app.vault.getAbstractFileByPath.mockReturnValue(mockFile);
996+
997+
let capturedFrontmatter: any = {};
998+
mockPlugin.app.fileManager.processFrontMatter.mockImplementation(async (_file, fn) => {
999+
capturedFrontmatter = { projects: ['[[Project Alpha]]'] };
1000+
fn(capturedFrontmatter);
1001+
});
1002+
1003+
await taskService.updateTask(taskWithProjects, { projects: [] });
1004+
1005+
expect(capturedFrontmatter.projects).toBeUndefined();
1006+
});
1007+
9901008
it('should preserve tags when not being updated', async () => {
9911009
const taskWithTags = TaskFactory.createTask({ tags: ['task', 'important'] });
9921010
const updates = { priority: 'high' };
@@ -1145,4 +1163,4 @@ describe('TaskService', () => {
11451163
expect(completed.priority).toBe('high');
11461164
});
11471165
});
1148-
});
1166+
});

0 commit comments

Comments
 (0)