Skip to content
This repository was archived by the owner on Jun 24, 2025. It is now read-only.

Commit c2b5f0a

Browse files
committed
feat(import/markdown): support todo lists in the CKEditor style
1 parent 2edaa2c commit c2b5f0a

File tree

3 files changed

+50
-2
lines changed

3 files changed

+50
-2
lines changed

docs/Release Notes/Release Notes/v0.93.0.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
* Basic Touch Bar support for macOS.
3838
* [Support Bearer Token](https://github.com/TriliumNext/Notes/issues/1701)
3939
* The tab bar is now scrollable when there are many tabs by @SiriusXT
40-
* Markdown export: support todo lists
40+
* Markdown import/export: support todo lists
4141

4242
## 🌍 Internationalization
4343

src/services/import/markdown.spec.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,4 +233,12 @@ second line 2</code></pre><ul><li>Hello</li><li>world</li></ul><ol><li>Hello</li
233233
expect(markdownService.renderToHtml(input, "Title")).toStrictEqual(expected);
234234
});
235235

236+
it("imports todo lists properly", () => {
237+
const input = trimIndentation`\
238+
- [x] Hello
239+
- [ ] World`;
240+
const expected = `<ul class="todo-list"><li><label class="todo-list__label"><input type="checkbox" checked="checked" disabled="disabled"><span class="todo-list__label__description">Hello</span></label></li><li><label class="todo-list__label"><input type="checkbox" disabled="disabled"><span class="todo-list__label__description">World</span></label></li></ul>`;
241+
expect(markdownService.renderToHtml(input, "Title")).toStrictEqual(expected);
242+
});
243+
236244
});

src/services/import/markdown.ts

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,52 @@ class CustomMarkdownRenderer extends Renderer {
4848
}
4949

5050
list(token: Tokens.List): string {
51-
return super.list(token)
51+
let result = super.list(token)
5252
.replace("\n", "") // we replace the first one only.
5353
.trimEnd();
54+
55+
// Handle todo-list in the CKEditor format.
56+
if (token.items.some(item => item.task)) {
57+
result = result.replace(/^<ul>/, "<ul class=\"todo-list\">");
58+
}
59+
60+
return result;
61+
}
62+
63+
checkbox({ checked }: Tokens.Checkbox): string {
64+
return '<input type="checkbox"'
65+
+ (checked ? 'checked="checked" ' : '')
66+
+ 'disabled="disabled">';
5467
}
5568

5669
listitem(item: Tokens.ListItem): string {
70+
// Handle todo-list in the CKEditor format.
71+
if (item.task) {
72+
let itemBody = '';
73+
const checkbox = this.checkbox({ checked: !!item.checked });
74+
if (item.loose) {
75+
if (item.tokens[0]?.type === 'paragraph') {
76+
item.tokens[0].text = checkbox + item.tokens[0].text;
77+
if (item.tokens[0].tokens && item.tokens[0].tokens.length > 0 && item.tokens[0].tokens[0].type === 'text') {
78+
item.tokens[0].tokens[0].text = checkbox + escape(item.tokens[0].tokens[0].text);
79+
item.tokens[0].tokens[0].escaped = true;
80+
}
81+
} else {
82+
item.tokens.unshift({
83+
type: 'text',
84+
raw: checkbox,
85+
text: checkbox,
86+
escaped: true,
87+
});
88+
}
89+
} else {
90+
itemBody += checkbox;
91+
}
92+
93+
itemBody += `<span class="todo-list__label__description">${this.parser.parse(item.tokens, !!item.loose)}</span>`;
94+
return `<li><label class="todo-list__label">${itemBody}</label></li>`;
95+
}
96+
5797
return super.listitem(item).trimEnd();
5898
}
5999

0 commit comments

Comments
 (0)