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', 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
    1. phase one
    2. \n
    3. phase two
    4. \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
    1. phase one
    2. \n
    3. phase two
    4. \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
    1. one
    2. \n
    \n\n
      \n
    1. two
    2. \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