DEV: Export Tag class to modify methods in plugin

This commit is contained in:
Vinoth Kannan 2018-08-01 15:10:15 +05:30 committed by Guo Xiang Tan
parent c4de36624f
commit 3eff6a0e9b

View File

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