Skip to content

Commit 269463b

Browse files
committed
plugin: normalize unicode whitespace in filter strings
Filters containing Unicode space separators (e.g., non-breaking spaces from copy-paste) would cause API errors when sent to Todoist. Now all Unicode Zs-category characters are replaced with ASCII spaces before the filter reaches the API.
1 parent fd21fcc commit 269463b

File tree

3 files changed

+45
-0
lines changed

3 files changed

+45
-0
lines changed

docs/docs/changelog.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1616

1717
- You can now sort tasks by their deadlines by name using `deadline` or `deadlineDescending` in the sorting field.
1818

19+
### 🐛 Bug Fixes
20+
21+
- Filter strings now have Unicode whitespace characters (e.g., non-breaking spaces from copy-paste) normalized to regular spaces, preventing API errors.
22+
1923
## v2.6.0 (2026-02-01)
2024

2125
### ✨ Features

plugin/src/query/replacements.test.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,4 +61,41 @@ describe("applyReplacements", () => {
6161
});
6262
}
6363
});
64+
65+
describe("unicode whitespace normalization", () => {
66+
const testcases: TestCase[] = [
67+
{
68+
description: "should replace non-breaking spaces with regular spaces",
69+
filter: "#Project\u00A0&\u00A0/section",
70+
expectedFilter: "#Project & /section",
71+
},
72+
{
73+
description: "should replace em spaces with regular spaces",
74+
filter: "#Project\u2003&\u2003/section",
75+
expectedFilter: "#Project & /section",
76+
},
77+
{
78+
description: "should trim leading and trailing whitespace",
79+
filter: " #Project & /section ",
80+
expectedFilter: "#Project & /section",
81+
},
82+
{
83+
description: "should not modify filters with only regular spaces",
84+
filter: "#Project & /section",
85+
expectedFilter: "#Project & /section",
86+
},
87+
];
88+
89+
for (const tc of testcases) {
90+
it(tc.description, () => {
91+
const query: TaskQuery = {
92+
filter: tc.filter,
93+
};
94+
95+
applyReplacements(query, new FakeContext(tc.filePath ?? ""));
96+
97+
expect(query.filter).toBe(tc.expectedFilter);
98+
});
99+
}
100+
});
64101
});

plugin/src/query/replacements.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,8 @@ export function applyReplacements(query: TaskQuery, ctx: MarkdownPostProcessorCo
66
// Replace {filename} with the base file name of file where the query originated.
77
const baseFileName = ctx.sourcePath.replace(/.*\//, "").replace(/\.md$/i, "");
88
query.filter = query.filter.replace(/{{filename}}/g, baseFileName);
9+
10+
// Normalize Unicode whitespace characters (e.g., non-breaking spaces, em spaces)
11+
// to regular ASCII spaces to prevent API errors.
12+
query.filter = query.filter.replace(/[\p{Zs}]/gu, " ").trim();
913
}

0 commit comments

Comments
 (0)