diff --git a/app/assets/javascripts/discourse/lib/to-markdown.js.es6 b/app/assets/javascripts/discourse/lib/to-markdown.js.es6 index fa8259b7df4..5d3fb6657de 100644 --- a/app/assets/javascripts/discourse/lib/to-markdown.js.es6 +++ b/app/assets/javascripts/discourse/lib/to-markdown.js.es6 @@ -1,11 +1,15 @@ -import parseHTML from 'discourse/helpers/parse-html'; +import parseHTML from "discourse/helpers/parse-html"; -const trimLeft = text => text.replace(/^\s+/,""); -const trimRight = text => text.replace(/\s+$/,""); -const countPipes = text => (text.replace(/\\\|/,"").match(/\|/g) || []).length; -const msoListClasses = ["MsoListParagraphCxSpFirst", "MsoListParagraphCxSpMiddle", "MsoListParagraphCxSpLast"]; +const trimLeft = text => text.replace(/^\s+/, ""); +const trimRight = text => text.replace(/\s+$/, ""); +const countPipes = text => (text.replace(/\\\|/, "").match(/\|/g) || []).length; +const msoListClasses = [ + "MsoListParagraphCxSpFirst", + "MsoListParagraphCxSpMiddle", + "MsoListParagraphCxSpLast" +]; -class Tag { +export class Tag { constructor(name, prefix = "", suffix = "", inline = false) { this.name = name; this.prefix = prefix; @@ -36,8 +40,28 @@ class Tag { } static blocks() { - return ["address", "article", "aside", "dd", "div", "dl", "dt", "fieldset", "figcaption", "figure", - "footer", "form", "header", "hgroup", "hr", "main", "nav", "p", "pre", "section"]; + return [ + "address", + "article", + "aside", + "dd", + "div", + "dl", + "dt", + "fieldset", + "figcaption", + "figure", + "footer", + "form", + "header", + "hgroup", + "hr", + "main", + "nav", + "p", + "pre", + "section" + ]; } static headings() { @@ -45,7 +69,14 @@ class Tag { } static emphases() { - return [ ["b", "**"], ["strong", "**"], ["i", "*"], ["em", "*"], ["s", "~~"], ["strike", "~~"] ]; + return [ + ["b", "**"], + ["strong", "**"], + ["i", "*"], + ["em", "*"], + ["s", "~~"], + ["strike", "~~"] + ]; } static slices() { @@ -53,7 +84,21 @@ class Tag { } static trimmable() { - return [...Tag.blocks(), ...Tag.headings(), ...Tag.slices(), "li", "td", "th", "br", "hr", "blockquote", "table", "ol", "tr", "ul"]; + return [ + ...Tag.blocks(), + ...Tag.headings(), + ...Tag.slices(), + "li", + "td", + "th", + "br", + "hr", + "blockquote", + "table", + "ol", + "tr", + "ul" + ]; } static block(name, prefix, suffix) { @@ -162,7 +207,9 @@ class Tag { const height = attr.height || pAttr.height; if (width && height) { - const pipe = this.element.parentNames.includes("table") ? "\\|" : "|"; + const pipe = this.element.parentNames.includes("table") + ? "\\|" + : "|"; alt = `${alt}${pipe}${width}x${height}`; } @@ -198,9 +245,10 @@ class Tag { toMarkdown() { const text = this.element.innerMarkdown().trim(); - if(text.includes("\n")) { // Unsupported format inside Markdown table cells + if (text.includes("\n")) { + // Unsupported format inside Markdown table cells let e = this.element; - while(e = e.parent) { + while ((e = e.parent)) { if (e.name === "table") { e.tag().invalid(); break; @@ -216,12 +264,18 @@ class Tag { static li() { return class extends Tag.slice("li", "\n") { decorate(text) { - let indent = this.element.filterParentNames(["ol", "ul"]).slice(1).map(() => "\t").join(""); + let indent = this.element + .filterParentNames(["ol", "ul"]) + .slice(1) + .map(() => "\t") + .join(""); const attrs = this.element.attributes; if (msoListClasses.includes(attrs.class)) { try { - const level = parseInt(attrs.style.match(/level./)[0].replace("level", "")); + const level = parseInt( + attrs.style.match(/level./)[0].replace("level", "") + ); indent = Array(level).join("\t") + indent; } finally { if (attrs.class === "MsoListParagraphCxSpFirst") { @@ -245,13 +299,15 @@ class Tag { decorate(text) { if (this.element.parentNames.includes("pre")) { - this.prefix = '\n\n```\n'; - this.suffix = '\n```\n\n'; + this.prefix = "\n\n```\n"; + this.suffix = "\n```\n\n"; } else { this.inline = true; } - text = $('').html(text).text(); + text = $("") + .html(text) + .text(); return super.decorate(text); } }; @@ -264,7 +320,10 @@ class Tag { } decorate(text) { - text = text.trim().replace(/\n{2,}>/g, "\n>").replace(/\n/g, "\n> "); + text = text + .trim() + .replace(/\n{2,}>/g, "\n>") + .replace(/\n/g, "\n> "); return super.decorate(text); } }; @@ -281,7 +340,7 @@ class Tag { this.isValid = false; if (this.element.parentNames.includes("table")) { let e = this.element; - while(e = e.parent) { + while ((e = e.parent)) { if (e.name === "table") { e.tag().invalid(); break; @@ -294,10 +353,15 @@ class Tag { text = super.decorate(text).replace(/\|\n{2,}\|/g, "|\n|"); const rows = text.trim().split("\n"); const pipeCount = countPipes(rows[0]); - this.isValid = this.isValid && rows.length > 1 && pipeCount > 2 && rows.reduce((a, c) => a && countPipes(c) <= pipeCount); // Unsupported table format for Markdown conversion + this.isValid = + this.isValid && + rows.length > 1 && + pipeCount > 2 && + rows.reduce((a, c) => a && countPipes(c) <= pipeCount); // Unsupported table format for Markdown conversion if (this.isValid) { - const splitterRow = [...Array(pipeCount-1)].map(() => "| --- ").join("") + "|\n"; + const splitterRow = + [...Array(pipeCount - 1)].map(() => "| --- ").join("") + "|\n"; text = text.replace("|\n", "|\n" + splitterRow); } else { text = text.replace(/\|/g, " "); @@ -330,7 +394,11 @@ class Tag { text = "\n" + text; const bullet = text.match(/\n\t*\*/)[0]; - for (let i = parseInt(this.element.attributes.start || 1); text.includes(bullet); i++) { + for ( + let i = parseInt(this.element.attributes.start || 1); + text.includes(bullet); + i++ + ) { text = text.replace(bullet, bullet.replace("*", `${i}.`)); } @@ -349,19 +417,35 @@ class Tag { } }; } - } -const tags = [ - ...Tag.blocks().map((b) => Tag.block(b)), - ...Tag.headings().map((h, i) => Tag.heading(h, i + 1)), - ...Tag.slices().map((s) => Tag.slice(s, "\n")), - ...Tag.emphases().map((e) => Tag.emphasis(e[0], e[1])), - Tag.cell("td"), Tag.cell("th"), - Tag.replace("br", "\n"), Tag.replace("hr", "\n---\n"), Tag.replace("head", ""), - Tag.keep("ins"), Tag.keep("del"), Tag.keep("small"), Tag.keep("big"), Tag.keep("kbd"), - Tag.li(), Tag.link(), Tag.image(), Tag.code(), Tag.blockquote(), Tag.table(), Tag.tr(), Tag.ol(), Tag.list("ul"), -]; +function tags() { + return [ + ...Tag.blocks().map(b => Tag.block(b)), + ...Tag.headings().map((h, i) => Tag.heading(h, i + 1)), + ...Tag.slices().map(s => Tag.slice(s, "\n")), + ...Tag.emphases().map(e => Tag.emphasis(e[0], e[1])), + Tag.cell("td"), + Tag.cell("th"), + Tag.replace("br", "\n"), + Tag.replace("hr", "\n---\n"), + Tag.replace("head", ""), + Tag.keep("ins"), + Tag.keep("del"), + Tag.keep("small"), + Tag.keep("big"), + Tag.keep("kbd"), + Tag.li(), + Tag.link(), + Tag.image(), + Tag.code(), + Tag.blockquote(), + Tag.table(), + Tag.tr(), + Tag.ol(), + Tag.list("ul") + ]; +} class Element { constructor(element, parent, previous, next) { @@ -390,7 +474,8 @@ class Element { } tag() { - const tag = new (tags.filter(t => (new t().name === this.name))[0] || Tag)(); + const tag = new (tags().filter(t => new t().name === this.name)[0] || + Tag)(); tag.element = this; return tag; } @@ -424,7 +509,7 @@ class Element { } toMarkdown() { - switch(this.type) { + switch (this.type) { case "text": return this.text(); break; @@ -451,8 +536,8 @@ class Element { let result = []; for (let i = 0; i < elements.length; i++) { - const prev = (i === 0) ? null : elements[i-1]; - const next = (i === elements.length) ? null : elements[i+1]; + const prev = i === 0 ? null : elements[i - 1]; + const next = i === elements.length ? null : elements[i + 1]; result.push(Element.toMarkdown(elements[i], parent, prev, next)); } @@ -470,7 +555,7 @@ function trimUnwanted(html) { html = html.replace(/\r|\n| /g, " "); let match; - while (match = html.match(/<[^\s>]+[^>]*>\s{2,}<[^\s>]+[^>]*>/)) { + while ((match = html.match(/<[^\s>]+[^>]*>\s{2,}<[^\s>]+[^>]*>/))) { html = html.replace(match[0], match[0].replace(/>\s{2,}, "> <")); } @@ -485,10 +570,14 @@ function putPlaceholders(html) { let match = codeRegEx.exec(origHtml); let placeholders = []; - while(match) { + while (match) { const placeholder = `DISCOURSE_PLACEHOLDER_${placeholders.length + 1}`; let code = match[1]; - code = $('
').html(code).text().replace(/^\n/, '').replace(/\n$/, ''); + code = $("") + .html(code) + .text() + .replace(/^\n/, "") + .replace(/\n$/, ""); placeholders.push([placeholder, code]); html = html.replace(match[0], `${placeholder}
`);
match = codeRegEx.exec(origHtml);
@@ -509,10 +598,18 @@ export default function toMarkdown(html) {
try {
const { elements, placeholders } = putPlaceholders(html);
let markdown = Element.parse(elements).trim();
- markdown = markdown.replace(/^/, "").replace(/<\/b>$/, "").trim(); // fix for google doc copy paste
- markdown = markdown.replace(/\n +/g, "\n").replace(/ +\n/g, "\n").replace(/ {2,}/g, " ").replace(/\n{3,}/g, "\n\n").replace(/\t/g, " ");
+ markdown = markdown
+ .replace(/^/, "")
+ .replace(/<\/b>$/, "")
+ .trim(); // fix for google doc copy paste
+ markdown = markdown
+ .replace(/\n +/g, "\n")
+ .replace(/ +\n/g, "\n")
+ .replace(/ {2,}/g, " ")
+ .replace(/\n{3,}/g, "\n\n")
+ .replace(/\t/g, " ");
return replacePlaceholders(markdown, placeholders);
- } catch(err) {
+ } catch (err) {
return "";
}
}