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