1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-11-23 22:36:32 +02:00

Desktop: Fixes #13549: OneNote importer: Support converting checklists to Markdown (#13698)

This commit is contained in:
Henry Heino
2025-11-15 00:56:44 -08:00
committed by GitHub
parent e8a083b7bd
commit d792a6b3a9
10 changed files with 385 additions and 529 deletions

View File

@@ -9,6 +9,7 @@ import InteropService from './InteropService';
import InteropService_Importer_OneNote from './InteropService_Importer_OneNote'; import InteropService_Importer_OneNote from './InteropService_Importer_OneNote';
import { JSDOM } from 'jsdom'; import { JSDOM } from 'jsdom';
import { ImportModuleOutputFormat } from './types'; import { ImportModuleOutputFormat } from './types';
import HtmlToMd from '../../HtmlToMd';
const instructionMessage = ` const instructionMessage = `
-------------------------------------- --------------------------------------
@@ -26,6 +27,21 @@ const removeItemIds = (body: string) => {
return body.replace(/:\/[a-z0-9]{32}/g, ':/id-here'); return body.replace(/:\/[a-z0-9]{32}/g, ':/id-here');
}; };
const removeDefaultCss = (body: string) => {
const defaultCssStart = body.indexOf('/*** Start default CSS ***/');
const endMarker = '/*** End default CSS ***/';
const defaultCssEnd = body.indexOf(endMarker);
if (defaultCssEnd === -1 || defaultCssStart === -1) return body;
const before = body.substring(0, defaultCssStart);
const after = body.substring(defaultCssEnd + endMarker.length);
return [before, '/* (For testing: Removed default CSS) */', after].join('\n');
};
const normalizeNoteForSnapshot = (body: string) => {
return removeItemIds(removeDefaultCss(body));
};
// This file is ignored if not running in CI. Look at onenote-converter/README.md and jest.config.js for more information // This file is ignored if not running in CI. Look at onenote-converter/README.md and jest.config.js for more information
describe('InteropService_Importer_OneNote', () => { describe('InteropService_Importer_OneNote', () => {
let tempDir: string; let tempDir: string;
@@ -68,7 +84,7 @@ describe('InteropService_Importer_OneNote', () => {
expectWithInstructions(mainNote.title).toBe('Page title'); expectWithInstructions(mainNote.title).toBe('Page title');
expectWithInstructions(mainNote.markup_language).toBe(MarkupToHtml.MARKUP_LANGUAGE_HTML); expectWithInstructions(mainNote.markup_language).toBe(MarkupToHtml.MARKUP_LANGUAGE_HTML);
expectWithInstructions(mainNote.body).toMatchSnapshot(mainNote.title); expectWithInstructions(normalizeNoteForSnapshot(mainNote.body)).toMatchSnapshot(mainNote.title);
}); });
it('should preserve indentation of subpages in Section page', async () => { it('should preserve indentation of subpages in Section page', async () => {
@@ -121,7 +137,7 @@ describe('InteropService_Importer_OneNote', () => {
expectWithInstructions(notes.filter(n => n.parent_id === parentSection.id).length).toBe(6); expectWithInstructions(notes.filter(n => n.parent_id === parentSection.id).length).toBe(6);
for (const note of notes) { for (const note of notes) {
expectWithInstructions(note.body).toMatchSnapshot(note.title); expectWithInstructions(normalizeNoteForSnapshot(note.body)).toMatchSnapshot(note.title);
} }
BaseModel.setIdGenerator(originalIdGenerator); BaseModel.setIdGenerator(originalIdGenerator);
}); });
@@ -186,7 +202,9 @@ describe('InteropService_Importer_OneNote', () => {
const originalIdGenerator = BaseModel.setIdGenerator(() => String(idx++)); const originalIdGenerator = BaseModel.setIdGenerator(() => String(idx++));
const notes = await importNote(`${supportDir}/onenote/bug_broken_character.zip`); const notes = await importNote(`${supportDir}/onenote/bug_broken_character.zip`);
expectWithInstructions(notes.find(n => n.title === 'Action research - Wikipedia').body).toMatchSnapshot(); expectWithInstructions(
normalizeNoteForSnapshot(notes.find(n => n.title === 'Action research - Wikipedia').body),
).toMatchSnapshot();
BaseModel.setIdGenerator(originalIdGenerator); BaseModel.setIdGenerator(originalIdGenerator);
}); });
@@ -197,7 +215,7 @@ describe('InteropService_Importer_OneNote', () => {
const notes = await importNote(`${supportDir}/onenote/remove_hyperlink_on_title.zip`); const notes = await importNote(`${supportDir}/onenote/remove_hyperlink_on_title.zip`);
for (const note of notes) { for (const note of notes) {
expectWithInstructions(note.body).toMatchSnapshot(note.title); expectWithInstructions(normalizeNoteForSnapshot(note.body)).toMatchSnapshot(note.title);
} }
BaseModel.setIdGenerator(originalIdGenerator); BaseModel.setIdGenerator(originalIdGenerator);
}); });
@@ -217,7 +235,7 @@ describe('InteropService_Importer_OneNote', () => {
const notes = await importNote(`${supportDir}/onenote/hyperlink_marker_as_first_character.zip`); const notes = await importNote(`${supportDir}/onenote/hyperlink_marker_as_first_character.zip`);
for (const note of notes) { for (const note of notes) {
expectWithInstructions(note.body).toMatchSnapshot(note.title); expectWithInstructions(normalizeNoteForSnapshot(note.body)).toMatchSnapshot(note.title);
} }
BaseModel.setIdGenerator(originalIdGenerator); BaseModel.setIdGenerator(originalIdGenerator);
}); });
@@ -230,7 +248,7 @@ describe('InteropService_Importer_OneNote', () => {
expectWithInstructions(notes.length).toBe(2); expectWithInstructions(notes.length).toBe(2);
for (const note of notes) { for (const note of notes) {
expectWithInstructions(note.body).toMatchSnapshot(note.title); expectWithInstructions(normalizeNoteForSnapshot(note.body)).toMatchSnapshot(note.title);
} }
BaseModel.setIdGenerator(originalIdGenerator); BaseModel.setIdGenerator(originalIdGenerator);
}); });
@@ -243,7 +261,7 @@ describe('InteropService_Importer_OneNote', () => {
expectWithInstructions(notes.length).toBe(2); expectWithInstructions(notes.length).toBe(2);
for (const note of notes) { for (const note of notes) {
expectWithInstructions(note.body).toMatchSnapshot(note.title); expectWithInstructions(normalizeNoteForSnapshot(note.body)).toMatchSnapshot(note.title);
} }
BaseModel.setIdGenerator(originalIdGenerator); BaseModel.setIdGenerator(originalIdGenerator);
}); });
@@ -253,11 +271,11 @@ describe('InteropService_Importer_OneNote', () => {
// InkBias bug // InkBias bug
const note1Content = notes.find(n => n.title === 'Marketing Funnel & Training').body; const note1Content = notes.find(n => n.title === 'Marketing Funnel & Training').body;
expect(removeItemIds(note1Content)).toMatchSnapshot(); expect(normalizeNoteForSnapshot(note1Content)).toMatchSnapshot();
// EntityGuid // EntityGuid
const note2Content = notes.find(n => n.title === 'Decrease support costs').body; const note2Content = notes.find(n => n.title === 'Decrease support costs').body;
expect(removeItemIds(note2Content)).toMatchSnapshot(); expect(normalizeNoteForSnapshot(note2Content)).toMatchSnapshot();
}); });
it('should support directly importing .one files', async () => { it('should support directly importing .one files', async () => {
@@ -277,7 +295,16 @@ describe('InteropService_Importer_OneNote', () => {
it('should support importing .one files that contain checkboxes', async () => { it('should support importing .one files that contain checkboxes', async () => {
const notes = await importNote(`${supportDir}/onenote/checkboxes_and_unicode.one`); const notes = await importNote(`${supportDir}/onenote/checkboxes_and_unicode.one`);
expectWithInstructions( expectWithInstructions(
removeItemIds(notes.find(n => n.title.startsWith('Test Todo')).body), normalizeNoteForSnapshot(notes.find(n => n.title.startsWith('Test Todo')).body),
).toMatchSnapshot(); ).toMatchSnapshot();
}); });
it('should correctly convert imported notes to Markdown', async () => {
const notes = await importNote(`${supportDir}/onenote/checkboxes_and_unicode.one`);
const checklistNote = notes.find(n => n.title.startsWith('Test Todo'));
const converter = new HtmlToMd();
const markdown = converter.parse(checklistNote.body);
expect(markdown).toMatchSnapshot('Test Todo: As Markdown');
});
}); });

View File

@@ -104,19 +104,9 @@ exports[`InteropService_Importer_OneNote should be able to create notes from cor
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>title</title> <title>title</title>
<style> <style>
* { margin: 0; padding: 0; font-weight: normal; }
table, tr, td { border-color: #A3A3A3; }
ul, ol { padding: 0; }
.title .outline-element { display: inline; }
.title .outline-element:nth-child(2) { margin-left: 10px !important; }
.container-outline { font-family: Calibri, sans-serif; font-size: 6pt; }
.ink-text, .ink-space { display: inline-block; position: relative; vertical-align: bottom; }
.ink-text { top: 0; left: 0; }
.note-tag-icon { position: relative; }
.note-tag-icon > svg, .note-tag-icon > img { position: absolute; } /* (For testing: Removed default CSS) */
.icon-secondary > svg, .icon-secondary > img { position: absolute; fill: black; filter: drop-shadow(0 0 2px white); height: 12px; top: -1px; }
.icon-secondary > .content { position: absolute; color: black; filter: drop-shadow(0 0 2px white); font-size: 10px; color: black; top: -1px; user-select: none; }
</style> </style>
@@ -138,25 +128,41 @@ exports[`InteropService_Importer_OneNote should be able to create notes from cor
</html>" </html>"
`; `;
exports[`InteropService_Importer_OneNote should correctly convert imported notes to Markdown: Test Todo: As Markdown 1`] = `
" Test Todo : cases à cocher lien vers doc sur partage
Test Todo : cases à cocher lien vers doc sur partage
jeudi 23 octobre 2025
09:24
## Todo HBA
- [x] Rédiger carnet MS-OneNote jeu d'essai (case cochée)
- [ ] Transmettre aux dev Joplin (case non cochée)
- [x] Exporter le carnet en \\*.packageone (case cochée)
- [ ] Exporter des page en \\*.one (case non cochée)
&nbsp;
## Todo Team appli
- [ ] Rédiger compte-rendu réunion de service
- [ ] Rédiger présentation service Joplin et fonctionnalités complémentaires associée à JBS
la doc sur le partage : [\\\\\\\\mondomaine.local\\\\partage\\\\DSI\\\\Projets Techniques 2025\\\\Remplacement Office\\\\JOPLIN_Alternatives_OneNote\\\\formation](file:///\\\\mondomaine.local\\partage\\DSI\\Projets%20Techniques%202025\\Remplacement%20Office\\JOPLIN_Alternatives_OneNote\\formation)
- [x] Documenter configuration synchro JBS saml pour un utilisateur (case cochée)"
`;
exports[`InteropService_Importer_OneNote should expect notes to be rendered the same: A page can have any width it wants 1`] = ` exports[`InteropService_Importer_OneNote should expect notes to be rendered the same: A page can have any width it wants 1`] = `
"<!DOCTYPE HTML> "<!DOCTYPE HTML>
<html lang="en"><head> <html lang="en"><head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>A page can have any width it wants?</title> <title>A page can have any width it wants?</title>
<style> <style>
* { margin: 0; padding: 0; font-weight: normal; }
table, tr, td { border-color: #A3A3A3; }
ul, ol { padding: 0; }
.title .outline-element { display: inline; }
.title .outline-element:nth-child(2) { margin-left: 10px !important; }
.container-outline { font-family: Calibri, sans-serif; font-size: 6pt; }
.ink-text, .ink-space { display: inline-block; position: relative; vertical-align: bottom; }
.ink-text { top: 0; left: 0; }
.note-tag-icon { position: relative; }
.note-tag-icon > svg, .note-tag-icon > img { position: absolute; } /* (For testing: Removed default CSS) */
.icon-secondary > svg, .icon-secondary > img { position: absolute; fill: black; filter: drop-shadow(0 0 2px white); height: 12px; top: -1px; }
.icon-secondary > .content { position: absolute; color: black; filter: drop-shadow(0 0 2px white); font-size: 10px; color: black; top: -1px; user-select: none; }
</style> </style>
@@ -186,19 +192,9 @@ exports[`InteropService_Importer_OneNote should expect notes to be rendered the
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>A page with a lot of svgs</title> <title>A page with a lot of svgs</title>
<style> <style>
* { margin: 0; padding: 0; font-weight: normal; }
table, tr, td { border-color: #A3A3A3; }
ul, ol { padding: 0; }
.title .outline-element { display: inline; }
.title .outline-element:nth-child(2) { margin-left: 10px !important; }
.container-outline { font-family: Calibri, sans-serif; font-size: 6pt; }
.ink-text, .ink-space { display: inline-block; position: relative; vertical-align: bottom; }
.ink-text { top: 0; left: 0; }
.note-tag-icon { position: relative; }
.note-tag-icon > svg, .note-tag-icon > img { position: absolute; } /* (For testing: Removed default CSS) */
.icon-secondary > svg, .icon-secondary > img { position: absolute; fill: black; filter: drop-shadow(0 0 2px white); height: 12px; top: -1px; }
.icon-secondary > .content { position: absolute; color: black; filter: drop-shadow(0 0 2px white); font-size: 10px; color: black; top: -1px; user-select: none; }
</style> </style>
@@ -226,19 +222,9 @@ exports[`InteropService_Importer_OneNote should expect notes to be rendered the
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>A page with text and drawing above it</title> <title>A page with text and drawing above it</title>
<style> <style>
* { margin: 0; padding: 0; font-weight: normal; }
table, tr, td { border-color: #A3A3A3; }
ul, ol { padding: 0; }
.title .outline-element { display: inline; }
.title .outline-element:nth-child(2) { margin-left: 10px !important; }
.container-outline { font-family: Calibri, sans-serif; font-size: 6pt; }
.ink-text, .ink-space { display: inline-block; position: relative; vertical-align: bottom; }
.ink-text { top: 0; left: 0; }
.note-tag-icon { position: relative; }
.note-tag-icon > svg, .note-tag-icon > img { position: absolute; } /* (For testing: Removed default CSS) */
.icon-secondary > svg, .icon-secondary > img { position: absolute; fill: black; filter: drop-shadow(0 0 2px white); height: 12px; top: -1px; }
.icon-secondary > .content { position: absolute; color: black; filter: drop-shadow(0 0 2px white); font-size: 10px; color: black; top: -1px; user-select: none; }
</style> </style>
@@ -272,19 +258,9 @@ exports[`InteropService_Importer_OneNote should expect notes to be rendered the
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>A simple filename</title> <title>A simple filename</title>
<style> <style>
* { margin: 0; padding: 0; font-weight: normal; }
table, tr, td { border-color: #A3A3A3; }
ul, ol { padding: 0; }
.title .outline-element { display: inline; }
.title .outline-element:nth-child(2) { margin-left: 10px !important; }
.container-outline { font-family: Calibri, sans-serif; font-size: 6pt; }
.ink-text, .ink-space { display: inline-block; position: relative; vertical-align: bottom; }
.ink-text { top: 0; left: 0; }
.note-tag-icon { position: relative; }
.note-tag-icon > svg, .note-tag-icon > img { position: absolute; } /* (For testing: Removed default CSS) */
.icon-secondary > svg, .icon-secondary > img { position: absolute; fill: black; filter: drop-shadow(0 0 2px white); height: 12px; top: -1px; }
.icon-secondary > .content { position: absolute; color: black; filter: drop-shadow(0 0 2px white); font-size: 10px; color: black; top: -1px; user-select: none; }
</style> </style>
@@ -312,19 +288,9 @@ exports[`InteropService_Importer_OneNote should expect notes to be rendered the
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Page with more than one font size</title> <title>Page with more than one font size</title>
<style> <style>
* { margin: 0; padding: 0; font-weight: normal; }
table, tr, td { border-color: #A3A3A3; }
ul, ol { padding: 0; }
.title .outline-element { display: inline; }
.title .outline-element:nth-child(2) { margin-left: 10px !important; }
.container-outline { font-family: Calibri, sans-serif; font-size: 6pt; }
.ink-text, .ink-space { display: inline-block; position: relative; vertical-align: bottom; }
.ink-text { top: 0; left: 0; }
.note-tag-icon { position: relative; }
.note-tag-icon > svg, .note-tag-icon > img { position: absolute; } /* (For testing: Removed default CSS) */
.icon-secondary > svg, .icon-secondary > img { position: absolute; fill: black; filter: drop-shadow(0 0 2px white); height: 12px; top: -1px; }
.icon-secondary > .content { position: absolute; color: black; filter: drop-shadow(0 0 2px white); font-size: 10px; color: black; top: -1px; user-select: none; }
</style> </style>
@@ -462,19 +428,9 @@ exports[`InteropService_Importer_OneNote should expect notes to be rendered the
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>text</title> <title>text</title>
<style> <style>
* { margin: 0; padding: 0; font-weight: normal; }
table, tr, td { border-color: #A3A3A3; }
ul, ol { padding: 0; }
.title .outline-element { display: inline; }
.title .outline-element:nth-child(2) { margin-left: 10px !important; }
.container-outline { font-family: Calibri, sans-serif; font-size: 6pt; }
.ink-text, .ink-space { display: inline-block; position: relative; vertical-align: bottom; }
.ink-text { top: 0; left: 0; }
.note-tag-icon { position: relative; }
.note-tag-icon > svg, .note-tag-icon > img { position: absolute; } /* (For testing: Removed default CSS) */
.icon-secondary > svg, .icon-secondary > img { position: absolute; fill: black; filter: drop-shadow(0 0 2px white); height: 12px; top: -1px; }
.icon-secondary > .content { position: absolute; color: black; filter: drop-shadow(0 0 2px white); font-size: 10px; color: black; top: -1px; user-select: none; }
</style> </style>
@@ -814,19 +770,9 @@ exports[`InteropService_Importer_OneNote should ignore broken characters at the
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Action research - Wikipedia</title> <title>Action research - Wikipedia</title>
<style> <style>
* { margin: 0; padding: 0; font-weight: normal; }
table, tr, td { border-color: #A3A3A3; }
ul, ol { padding: 0; }
.title .outline-element { display: inline; }
.title .outline-element:nth-child(2) { margin-left: 10px !important; }
.container-outline { font-family: Calibri, sans-serif; font-size: 6pt; }
.ink-text, .ink-space { display: inline-block; position: relative; vertical-align: bottom; }
.ink-text { top: 0; left: 0; }
.note-tag-icon { position: relative; }
.note-tag-icon > svg, .note-tag-icon > img { position: absolute; } /* (For testing: Removed default CSS) */
.icon-secondary > svg, .icon-secondary > img { position: absolute; fill: black; filter: drop-shadow(0 0 2px white); height: 12px; top: -1px; }
.icon-secondary > .content { position: absolute; color: black; filter: drop-shadow(0 0 2px white); font-size: 10px; color: black; top: -1px; user-select: none; }
.list-0 li { padding-left: 10px; } .list-0 li { padding-left: 10px; }
.list-0 li::marker { content: '•'; font-family: Georgia; } .list-0 li::marker { content: '•'; font-family: Georgia; }
@@ -887,19 +833,9 @@ exports[`InteropService_Importer_OneNote should import a simple OneNote notebook
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Page title</title> <title>Page title</title>
<style> <style>
* { margin: 0; padding: 0; font-weight: normal; }
table, tr, td { border-color: #A3A3A3; }
ul, ol { padding: 0; }
.title .outline-element { display: inline; }
.title .outline-element:nth-child(2) { margin-left: 10px !important; }
.container-outline { font-family: Calibri, sans-serif; font-size: 6pt; }
.ink-text, .ink-space { display: inline-block; position: relative; vertical-align: bottom; }
.ink-text { top: 0; left: 0; }
.note-tag-icon { position: relative; }
.note-tag-icon > svg, .note-tag-icon > img { position: absolute; } /* (For testing: Removed default CSS) */
.icon-secondary > svg, .icon-secondary > img { position: absolute; fill: black; filter: drop-shadow(0 0 2px white); height: 12px; top: -1px; }
.icon-secondary > .content { position: absolute; color: black; filter: drop-shadow(0 0 2px white); font-size: 10px; color: black; top: -1px; user-select: none; }
</style> </style>
@@ -1031,19 +967,9 @@ exports[`InteropService_Importer_OneNote should remove hyperlink from title: Tip
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Tips from a Pro: Using Trees for Dramatic Landscape Photography</title> <title>Tips from a Pro: Using Trees for Dramatic Landscape Photography</title>
<style> <style>
* { margin: 0; padding: 0; font-weight: normal; }
table, tr, td { border-color: #A3A3A3; }
ul, ol { padding: 0; }
.title .outline-element { display: inline; }
.title .outline-element:nth-child(2) { margin-left: 10px !important; }
.container-outline { font-family: Calibri, sans-serif; font-size: 6pt; }
.ink-text, .ink-space { display: inline-block; position: relative; vertical-align: bottom; }
.ink-text { top: 0; left: 0; }
.note-tag-icon { position: relative; }
.note-tag-icon > svg, .note-tag-icon > img { position: absolute; } /* (For testing: Removed default CSS) */
.icon-secondary > svg, .icon-secondary > img { position: absolute; fill: black; filter: drop-shadow(0 0 2px white); height: 12px; top: -1px; }
.icon-secondary > .content { position: absolute; color: black; filter: drop-shadow(0 0 2px white); font-size: 10px; color: black; top: -1px; user-select: none; }
</style> </style>
@@ -1072,19 +998,9 @@ exports[`InteropService_Importer_OneNote should remove hyperlink from title: wik
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>wikipedia link</title> <title>wikipedia link</title>
<style> <style>
* { margin: 0; padding: 0; font-weight: normal; }
table, tr, td { border-color: #A3A3A3; }
ul, ol { padding: 0; }
.title .outline-element { display: inline; }
.title .outline-element:nth-child(2) { margin-left: 10px !important; }
.container-outline { font-family: Calibri, sans-serif; font-size: 6pt; }
.ink-text, .ink-space { display: inline-block; position: relative; vertical-align: bottom; }
.ink-text { top: 0; left: 0; }
.note-tag-icon { position: relative; }
.note-tag-icon > svg, .note-tag-icon > img { position: absolute; } /* (For testing: Removed default CSS) */
.icon-secondary > svg, .icon-secondary > img { position: absolute; fill: black; filter: drop-shadow(0 0 2px white); height: 12px; top: -1px; }
.icon-secondary > .content { position: absolute; color: black; filter: drop-shadow(0 0 2px white); font-size: 10px; color: black; top: -1px; user-select: none; }
</style> </style>
@@ -1113,19 +1029,9 @@ exports[`InteropService_Importer_OneNote should remove hyperlink from title: 风
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>风景 (Web view)</title> <title>风景 (Web view)</title>
<style> <style>
* { margin: 0; padding: 0; font-weight: normal; }
table, tr, td { border-color: #A3A3A3; }
ul, ol { padding: 0; }
.title .outline-element { display: inline; }
.title .outline-element:nth-child(2) { margin-left: 10px !important; }
.container-outline { font-family: Calibri, sans-serif; font-size: 6pt; }
.ink-text, .ink-space { display: inline-block; position: relative; vertical-align: bottom; }
.ink-text { top: 0; left: 0; }
.note-tag-icon { position: relative; }
.note-tag-icon > svg, .note-tag-icon > img { position: absolute; } /* (For testing: Removed default CSS) */
.icon-secondary > svg, .icon-secondary > img { position: absolute; fill: black; filter: drop-shadow(0 0 2px white); height: 12px; top: -1px; }
.icon-secondary > .content { position: absolute; color: black; filter: drop-shadow(0 0 2px white); font-size: 10px; color: black; top: -1px; user-select: none; }
</style> </style>
@@ -1154,19 +1060,9 @@ exports[`InteropService_Importer_OneNote should remove hyperlink from title: 风
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>风景</title> <title>风景</title>
<style> <style>
* { margin: 0; padding: 0; font-weight: normal; }
table, tr, td { border-color: #A3A3A3; }
ul, ol { padding: 0; }
.title .outline-element { display: inline; }
.title .outline-element:nth-child(2) { margin-left: 10px !important; }
.container-outline { font-family: Calibri, sans-serif; font-size: 6pt; }
.ink-text, .ink-space { display: inline-block; position: relative; vertical-align: bottom; }
.ink-text { top: 0; left: 0; }
.note-tag-icon { position: relative; }
.note-tag-icon > svg, .note-tag-icon > img { position: absolute; } /* (For testing: Removed default CSS) */
.icon-secondary > svg, .icon-secondary > img { position: absolute; fill: black; filter: drop-shadow(0 0 2px white); height: 12px; top: -1px; }
.icon-secondary > .content { position: absolute; color: black; filter: drop-shadow(0 0 2px white); font-size: 10px; color: black; top: -1px; user-select: none; }
</style> </style>
@@ -1194,19 +1090,9 @@ exports[`InteropService_Importer_OneNote should render audio as links to resourc
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>My title</title> <title>My title</title>
<style> <style>
* { margin: 0; padding: 0; font-weight: normal; }
table, tr, td { border-color: #A3A3A3; }
ul, ol { padding: 0; }
.title .outline-element { display: inline; }
.title .outline-element:nth-child(2) { margin-left: 10px !important; }
.container-outline { font-family: Calibri, sans-serif; font-size: 6pt; }
.ink-text, .ink-space { display: inline-block; position: relative; vertical-align: bottom; }
.ink-text { top: 0; left: 0; }
.note-tag-icon { position: relative; }
.note-tag-icon > svg, .note-tag-icon > img { position: absolute; } /* (For testing: Removed default CSS) */
.icon-secondary > svg, .icon-secondary > img { position: absolute; fill: black; filter: drop-shadow(0 0 2px white); height: 12px; top: -1px; }
.icon-secondary > .content { position: absolute; color: black; filter: drop-shadow(0 0 2px white); font-size: 10px; color: black; top: -1px; user-select: none; }
</style> </style>
@@ -1337,19 +1223,9 @@ exports[`InteropService_Importer_OneNote should render links properly by ignorin
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Is Mexico safe for shooting Street Photography?</title> <title>Is Mexico safe for shooting Street Photography?</title>
<style> <style>
* { margin: 0; padding: 0; font-weight: normal; }
table, tr, td { border-color: #A3A3A3; }
ul, ol { padding: 0; }
.title .outline-element { display: inline; }
.title .outline-element:nth-child(2) { margin-left: 10px !important; }
.container-outline { font-family: Calibri, sans-serif; font-size: 6pt; }
.ink-text, .ink-space { display: inline-block; position: relative; vertical-align: bottom; }
.ink-text { top: 0; left: 0; }
.note-tag-icon { position: relative; }
.note-tag-icon > svg, .note-tag-icon > img { position: absolute; } /* (For testing: Removed default CSS) */
.icon-secondary > svg, .icon-secondary > img { position: absolute; fill: black; filter: drop-shadow(0 0 2px white); height: 12px; top: -1px; }
.icon-secondary > .content { position: absolute; color: black; filter: drop-shadow(0 0 2px white); font-size: 10px; color: black; top: -1px; user-select: none; }
</style> </style>
@@ -1476,27 +1352,17 @@ exports[`InteropService_Importer_OneNote should support importing .one files tha
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Test Todo : cases à cocher lien vers doc sur partage</title> <title>Test Todo : cases à cocher lien vers doc sur partage</title>
<style> <style>
* { margin: 0; padding: 0; font-weight: normal; }
table, tr, td { border-color: #A3A3A3; }
ul, ol { padding: 0; }
.title .outline-element { display: inline; }
.title .outline-element:nth-child(2) { margin-left: 10px !important; }
.container-outline { font-family: Calibri, sans-serif; font-size: 6pt; }
.ink-text, .ink-space { display: inline-block; position: relative; vertical-align: bottom; }
.ink-text { top: 0; left: 0; }
.note-tag-icon { position: relative; }
.note-tag-icon > svg, .note-tag-icon > img { position: absolute; } /* (For testing: Removed default CSS) */
.icon-secondary > svg, .icon-secondary > img { position: absolute; fill: black; filter: drop-shadow(0 0 2px white); height: 12px; top: -1px; }
.icon-secondary > .content { position: absolute; color: black; filter: drop-shadow(0 0 2px white); font-size: 10px; color: black; top: -1px; user-select: none; }
.icon-0 > svg, .icon-0 > img { fill: #4673b7; height: 20px; left: -25px; width: 20px; }
.icon-1 > svg, .icon-1 > img { fill: #4673b7; height: 20px; left: -25px; width: 20px; } .icon-0 > svg, .icon-0 > img { fill: #4673b7; }
.icon-2 > svg, .icon-2 > img { fill: #4673b7; height: 20px; left: -25px; width: 20px; } .icon-1 > svg, .icon-1 > img { fill: #4673b7; }
.icon-3 > svg, .icon-3 > img { fill: #4673b7; height: 20px; left: -25px; width: 20px; } .icon-2 > svg, .icon-2 > img { fill: #4673b7; }
.icon-4 > svg, .icon-4 > img { fill: #4673b7; height: 20px; left: -25px; width: 20px; } .icon-3 > svg, .icon-3 > img { fill: #4673b7; }
.icon-5 > svg, .icon-5 > img { fill: #4673b7; height: 20px; left: -25px; width: 20px; } .icon-4 > svg, .icon-4 > img { fill: #4673b7; }
.icon-6 > svg, .icon-6 > img { fill: #4673b7; height: 20px; left: -25px; width: 20px; } .icon-5 > svg, .icon-5 > img { fill: #4673b7; }
.icon-6 > svg, .icon-6 > img { fill: #4673b7; }
</style> </style>
</head> </head>
@@ -1506,23 +1372,23 @@ exports[`InteropService_Importer_OneNote should support importing .one files tha
</div><div class="container-outline"><div class="outline-element" style="margin-left: 0px;"><span style="color: rgb(118,118,118); font-family: Calibri; font-size: 10pt; line-height: 16px;">jeudi 23 octobre 2025</span></div> </div><div class="container-outline"><div class="outline-element" style="margin-left: 0px;"><span style="color: rgb(118,118,118); font-family: Calibri; font-size: 10pt; line-height: 16px;">jeudi 23 octobre 2025</span></div>
<div class="outline-element" style="margin-left: 0px;"><span style="color: rgb(118,118,118); font-family: Calibri; font-size: 10pt; line-height: 16px;">09:24</span></div> <div class="outline-element" style="margin-left: 0px;"><span style="color: rgb(118,118,118); font-family: Calibri; font-size: 10pt; line-height: 16px;">09:24</span></div>
</div></div><div class="container-outline" style="left: 48px; position: absolute; top: 115px; width: 720px;"><div class="outline-element" style="margin-left: 0px;"><h2 style="color: rgb(46,117,181); font-family: Calibri; font-size: 14pt; line-height: 22px;">Todo HBA</h2></div> </div></div><div class="container-outline" style="left: 48px; position: absolute; top: 115px; width: 720px;"><div class="outline-element" style="margin-left: 0px;"><h2 style="color: rgb(46,117,181); font-family: Calibri; font-size: 14pt; line-height: 22px;">Todo HBA</h2></div>
<div class="outline-element" style="margin-left: 0px;"><p style="font-family: Calibri; font-size: 11pt; line-height: 17px;"><span class="note-tag-icon icon-0"><img alt="Checked" src=":/id-here"> <ul class="tagged-list"><li class="outline-element" style="margin-left: 0px;"><span style="font-family: Calibri; font-size: 11pt; line-height: 17px;"><span aria-checked="true" aria-disabled="true" class="note-tag-icon icon-0 -checkbox -large" role="checkbox"><img alt="Checked" src=":/id-here">
</span>Rédiger carnet MS-OneNote jeu d'essai (case cochée)</p></div> </span>Rédiger carnet MS-OneNote jeu d'essai (case cochée)</span></li>
<div class="outline-element" style="margin-left: 0px;"><p style="font-family: Calibri; font-size: 11pt; line-height: 17px;"><span class="note-tag-icon icon-1"><img class="checkbox-icon" alt="Unchecked" src=":/id-here"> <li class="outline-element" style="margin-left: 0px;"><span style="font-family: Calibri; font-size: 11pt; line-height: 17px;"><span aria-checked="false" aria-disabled="true" class="note-tag-icon icon-1 -checkbox -large" role="checkbox"><img class="checkbox-icon" alt="Unchecked" src=":/id-here">
</span>Transmettre aux dev Joplin (case non cochée)</p></div><div class="outline-element" style="margin-left: 36px;"><p style="font-family: Calibri; font-size: 11pt; line-height: 17px;"><span class="note-tag-icon icon-2"><img alt="Checked" src=":/id-here"> </span>Transmettre aux dev Joplin (case non cochée)</span><ul class="tagged-list"><li class="outline-element" style="margin-left: 36px;"><span style="font-family: Calibri; font-size: 11pt; line-height: 17px;"><span aria-checked="true" aria-disabled="true" class="note-tag-icon icon-2 -checkbox -large" role="checkbox"><img alt="Checked" src=":/id-here">
</span>Exporter le carnet en *.packageone (case cochée)</p></div> </span>Exporter le carnet en *.packageone (case cochée)</span></li>
<div class="outline-element" style="margin-left: 36px;"><p style="font-family: Calibri; font-size: 11pt; line-height: 17px;"><span class="note-tag-icon icon-3"><img class="checkbox-icon" alt="Unchecked" src=":/id-here"> <li class="outline-element" style="margin-left: 36px;"><span style="font-family: Calibri; font-size: 11pt; line-height: 17px;"><span aria-checked="false" aria-disabled="true" class="note-tag-icon icon-3 -checkbox -large" role="checkbox"><img class="checkbox-icon" alt="Unchecked" src=":/id-here">
</span>Exporter des page en *.one (case non cochée)</p></div> </span>Exporter des page en *.one (case non cochée)</span></li>
</ul></li>
<div class="outline-element" style="margin-left: 0px;"><p style="font-family: Calibri; font-size: 11pt; line-height: 17px;">&nbsp;</p></div> </ul><div class="outline-element" style="margin-left: 0px;"><p style="font-family: Calibri; font-size: 11pt; line-height: 17px;">&nbsp;</p></div>
<div class="outline-element" style="margin-left: 0px;"><h2 style="color: rgb(46,117,181); font-family: Calibri; font-size: 14pt; line-height: 22px;">Todo Team appli</h2></div> <div class="outline-element" style="margin-left: 0px;"><h2 style="color: rgb(46,117,181); font-family: Calibri; font-size: 14pt; line-height: 22px;">Todo Team appli</h2></div>
<div class="outline-element" style="margin-left: 0px;"><p style="font-family: Calibri; font-size: 11pt; line-height: 17px;"><span class="note-tag-icon icon-4"><img class="checkbox-icon" alt="Unchecked" src=":/id-here"> <ul class="tagged-list"><li class="outline-element" style="margin-left: 0px;"><span style="font-family: Calibri; font-size: 11pt; line-height: 17px;"><span aria-checked="false" aria-disabled="true" class="note-tag-icon icon-4 -checkbox -large" role="checkbox"><img class="checkbox-icon" alt="Unchecked" src=":/id-here">
</span>Rédiger compte-rendu réunion de service</p></div> </span>Rédiger compte-rendu réunion de service</span></li>
<div class="outline-element" style="margin-left: 0px;"><p style="font-family: Calibri; font-size: 11pt; line-height: 17px;"><span class="note-tag-icon icon-5"><img class="checkbox-icon" alt="Unchecked" src=":/id-here"> <li class="outline-element" style="margin-left: 0px;"><span style="font-family: Calibri; font-size: 11pt; line-height: 17px;"><span aria-checked="false" aria-disabled="true" class="note-tag-icon icon-5 -checkbox -large" role="checkbox"><img class="checkbox-icon" alt="Unchecked" src=":/id-here">
</span>Rédiger présentation service Joplin et fonctionnalités complémentaires associée à JBS<br>la doc sur le partage : <a href="file:///\\\\mondomaine.local\\partage\\DSI\\Projets%20Techniques%202025\\Remplacement%20Office\\JOPLIN_Alternatives_OneNote\\formation" style="">\\\\mondomaine.local\\partage\\DSI\\Projets Techniques 2025\\Remplacement Office\\JOPLIN_Alternatives_OneNote\\formation</a> </p></div><div class="outline-element" style="margin-left: 36px;"><p style="font-family: Calibri; font-size: 11pt; line-height: 17px;"><span class="note-tag-icon icon-6"><img alt="Checked" src=":/id-here"> </span>Rédiger présentation service Joplin et fonctionnalités complémentaires associée à JBS<br>la doc sur le partage : <a href="file:///\\\\mondomaine.local\\partage\\DSI\\Projets%20Techniques%202025\\Remplacement%20Office\\JOPLIN_Alternatives_OneNote\\formation" style="">\\\\mondomaine.local\\partage\\DSI\\Projets Techniques 2025\\Remplacement Office\\JOPLIN_Alternatives_OneNote\\formation</a> </span><ul class="tagged-list"><li class="outline-element" style="margin-left: 36px;"><span style="font-family: Calibri; font-size: 11pt; line-height: 17px;"><span aria-checked="true" aria-disabled="true" class="note-tag-icon icon-6 -checkbox -large" role="checkbox"><img alt="Checked" src=":/id-here">
</span>Documenter configuration synchro JBS saml pour un utilisateur (case cochée)</p></div> </span>Documenter configuration synchro JBS saml pour un utilisateur (case cochée)</span></li>
</ul></li>
</div> </ul></div>
<script> <script>
if (window.parent !== null) { if (window.parent !== null) {
@@ -1539,19 +1405,9 @@ exports[`InteropService_Importer_OneNote should use default value for EntityGuid
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Marketing Funnel &amp; Training</title> <title>Marketing Funnel &amp; Training</title>
<style> <style>
* { margin: 0; padding: 0; font-weight: normal; }
table, tr, td { border-color: #A3A3A3; }
ul, ol { padding: 0; }
.title .outline-element { display: inline; }
.title .outline-element:nth-child(2) { margin-left: 10px !important; }
.container-outline { font-family: Calibri, sans-serif; font-size: 6pt; }
.ink-text, .ink-space { display: inline-block; position: relative; vertical-align: bottom; }
.ink-text { top: 0; left: 0; }
.note-tag-icon { position: relative; }
.note-tag-icon > svg, .note-tag-icon > img { position: absolute; } /* (For testing: Removed default CSS) */
.icon-secondary > svg, .icon-secondary > img { position: absolute; fill: black; filter: drop-shadow(0 0 2px white); height: 12px; top: -1px; }
.icon-secondary > .content { position: absolute; color: black; filter: drop-shadow(0 0 2px white); font-size: 10px; color: black; top: -1px; user-select: none; }
</style> </style>
@@ -1586,19 +1442,9 @@ exports[`InteropService_Importer_OneNote should use default value for EntityGuid
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Decrease support costs</title> <title>Decrease support costs</title>
<style> <style>
* { margin: 0; padding: 0; font-weight: normal; }
table, tr, td { border-color: #A3A3A3; }
ul, ol { padding: 0; }
.title .outline-element { display: inline; }
.title .outline-element:nth-child(2) { margin-left: 10px !important; }
.container-outline { font-family: Calibri, sans-serif; font-size: 6pt; }
.ink-text, .ink-space { display: inline-block; position: relative; vertical-align: bottom; }
.ink-text { top: 0; left: 0; }
.note-tag-icon { position: relative; }
.note-tag-icon > svg, .note-tag-icon > img { position: absolute; } /* (For testing: Removed default CSS) */
.icon-secondary > svg, .icon-secondary > img { position: absolute; fill: black; filter: drop-shadow(0 0 2px white); height: 12px; top: -1px; }
.icon-secondary > .content { position: absolute; color: black; filter: drop-shadow(0 0 2px white); font-size: 10px; color: black; top: -1px; user-select: none; }
</style> </style>

View File

@@ -18,13 +18,17 @@ impl<'a> Renderer<'a> {
let mut list_end = None; let mut list_end = None;
for (element, parent_level, current_level) in elements { for (element, parent_level, current_level) in elements {
if !in_list && self.is_list(element) { if !in_list && self.is_onenote_list(element) {
let tags = self.list_tags(element); let tags = self.list_tags(element);
let list_start = tags.0; let list_start = tags.0;
list_end = Some(tags.1); list_end = Some(tags.1);
contents.push_str(&list_start); contents.push_str(&list_start);
in_list = true; in_list = true;
} else if !in_list && self.is_tag_list(element) {
contents.push_str("<ul class=\"tagged-list\">");
list_end = Some("</ul>".into());
in_list = true;
} }
if in_list && !self.is_list(element) { if in_list && !self.is_list(element) {
@@ -176,7 +180,15 @@ impl<'a> Renderer<'a> {
.unwrap_or_default() .unwrap_or_default()
} }
fn is_onenote_list(&self, element: &OutlineElement) -> bool {
!element.list_contents().is_empty()
}
fn is_tag_list(&self, element: &OutlineElement) -> bool {
self.has_note_tag(element)
}
pub(crate) fn is_list(&self, element: &OutlineElement) -> bool { pub(crate) fn is_list(&self, element: &OutlineElement) -> bool {
element.list_contents().first().is_some() self.is_onenote_list(element) || self.is_tag_list(element)
} }
} }

View File

@@ -1,5 +1,5 @@
use crate::page::Renderer; use crate::page::Renderer;
use crate::utils::StyleSet; use crate::utils::{AttributeSet, StyleSet};
use parser::contents::{NoteTag, OutlineElement}; use parser::contents::{NoteTag, OutlineElement};
use parser::property::common::ColorRef; use parser::property::common::ColorRef;
use parser::property::note_tag::{ActionItemStatus, NoteTagShape}; use parser::property::note_tag::{ActionItemStatus, NoteTagShape};
@@ -45,6 +45,35 @@ enum IconSize {
Large, Large,
} }
struct NoteTagIcon {
html: Cow<'static, str>,
size: IconSize,
styles: StyleSet,
is_checkbox: bool,
}
impl From<(Cow<'static, str>, IconSize)> for NoteTagIcon {
fn from((html, size): (Cow<'static, str>, IconSize)) -> Self {
Self {
html,
size,
styles: StyleSet::new(),
is_checkbox: false,
}
}
}
impl From<(Cow<'static, str>, IconSize, StyleSet)> for NoteTagIcon {
fn from((html, size, styles): (Cow<'static, str>, IconSize, StyleSet)) -> Self {
Self {
html,
size,
styles,
is_checkbox: false,
}
}
}
impl<'a> Renderer<'a> { impl<'a> Renderer<'a> {
pub(crate) fn render_with_note_tags( pub(crate) fn render_with_note_tags(
&mut self, &mut self,
@@ -63,6 +92,55 @@ impl<'a> Renderer<'a> {
} }
} }
fn build_note_tag_class_names(&mut self, icon: &NoteTagIcon) -> Vec<String> {
let mut icon_classes = vec!["note-tag-icon".to_string()];
if icon.styles.len() > 0 {
let class = self.gen_class("icon");
icon_classes.push(class.to_string());
self.global_styles
// Select both `svg` and `img`: `svg`s may be replaced with `img` later in the import process:
.insert(
format!(".{} > svg, .{} > img", class, class),
icon.styles.clone(),
);
}
if icon.is_checkbox {
icon_classes.push("-checkbox".into());
}
if icon.size == IconSize::Large {
icon_classes.push("-large".into());
} else if icon.size == IconSize::Normal {
icon_classes.push("-normal".into());
}
icon_classes
}
fn get_note_tag_attrs(
&mut self,
icon: &NoteTagIcon,
status: ActionItemStatus,
class_names: &[String],
) -> AttributeSet {
let mut attrs = AttributeSet::new();
attrs.set("class", class_names.join(" "));
if icon.is_checkbox {
attrs.set("role", "checkbox".into());
attrs.set(
"aria-checked",
if status.completed() { "true" } else { "false" }.into(),
);
attrs.set("aria-disabled", "true".into());
}
attrs
}
pub(crate) fn render_note_tags(&mut self, note_tags: &[NoteTag]) -> Option<(String, StyleSet)> { pub(crate) fn render_note_tags(&mut self, note_tags: &[NoteTag]) -> Option<(String, StyleSet)> {
let mut markup = String::new(); let mut markup = String::new();
let mut styles = StyleSet::new(); let mut styles = StyleSet::new();
@@ -82,24 +160,12 @@ impl<'a> Renderer<'a> {
} }
if def.shape() != NoteTagShape::NoIcon { if def.shape() != NoteTagShape::NoIcon {
let (icon, icon_style) = let icon = self.note_tag_icon(def.shape(), note_tag.item_status());
self.note_tag_icon(def.shape(), note_tag.item_status()); let icon_classes = self.build_note_tag_class_names(&icon);
let mut icon_classes = vec!["note-tag-icon".to_string()]; let attrs =
self.get_note_tag_attrs(&icon, note_tag.item_status(), &icon_classes);
if icon_style.len() > 0 { markup.push_str(&format!("<span {}>{}</span>", attrs, icon.html,));
let class = self.gen_class("icon");
icon_classes.push(class.to_string());
self.global_styles
// Select both `svg` and `img`: `svg`s may be replaced with `img` later in the import process:
.insert(format!(".{} > svg, .{} > img", class, class), icon_style);
}
markup.push_str(&format!(
"<span class=\"{}\">{}</span>",
icon_classes.join(" "),
icon
));
} }
} }
} }
@@ -115,291 +181,197 @@ impl<'a> Renderer<'a> {
.any(|text| !text.note_tags().is_empty()) .any(|text| !text.note_tags().is_empty())
} }
fn note_tag_icon( fn note_tag_icon(&self, shape: NoteTagShape, status: ActionItemStatus) -> NoteTagIcon {
&self,
shape: NoteTagShape,
status: ActionItemStatus,
) -> (Cow<'static, str>, StyleSet) {
let mut style = StyleSet::new();
match shape { match shape {
NoteTagShape::GreenCheckBox => self.icon_checkbox(status, style, COLOR_GREEN), NoteTagShape::GreenCheckBox => self.icon_checkbox(status, COLOR_GREEN),
NoteTagShape::YellowCheckBox => self.icon_checkbox(status, style, COLOR_YELLOW), NoteTagShape::YellowCheckBox => self.icon_checkbox(status, COLOR_YELLOW),
NoteTagShape::BlueCheckBox => self.icon_checkbox(status, style, COLOR_BLUE), NoteTagShape::BlueCheckBox => self.icon_checkbox(status, COLOR_BLUE),
NoteTagShape::GreenStarCheckBox => { NoteTagShape::GreenStarCheckBox => self.icon_checkbox_with_star(status, COLOR_GREEN),
self.icon_checkbox_with_star(status, style, COLOR_GREEN) NoteTagShape::YellowStarCheckBox => self.icon_checkbox_with_star(status, COLOR_YELLOW),
} NoteTagShape::BlueStarCheckBox => self.icon_checkbox_with_star(status, COLOR_BLUE),
NoteTagShape::YellowStarCheckBox => {
self.icon_checkbox_with_star(status, style, COLOR_YELLOW)
}
NoteTagShape::BlueStarCheckBox => {
self.icon_checkbox_with_star(status, style, COLOR_BLUE)
}
NoteTagShape::GreenExclamationCheckBox => { NoteTagShape::GreenExclamationCheckBox => {
self.icon_checkbox_with_exclamation(status, style, COLOR_GREEN) self.icon_checkbox_with_exclamation(status, COLOR_GREEN)
} }
NoteTagShape::YellowExclamationCheckBox => { NoteTagShape::YellowExclamationCheckBox => {
self.icon_checkbox_with_exclamation(status, style, COLOR_YELLOW) self.icon_checkbox_with_exclamation(status, COLOR_YELLOW)
} }
NoteTagShape::BlueExclamationCheckBox => { NoteTagShape::BlueExclamationCheckBox => {
self.icon_checkbox_with_exclamation(status, style, COLOR_BLUE) self.icon_checkbox_with_exclamation(status, COLOR_BLUE)
} }
NoteTagShape::GreenRightArrowCheckBox => { NoteTagShape::GreenRightArrowCheckBox => {
self.icon_checkbox_with_right_arrow(status, style, COLOR_GREEN) self.icon_checkbox_with_right_arrow(status, COLOR_GREEN)
} }
NoteTagShape::YellowRightArrowCheckBox => { NoteTagShape::YellowRightArrowCheckBox => {
self.icon_checkbox_with_right_arrow(status, style, COLOR_YELLOW) self.icon_checkbox_with_right_arrow(status, COLOR_YELLOW)
} }
NoteTagShape::BlueRightArrowCheckBox => { NoteTagShape::BlueRightArrowCheckBox => {
self.icon_checkbox_with_right_arrow(status, style, COLOR_BLUE) self.icon_checkbox_with_right_arrow(status, COLOR_BLUE)
} }
NoteTagShape::YellowStar => { NoteTagShape::YellowStar => {
let mut style = StyleSet::new();
style.set("fill", COLOR_YELLOW.to_string()); style.set("fill", COLOR_YELLOW.to_string());
( (Cow::from(ICON_STAR), IconSize::Normal, style).into()
Cow::from(ICON_STAR),
self.icon_style(IconSize::Normal, style),
)
} }
NoteTagShape::QuestionMark => ( NoteTagShape::QuestionMark => (Cow::from(ICON_QUESTION_MARK), IconSize::Normal).into(),
Cow::from(ICON_QUESTION_MARK),
self.icon_style(IconSize::Normal, style),
),
NoteTagShape::HighPriority => ( NoteTagShape::HighPriority => (Cow::from(ICON_ERROR), IconSize::Normal).into(),
Cow::from(ICON_ERROR), NoteTagShape::ContactInformation => (Cow::from(ICON_PHONE), IconSize::Normal).into(),
self.icon_style(IconSize::Normal, style),
),
NoteTagShape::ContactInformation => (
Cow::from(ICON_PHONE),
self.icon_style(IconSize::Normal, style),
),
NoteTagShape::LightBulb => ( NoteTagShape::LightBulb => (Cow::from(ICON_LIGHT_BULB), IconSize::Normal).into(),
Cow::from(ICON_LIGHT_BULB),
self.icon_style(IconSize::Normal, style),
),
NoteTagShape::Home => ( NoteTagShape::Home => (Cow::from(ICON_HOME), IconSize::Normal).into(),
Cow::from(ICON_HOME), NoteTagShape::CommentBubble => (Cow::from(ICON_BUBBLE), IconSize::Normal).into(),
self.icon_style(IconSize::Normal, style),
),
NoteTagShape::CommentBubble => (
Cow::from(ICON_BUBBLE),
self.icon_style(IconSize::Normal, style),
),
NoteTagShape::AwardRibbon => ( NoteTagShape::AwardRibbon => (Cow::from(ICON_AWARD), IconSize::Normal).into(),
Cow::from(ICON_AWARD),
self.icon_style(IconSize::Normal, style),
),
NoteTagShape::BlueCheckBox1 => self.icon_checkbox_with_1(status, style, COLOR_BLUE), NoteTagShape::BlueCheckBox1 => self.icon_checkbox_with_1(status, COLOR_BLUE),
NoteTagShape::BlueCheckBox2 => self.icon_checkbox_with_2(status, style, COLOR_BLUE), NoteTagShape::BlueCheckBox2 => self.icon_checkbox_with_2(status, COLOR_BLUE),
NoteTagShape::BlueCheckBox3 => self.icon_checkbox_with_3(status, style, COLOR_BLUE), NoteTagShape::BlueCheckBox3 => self.icon_checkbox_with_3(status, COLOR_BLUE),
NoteTagShape::BlueCheckMark => self.icon_checkmark(style, COLOR_BLUE), NoteTagShape::BlueCheckMark => self.icon_checkmark(COLOR_BLUE),
NoteTagShape::BlueCircle => self.icon_circle(style, COLOR_BLUE), NoteTagShape::BlueCircle => self.icon_circle(COLOR_BLUE),
NoteTagShape::GreenCheckBox1 => self.icon_checkbox_with_1(status, style, COLOR_GREEN), NoteTagShape::GreenCheckBox1 => self.icon_checkbox_with_1(status, COLOR_GREEN),
NoteTagShape::GreenCheckBox2 => self.icon_checkbox_with_2(status, style, COLOR_GREEN), NoteTagShape::GreenCheckBox2 => self.icon_checkbox_with_2(status, COLOR_GREEN),
NoteTagShape::GreenCheckBox3 => self.icon_checkbox_with_3(status, style, COLOR_GREEN), NoteTagShape::GreenCheckBox3 => self.icon_checkbox_with_3(status, COLOR_GREEN),
NoteTagShape::GreenCheckMark => self.icon_checkmark(style, COLOR_GREEN), NoteTagShape::GreenCheckMark => self.icon_checkmark(COLOR_GREEN),
NoteTagShape::GreenCircle => self.icon_circle(style, COLOR_GREEN), NoteTagShape::GreenCircle => self.icon_circle(COLOR_GREEN),
NoteTagShape::YellowCheckBox1 => self.icon_checkbox_with_1(status, style, COLOR_YELLOW), NoteTagShape::YellowCheckBox1 => self.icon_checkbox_with_1(status, COLOR_YELLOW),
NoteTagShape::YellowCheckBox2 => self.icon_checkbox_with_2(status, style, COLOR_YELLOW), NoteTagShape::YellowCheckBox2 => self.icon_checkbox_with_2(status, COLOR_YELLOW),
NoteTagShape::YellowCheckBox3 => self.icon_checkbox_with_3(status, style, COLOR_YELLOW), NoteTagShape::YellowCheckBox3 => self.icon_checkbox_with_3(status, COLOR_YELLOW),
NoteTagShape::YellowCheckMark => self.icon_checkmark(style, COLOR_YELLOW), NoteTagShape::YellowCheckMark => self.icon_checkmark(COLOR_YELLOW),
NoteTagShape::YellowCircle => self.icon_circle(style, COLOR_YELLOW), NoteTagShape::YellowCircle => self.icon_circle(COLOR_YELLOW),
NoteTagShape::BluePersonCheckBox => { NoteTagShape::BluePersonCheckBox => self.icon_checkbox_with_person(status, COLOR_BLUE),
self.icon_checkbox_with_person(status, style, COLOR_BLUE)
}
NoteTagShape::YellowPersonCheckBox => { NoteTagShape::YellowPersonCheckBox => {
self.icon_checkbox_with_person(status, style, COLOR_YELLOW) self.icon_checkbox_with_person(status, COLOR_YELLOW)
} }
NoteTagShape::GreenPersonCheckBox => { NoteTagShape::GreenPersonCheckBox => {
self.icon_checkbox_with_person(status, style, COLOR_GREEN) self.icon_checkbox_with_person(status, COLOR_GREEN)
} }
NoteTagShape::BlueFlagCheckBox => { NoteTagShape::BlueFlagCheckBox => self.icon_checkbox_with_flag(status, COLOR_BLUE),
self.icon_checkbox_with_flag(status, style, COLOR_BLUE) NoteTagShape::RedFlagCheckBox => self.icon_checkbox_with_flag(status, COLOR_RED),
} NoteTagShape::GreenFlagCheckBox => self.icon_checkbox_with_flag(status, COLOR_GREEN),
NoteTagShape::RedFlagCheckBox => self.icon_checkbox_with_flag(status, style, COLOR_RED), NoteTagShape::RedSquare => self.icon_square(COLOR_RED),
NoteTagShape::GreenFlagCheckBox => { NoteTagShape::YellowSquare => self.icon_square(COLOR_YELLOW),
self.icon_checkbox_with_flag(status, style, COLOR_GREEN) NoteTagShape::BlueSquare => self.icon_square(COLOR_BLUE),
} NoteTagShape::GreenSquare => self.icon_square(COLOR_GREEN),
NoteTagShape::RedSquare => self.icon_square(style, COLOR_RED), NoteTagShape::OrangeSquare => self.icon_square(COLOR_ORANGE),
NoteTagShape::YellowSquare => self.icon_square(style, COLOR_YELLOW), NoteTagShape::PinkSquare => self.icon_square(COLOR_PINK),
NoteTagShape::BlueSquare => self.icon_square(style, COLOR_BLUE), NoteTagShape::EMailMessage => (Cow::from(ICON_EMAIL), IconSize::Normal).into(),
NoteTagShape::GreenSquare => self.icon_square(style, COLOR_GREEN),
NoteTagShape::OrangeSquare => self.icon_square(style, COLOR_ORANGE),
NoteTagShape::PinkSquare => self.icon_square(style, COLOR_PINK),
NoteTagShape::EMailMessage => (
Cow::from(ICON_EMAIL),
self.icon_style(IconSize::Normal, style),
),
NoteTagShape::Contact => ( NoteTagShape::Contact => (Cow::from(ICON_CONTACT), IconSize::Normal).into(),
Cow::from(ICON_CONTACT),
self.icon_style(IconSize::Normal, style),
),
NoteTagShape::MusicalNote => ( NoteTagShape::MusicalNote => (Cow::from(ICON_MUSIC), IconSize::Normal).into(),
Cow::from(ICON_MUSIC), NoteTagShape::MovieClip => (Cow::from(ICON_FILM), IconSize::Normal).into(),
self.icon_style(IconSize::Normal, style),
),
NoteTagShape::MovieClip => (
Cow::from(ICON_FILM),
self.icon_style(IconSize::Normal, style),
),
NoteTagShape::HyperlinkGlobe => ( NoteTagShape::HyperlinkGlobe => (Cow::from(ICON_LINK), IconSize::Normal).into(),
Cow::from(ICON_LINK),
self.icon_style(IconSize::Normal, style),
),
NoteTagShape::Padlock => ( NoteTagShape::Padlock => (Cow::from(ICON_LOCK), IconSize::Normal).into(),
Cow::from(ICON_LOCK), NoteTagShape::OpenBook => (Cow::from(ICON_BOOK), IconSize::Normal).into(),
self.icon_style(IconSize::Normal, style),
),
NoteTagShape::OpenBook => (
Cow::from(ICON_BOOK),
self.icon_style(IconSize::Normal, style),
),
NoteTagShape::BlankPaperWithLines => ( NoteTagShape::BlankPaperWithLines => (Cow::from(ICON_PAPER), IconSize::Normal).into(),
Cow::from(ICON_PAPER),
self.icon_style(IconSize::Normal, style),
),
NoteTagShape::Pen => ( NoteTagShape::Pen => (Cow::from(ICON_PEN), IconSize::Normal).into(),
Cow::from(ICON_PEN),
self.icon_style(IconSize::Normal, style),
),
shape => self.icon_fallback(style, shape), shape => self.icon_fallback(shape),
} }
} }
fn icon_fallback(&self, style: StyleSet, shape: NoteTagShape) -> (Cow<'static, str>, StyleSet) { fn icon_fallback(&self, shape: NoteTagShape) -> NoteTagIcon {
log_warn!("Unsupported icon type: {:?}", shape); log_warn!("Unsupported icon type: {:?}", shape);
( (Cow::from(ICON_QUESTION_MARK), IconSize::Normal).into()
Cow::from(ICON_QUESTION_MARK),
self.icon_style(IconSize::Normal, style),
)
} }
fn icon_checkbox( fn icon_checkbox(&self, status: ActionItemStatus, color: &'static str) -> NoteTagIcon {
&self, let mut styles = StyleSet::new();
status: ActionItemStatus, styles.set("fill", color.to_string());
mut style: StyleSet,
color: &'static str,
) -> (Cow<'static, str>, StyleSet) {
style.set("fill", color.to_string());
if status.completed() { let html = if status.completed() {
( Cow::from(ICON_CHECKBOX_COMPLETE)
Cow::from(ICON_CHECKBOX_COMPLETE),
self.icon_style(IconSize::Large, style),
)
} else { } else {
( Cow::from(ICON_CHECKBOX_EMPTY)
Cow::from(ICON_CHECKBOX_EMPTY), };
self.icon_style(IconSize::Large, style),
) NoteTagIcon {
html,
size: IconSize::Large,
styles,
is_checkbox: true,
} }
} }
fn icon_checkbox_with_person( fn icon_checkbox_with_person(
&self, &self,
status: ActionItemStatus, status: ActionItemStatus,
style: StyleSet,
color: &'static str, color: &'static str,
) -> (Cow<'static, str>, StyleSet) { ) -> NoteTagIcon {
self.icon_checkbox_with(status, style, color, ICON_PERSON) self.icon_checkbox_with(status, color, ICON_PERSON)
} }
fn icon_checkbox_with_right_arrow( fn icon_checkbox_with_right_arrow(
&self, &self,
status: ActionItemStatus, status: ActionItemStatus,
style: StyleSet,
color: &'static str, color: &'static str,
) -> (Cow<'static, str>, StyleSet) { ) -> NoteTagIcon {
self.icon_checkbox_with(status, style, color, ICON_ARROW_RIGHT) self.icon_checkbox_with(status, color, ICON_ARROW_RIGHT)
} }
fn icon_checkbox_with_star( fn icon_checkbox_with_star(
&self, &self,
status: ActionItemStatus, status: ActionItemStatus,
style: StyleSet,
color: &'static str, color: &'static str,
) -> (Cow<'static, str>, StyleSet) { ) -> NoteTagIcon {
self.icon_checkbox_with(status, style, color, ICON_STAR) self.icon_checkbox_with(status, color, ICON_STAR)
} }
fn icon_checkbox_with_flag( fn icon_checkbox_with_flag(
&self, &self,
status: ActionItemStatus, status: ActionItemStatus,
style: StyleSet,
color: &'static str, color: &'static str,
) -> (Cow<'static, str>, StyleSet) { ) -> NoteTagIcon {
self.icon_checkbox_with(status, style, color, ICON_FLAG) self.icon_checkbox_with(status, color, ICON_FLAG)
} }
fn icon_checkbox_with_1( fn icon_checkbox_with_1(&self, status: ActionItemStatus, color: &'static str) -> NoteTagIcon {
&self, self.icon_checkbox_with(status, color, "<span class=\"content\">1</span>")
status: ActionItemStatus,
style: StyleSet,
color: &'static str,
) -> (Cow<'static, str>, StyleSet) {
self.icon_checkbox_with(status, style, color, "<span class=\"content\">1</span>")
} }
fn icon_checkbox_with_2( fn icon_checkbox_with_2(&self, status: ActionItemStatus, color: &'static str) -> NoteTagIcon {
&self, self.icon_checkbox_with(status, color, "<span class=\"content\">2</span>")
status: ActionItemStatus,
style: StyleSet,
color: &'static str,
) -> (Cow<'static, str>, StyleSet) {
self.icon_checkbox_with(status, style, color, "<span class=\"content\">2</span>")
} }
fn icon_checkbox_with_3( fn icon_checkbox_with_3(&self, status: ActionItemStatus, color: &'static str) -> NoteTagIcon {
&self, self.icon_checkbox_with(status, color, "<span class=\"content\">3</span>")
status: ActionItemStatus,
style: StyleSet,
color: &'static str,
) -> (Cow<'static, str>, StyleSet) {
self.icon_checkbox_with(status, style, color, "<span class=\"content\">3</span>")
} }
fn icon_checkbox_with_exclamation( fn icon_checkbox_with_exclamation(
&self, &self,
status: ActionItemStatus, status: ActionItemStatus,
style: StyleSet,
color: &'static str, color: &'static str,
) -> (Cow<'static, str>, StyleSet) { ) -> NoteTagIcon {
self.icon_checkbox_with(status, style, color, "<span class=\"content\">!</span>") self.icon_checkbox_with(status, color, "<span class=\"content\">!</span>")
} }
fn icon_checkbox_with( fn icon_checkbox_with(
&self, &self,
status: ActionItemStatus, status: ActionItemStatus,
mut style: StyleSet,
color: &'static str, color: &'static str,
secondary_icon: &'static str, secondary_icon: &'static str,
) -> (Cow<'static, str>, StyleSet) { ) -> NoteTagIcon {
let mut style = StyleSet::new();
style.set("fill", color.to_string()); style.set("fill", color.to_string());
let mut content = String::new(); let mut content = String::new();
@@ -414,75 +386,37 @@ impl<'a> Renderer<'a> {
secondary_icon secondary_icon
)); ));
(Cow::from(content), self.icon_style(IconSize::Large, style)) NoteTagIcon {
} html: Cow::from(content),
size: IconSize::Large,
fn icon_checkmark( styles: style,
&self, is_checkbox: true,
mut style: StyleSet,
color: &'static str,
) -> (Cow<'static, str>, StyleSet) {
style.set("fill", color.to_string());
(
Cow::from(ICON_CHECK_MARK),
self.icon_style(IconSize::Large, style),
)
}
fn icon_circle(
&self,
mut style: StyleSet,
color: &'static str,
) -> (Cow<'static, str>, StyleSet) {
style.set("fill", color.to_string());
(
Cow::from(ICON_CIRCLE),
self.icon_style(IconSize::Normal, style),
)
}
fn icon_square(
&self,
mut style: StyleSet,
color: &'static str,
) -> (Cow<'static, str>, StyleSet) {
style.set("fill", color.to_string());
(
Cow::from(ICON_SQUARE),
self.icon_style(IconSize::Large, style),
)
}
fn icon_style(&self, size: IconSize, mut style: StyleSet) -> StyleSet {
match size {
IconSize::Normal => {
style.set("height", "16px".to_string());
style.set("width", "16px".to_string());
}
IconSize::Large => {
style.set("height", "20px".to_string());
style.set("width", "20px".to_string());
}
} }
}
match (self.in_list, size) { fn icon_checkmark(&self, color: &'static str) -> NoteTagIcon {
(false, IconSize::Normal) => { let mut style = StyleSet::new();
style.set("left", "-23px".to_string()); style.set("fill", color.to_string());
}
(false, IconSize::Large) => {
style.set("left", "-25px".to_string());
}
(true, IconSize::Normal) => {
style.set("left", "-38px".to_string());
}
(true, IconSize::Large) => {
style.set("left", "-40px".to_string());
}
};
style NoteTagIcon {
is_checkbox: true,
html: Cow::from(ICON_CHECK_MARK),
size: IconSize::Large,
styles: style,
}
}
fn icon_circle(&self, color: &'static str) -> NoteTagIcon {
let mut style = StyleSet::new();
style.set("fill", color.to_string());
(Cow::from(ICON_CIRCLE), IconSize::Normal, style).into()
}
fn icon_square(&self, color: &'static str) -> NoteTagIcon {
let mut style = StyleSet::new();
style.set("fill", color.to_string());
(Cow::from(ICON_SQUARE), IconSize::Large, style).into()
} }
} }

View File

@@ -78,7 +78,7 @@ impl<'a> Renderer<'a> {
let is_list = self.is_list(element); let is_list = self.is_list(element);
let mut attrs = AttributeSet::new(); let mut attrs = AttributeSet::new();
attrs.set("class", "outline-element".to_string()); attrs.set("class", "outline-element".into());
let mut styles = StyleSet::new(); let mut styles = StyleSet::new();
styles.set("margin-left", px(indent_width as f32)); styles.set("margin-left", px(indent_width as f32));

View File

@@ -100,7 +100,7 @@ impl<'a> Renderer<'a> {
fn table_cell_level(&self, elements: &[OutlineElement]) -> u8 { fn table_cell_level(&self, elements: &[OutlineElement]) -> u8 {
let needs_nesting = elements let needs_nesting = elements
.iter() .iter()
.any(|element| self.is_list(element) || self.has_note_tag(element)); .any(|element| self.is_list(element));
if needs_nesting { 2 } else { 1 } if needs_nesting { 2 } else { 1 }
} }

View File

@@ -4,6 +4,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>{{ name }}</title> <title>{{ name }}</title>
<style> <style>
/*** Start default CSS ***/
* { margin: 0; padding: 0; font-weight: normal; } * { margin: 0; padding: 0; font-weight: normal; }
table, tr, td { border-color: #A3A3A3; } table, tr, td { border-color: #A3A3A3; }
ul, ol { padding: 0; } ul, ol { padding: 0; }
@@ -12,11 +13,31 @@
.container-outline { font-family: Calibri, sans-serif; font-size: 6pt; } .container-outline { font-family: Calibri, sans-serif; font-size: 6pt; }
.ink-text, .ink-space { display: inline-block; position: relative; vertical-align: bottom; } .ink-text, .ink-space { display: inline-block; position: relative; vertical-align: bottom; }
.ink-text { top: 0; left: 0; } .ink-text { top: 0; left: 0; }
.note-tag-icon { position: relative; }
/* Icons */
.note-tag-icon {
position: relative;
--note-tag-left: -23px;
--note-tag-width: 16px;
--note-tag-height: 16px;
}
.note-tag-icon.-large {
--note-tag-left: -25px;
--note-tag-width: 20px;
--note-tag-height: 20px;
}
.note-tag-icon > img, .note-tag-icon > svg {
width: var(--note-tag-width);
height: var(--note-tag-height);
left: var(--note-tag-left);
}
.tagged-list { list-style: none; padding-left: 0; }
{# Select both SVGs and IMGs: Joplin post-processes the converter's output, converting SVGs to IMGs. #} {# Select both SVGs and IMGs: Joplin post-processes the converter's output, converting SVGs to IMGs. #}
.note-tag-icon > svg, .note-tag-icon > img { position: absolute; } .note-tag-icon > svg, .note-tag-icon > img { position: absolute; }
.icon-secondary > svg, .icon-secondary > img { position: absolute; fill: black; filter: drop-shadow(0 0 2px white); height: 12px; top: -1px; } .icon-secondary > svg, .icon-secondary > img { position: absolute; fill: black; filter: drop-shadow(0 0 2px white); height: 12px; top: -1px; }
.icon-secondary > .content { position: absolute; color: black; filter: drop-shadow(0 0 2px white); font-size: 10px; color: black; top: -1px; user-select: none; } .icon-secondary > .content { position: absolute; color: black; filter: drop-shadow(0 0 2px white); font-size: 10px; color: black; top: -1px; user-select: none; }
/*** End default CSS ***/
{% for entry in global_styles -%} {% for entry in global_styles -%}
{{ entry.0 }} { {{ entry.1 }} } {{ entry.0 }} { {{ entry.1 }} }

View File

@@ -9,6 +9,7 @@ pub(crate) fn px(inches: f32) -> String {
format!("{}px", (inches * 48.0).round()) format!("{}px", (inches * 48.0).round())
} }
#[derive(Clone)]
pub(crate) struct AttributeSet(HashMap<&'static str, String>); pub(crate) struct AttributeSet(HashMap<&'static str, String>);
impl AttributeSet { impl AttributeSet {
@@ -21,6 +22,12 @@ impl AttributeSet {
} }
} }
impl<const N: usize> From<[(&'static str, String); N]> for AttributeSet {
fn from(data: [(&'static str, String); N]) -> Self {
Self(data.into())
}
}
impl Display for AttributeSet { impl Display for AttributeSet {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!( write!(

View File

@@ -3,15 +3,20 @@ export default function taskListItems (turndownService) {
filter: function (node) { filter: function (node) {
const parent = node.parentNode; const parent = node.parentNode;
const grandparent = parent.parentNode; const grandparent = parent.parentNode;
return node.type === 'checkbox' && ( const grandparentIsListItem = !!grandparent && grandparent.nodeName === 'LI';
return (node.type === 'checkbox' || node.role === 'checkbox') && (
parent.nodeName === 'LI' parent.nodeName === 'LI'
// Handles the case where the label contains the checkbox. For example, // Handles the case where the label contains the checkbox. For example,
// <label><input ...> ...label text...</label> // <label><input ...> ...label text...</label>
|| (parent.nodeName === 'LABEL' && grandparent && grandparent.nodeName === 'LI') || (parent.nodeName === 'LABEL' && grandparentIsListItem)
// Handles the case where the input is contained within a <span>
// <li><span><input ...></span></li>
|| (parent.nodeName === 'SPAN' && grandparentIsListItem)
) )
}, },
replacement: function (content, node) { replacement: function (content, node) {
return (node.checked ? '[x]' : '[ ]') + ' ' const checked = node.nodeName === 'INPUT' ? node.checked : node.getAttribute('aria-checked') === 'true';
return (checked ? '[x]' : '[ ]') + ' '
} }
}) })
} }

View File

@@ -864,8 +864,12 @@ function joplinCheckboxInfo(liNode) {
}; };
} }
// Should handle both <ul class='joplin-checklist'><li>...</li></ul>
// and <ul><li class='joplin-checklist-item'>...</li></ul>. The second is present
// in certain types of imported notes.
const parentChecklist = findParent(liNode, 'class', 'joplin-checklist'); const parentChecklist = findParent(liNode, 'class', 'joplin-checklist');
if (parentChecklist) { const currentChecklist = liNode.classList.contains('joplin-checklist-item');
if (parentChecklist || currentChecklist) {
return { return {
checked: !!liNode.classList && liNode.classList.contains('checked'), checked: !!liNode.classList && liNode.classList.contains('checked'),
renderingType: 2, renderingType: 2,