Support mixed nested lists (#39)

This commit is contained in:
Yuri Karamian
2026-08-03 02:26:39 +02:00
parent 6b165059cc
commit 8731ca4ec1
3 changed files with 171 additions and 8 deletions
+80
View File
@@ -0,0 +1,80 @@
import assert from 'node:assert/strict';
import { readFile } from 'node:fs/promises';
import test from 'node:test';
import { Schema } from '@tiptap/pm/model';
import { transformWithEsbuild } from 'vite';
const source = await readFile(
new URL('../src/lib/editor/mixedLists.ts', import.meta.url),
'utf8'
);
const { code } = await transformWithEsbuild(source, 'mixedLists.ts', {
loader: 'ts',
format: 'esm',
target: 'esnext'
});
const { convertListNode } = await import(
`data:text/javascript;base64,${Buffer.from(code).toString('base64')}`
);
const schema = new Schema({
nodes: {
doc: { content: 'block+' },
paragraph: { content: 'text*', group: 'block' },
text: { group: 'inline' },
bulletList: { content: 'listItem+', group: 'block' },
listItem: { content: 'paragraph block*' },
taskList: { content: 'taskItem+', group: 'block' },
taskItem: {
attrs: { checked: { default: false } },
content: 'paragraph block*'
}
}
});
function paragraph(text) {
return schema.node('paragraph', null, [schema.text(text)]);
}
test('converts every task item in a list to a bullet item', () => {
const sourceList = schema.node('taskList', null, [
schema.node('taskItem', { checked: true }, [paragraph('First')]),
schema.node('taskItem', { checked: false }, [paragraph('Second')])
]);
const converted = convertListNode(schema, sourceList, 'bulletList');
assert.equal(converted.type.name, 'bulletList');
assert.deepEqual(
converted.content.content.map((item) => item.type.name),
['listItem', 'listItem']
);
assert.equal(converted.textContent, 'FirstSecond');
assert.equal(sourceList.type.name, 'taskList');
});
test('converts bullet items to unchecked tasks without dropping nested blocks', () => {
const nestedBullets = schema.node('bulletList', null, [
schema.node('listItem', null, [paragraph('Nested note')])
]);
const sourceList = schema.node('bulletList', null, [
schema.node('listItem', null, [paragraph('Parent'), nestedBullets])
]);
const converted = convertListNode(schema, sourceList, 'taskList');
const taskItem = converted.firstChild;
assert.equal(converted.type.name, 'taskList');
assert.equal(taskItem.type.name, 'taskItem');
assert.equal(taskItem.attrs.checked, false);
assert.equal(taskItem.lastChild.type.name, 'bulletList');
assert.equal(taskItem.textContent, 'ParentNested note');
});
test('leaves a list alone when it already has the requested type', () => {
const bullets = schema.node('bulletList', null, [
schema.node('listItem', null, [paragraph('Unchanged')])
]);
assert.equal(convertListNode(schema, bullets, 'bulletList'), null);
});