diff --git a/src/sql/workbench/contrib/notebook/browser/htmlMarkdownConverter.ts b/src/sql/workbench/contrib/notebook/browser/htmlMarkdownConverter.ts index cc7d7b5ba4..9fbeaaef04 100644 --- a/src/sql/workbench/contrib/notebook/browser/htmlMarkdownConverter.ts +++ b/src/sql/workbench/contrib/notebook/browser/htmlMarkdownConverter.ts @@ -34,7 +34,7 @@ export class HTMLMarkdownConverter { private turndownService: TurndownService; constructor(private notebookUri: URI) { - this.turndownService = new TurndownService({ 'emDelimiter': '_', 'bulletListMarker': '-', 'headingStyle': 'atx' }); + this.turndownService = new TurndownService({ 'emDelimiter': '_', 'bulletListMarker': '-', 'headingStyle': 'atx', blankReplacement: blankReplacement }); this.setTurndownOptions(); } @@ -143,6 +143,39 @@ export class HTMLMarkdownConverter { return `[${content}](${node.href})`; } }); + // Only nested list case differs from original turndown rule + // This ensures that tightly coupled lists are treated as such and do not have excess newlines in markdown + this.turndownService.addRule('list', { + filter: ['ul', 'ol'], + replacement: function (content, node) { + let parent = node.parentNode; + if ((parent.nodeName === 'LI' && parent.lastElementChild === node)) { + return '\n' + content; + } else if (parent.nodeName === 'UL' || parent.nodeName === 'OL') { // Nested list case + return '\n' + content + '\n'; + } else { + return '\n\n' + content + '\n\n'; + } + } + }); + this.turndownService.addRule('lineBreak', { + filter: 'br', + replacement: function (content, node, options) { + // For elements that aren't lists, convert
into its markdown equivalent + if (node.parentElement?.nodeName !== 'LI') { + return options.br + '\n'; + } + // One (and only one) line break is ignored when it's inside of a list item + // Otherwise, a new list will be created due to the looseness of the list + let numberLineBreaks = 0; + (node.parentElement as HTMLElement)?.childNodes?.forEach(n => { + if (n.nodeName === 'BR') { + numberLineBreaks++; + } + }); + return numberLineBreaks > 1 ? options.br + '\n' : ''; + } + }); this.turndownService.addRule('listItem', { filter: 'li', replacement: function (content, node, options) { @@ -231,6 +264,14 @@ function escapeMarkdown(text) { ); } +function blankReplacement(content, node) { + // When outdenting a nested list, an empty list will still remain. Need to handle this case. + if (node.nodeName === 'UL' || node.nodeName === 'OL') { + return '\n'; + } + return node.isBlock ? '\n\n' : ''; +} + export function findPathRelativeToContent(notebookFolder: string, contentPath: URI | undefined): string { if (notebookFolder) { if (contentPath?.scheme === 'file') { diff --git a/src/sql/workbench/contrib/notebook/test/browser/htmlMarkdownConverter.test.ts b/src/sql/workbench/contrib/notebook/test/browser/htmlMarkdownConverter.test.ts index 04698f60ac..cc7f70f8c9 100644 --- a/src/sql/workbench/contrib/notebook/test/browser/htmlMarkdownConverter.test.ts +++ b/src/sql/workbench/contrib/notebook/test/browser/htmlMarkdownConverter.test.ts @@ -148,10 +148,18 @@ suite('HTML Markdown Converter', function (): void { test('Should transform
  • tags', () => { htmlString = ''; assert.equal(htmlMarkdownConverter.convert(htmlString), `- Test`, 'Basic unordered list test failed'); + htmlString = ''; + assert.equal(htmlMarkdownConverter.convert(htmlString), `- Test\n- Test2`, 'Basic unordered list test with span and line break failed'); + htmlString = ''; + assert.equal(htmlMarkdownConverter.convert(htmlString), `- Test \n \n \n- Test2`, 'Basic unordered list test with span and line break failed'); htmlString = ''; assert.equal(htmlMarkdownConverter.convert(htmlString), `- Test\n- Test2`, 'Basic unordered 2 item list test failed'); - htmlString = ''; + htmlString = ''; assert.equal(htmlMarkdownConverter.convert(htmlString), `- Test\n - Test2\n- Test3`, 'Nested item list test failed'); + htmlString = ''; + assert.equal(htmlMarkdownConverter.convert(htmlString), `- Test\n - Test2\n- Test3`, 'Nested item list test empty list failed'); + htmlString = ''; + assert.equal(htmlMarkdownConverter.convert(htmlString), `- Hello\n- Hello`, 'Nested item list test empty list failed'); htmlString = '
    1. Test
    '; assert.equal(htmlMarkdownConverter.convert(htmlString), `1. Test`, 'Basic ordered item test failed'); htmlString = '
    1. Test
    2. Test2
    ';