diff --git a/scripts/lib/plan-canvas/markdown.js b/scripts/lib/plan-canvas/markdown.js
index 84799a3c..94167141 100644
--- a/scripts/lib/plan-canvas/markdown.js
+++ b/scripts/lib/plan-canvas/markdown.js
@@ -132,8 +132,12 @@ function renderListItem(text) {
return '
' + renderInline(text) + '';
}
+function listTag(marker) {
+ return /^\d/.test(marker) ? 'ol' : 'ul';
+}
+
function buildList(items, start, indent) {
- const tag = /^\d/.test(items[start].marker) ? 'ol' : 'ul';
+ const tag = listTag(items[start].marker);
const parts = [];
let i = start;
while (i < items.length && items[i].indent >= indent) {
@@ -148,6 +152,10 @@ function buildList(items, start, indent) {
}
i = nested.end;
} else {
+ // A marker-type change at the same indent starts a new list
+ // (CommonMark); stop here so the caller renders the next run with
+ // its own tag instead of absorbing it into this one.
+ if (listTag(items[i].marker) !== tag) break;
parts.push(renderListItem(items[i].text));
i += 1;
}
@@ -155,6 +163,17 @@ function buildList(items, start, indent) {
return { html: '<' + tag + '>\n' + parts.join('\n') + '\n' + tag + '>', end: i };
}
+function buildListBlock(items) {
+ let lists = [];
+ let i = 0;
+ while (i < items.length) {
+ const list = buildList(items, i, items[i].indent);
+ lists = [...lists, list.html];
+ i = list.end;
+ }
+ return lists.join('\n');
+}
+
function startsBlock(line, nextLine) {
return /^```/.test(line) ||
/^#{1,6}\s/.test(line) ||
@@ -257,7 +276,10 @@ function renderMarkdown(text) {
items.push({ indent: m[1].length, marker: m[2], text: m[3] });
i += 1;
}
- out.push(buildList(items, 0, items[0].indent).html);
+ // An outdent below the first item's indentation ends that list. Render
+ // the remaining run as a sibling list so malformed indentation cannot
+ // silently drop content or create an empty parent item.
+ out.push(buildListBlock(items));
continue;
}
diff --git a/tests/lib/plan-canvas-markdown.test.js b/tests/lib/plan-canvas-markdown.test.js
index 5b401ff4..d0953814 100644
--- a/tests/lib/plan-canvas-markdown.test.js
+++ b/tests/lib/plan-canvas-markdown.test.js
@@ -152,6 +152,44 @@ function runTests() {
assert.strictEqual(renderMarkdown('* a\n* b'), '');
})) passed++; else failed++;
+ if (test('preserves items that outdent below the first item', () => {
+ assert.strictEqual(
+ renderMarkdown(' - alpha\n- beta\n- gamma'),
+ '\n'
+ );
+ })) passed++; else failed++;
+
+ if (test('uses each outdented run marker for its list type', () => {
+ assert.strictEqual(
+ renderMarkdown(' - prep\n1. phase one\n2. phase two'),
+ '\n\n- phase one
\n- phase two
\n
'
+ );
+ })) passed++; else failed++;
+
+ if (test('marker type change at the same indent starts a new list', () => {
+ assert.strictEqual(
+ renderMarkdown('- prep\n1. phase one\n2. phase two'),
+ '\n\n- phase one
\n- phase two
\n
'
+ );
+ })) passed++; else failed++;
+
+ if (test('switching back to bullets after a numbered run starts a third list', () => {
+ assert.strictEqual(
+ renderMarkdown('1. one\n- bullet\n2. two'),
+ '\n- one
\n
\n\n\n- two
\n
'
+ );
+ })) passed++; else failed++;
+
+ if (test('renders repeated outdents without empty parent items', () => {
+ const out = renderMarkdown(' - deep one\n - deep two\n - middle\n- shallow');
+ assert.strictEqual(
+ out,
+ '\n' +
+ '\n'
+ );
+ assert.ok(!out.includes('\n'), `No empty parent item expected, got ${out}`);
+ })) passed++; else failed++;
+
// Table tests
console.log('\nTables:');