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 hasChild = (e, n) => { return (e.children || []).some(c => c.name === n); }; export class Tag { constructor(name, prefix = "", suffix = "", inline = false) { this.name = name; this.prefix = prefix; this.suffix = suffix; this.inline = inline; } decorate(text) { if (this.prefix || this.suffix) { text = [this.prefix, text, this.suffix].join(""); } if (this.inline) { text = " " + text + " "; } return text; } toMarkdown() { const text = this.element.innerMarkdown(); if (text && text.trim()) { return this.decorate(text); } return text; } static blocks() { return [ "address", "article", "aside", "dd", "div", "dl", "dt", "fieldset", "figcaption", "figure", "footer", "form", "header", "hgroup", "hr", "main", "nav", "p", "pre", "section" ]; } static headings() { return ["h1", "h2", "h3", "h4", "h5", "h6"]; } static emphases() { return [ ["b", "**"], ["strong", "**"], ["i", "*"], ["em", "*"], ["s", "~~"], ["strike", "~~"] ]; } static slices() { return ["dt", "dd", "thead", "tbody", "tfoot"]; } static trimmable() { return [ ...Tag.blocks(), ...Tag.headings(), ...Tag.slices(), "li", "td", "th", "br", "hr", "blockquote", "table", "ol", "tr", "ul" ]; } static block(name, prefix, suffix) { return class extends Tag { constructor() { super(name, prefix, suffix); this.gap = "\n\n"; } decorate(text) { const parent = this.element.parent; if (this.name === "p" && parent && parent.name === "li") { // fix for google docs this.gap = ""; } return `${this.gap}${this.prefix}${text}${this.suffix}${this.gap}`; } }; } static heading(name, i) { const prefix = `${[...Array(i)].map(() => "#").join("")} `; return Tag.block(name, prefix, ""); } static emphasis(name, decorator) { return class extends Tag { constructor() { super(name, decorator, decorator, true); } decorate(text) { if (text.includes("\n")) { this.prefix = `<${this.name}>`; this.suffix = ``; } let space = text.match(/^\s/) || [""]; this.prefix = space[0] + this.prefix; space = text.match(/\s$/) || [""]; this.suffix = this.suffix + space[0]; return super.decorate(text.trim()); } }; } static keep(name) { return class extends Tag { constructor() { super(name, `<${name}>`, ``); } }; } static replace(name, text) { return class extends Tag { constructor() { super(name, "", ""); this.text = text; } toMarkdown() { return this.text; } }; } static span() { return class extends Tag { constructor() { super("span"); } decorate(text) { const attr = this.element.attributes; if (attr.class === "badge badge-notification clicks") { return ""; } return super.decorate(text); } }; } static link() { return class extends Tag { constructor() { super("a", "", "", true); } decorate(text) { const e = this.element; const attr = e.attributes; if (/^mention/.test(attr.class) && "@" === text[0]) { return text; } else if ("hashtag" === attr.class && "#" === text[0]) { return text; } else if ( ["lightbox", "d-lazyload"].includes(attr.class) && hasChild(e, "img") ) { text = attr.title || ""; return "![" + text + "](" + attr.href + ")"; } if (attr.href && text !== attr.href) { text = text.replace(/\n{2,}/g, "\n"); return "[" + text + "](" + attr.href + ")"; } return text; } }; } static image() { return class extends Tag { constructor() { super("img", "", "", true); } toMarkdown() { const e = this.element; const attr = e.attributes; const pAttr = (e.parent && e.parent.attributes) || {}; const src = attr.src || pAttr.src; const cssClass = attr.class || pAttr.class; if (cssClass && cssClass.includes("emoji")) { return attr.title || pAttr.title; } if (src) { let alt = attr.alt || pAttr.alt || ""; const width = attr.width || pAttr.width; const height = attr.height || pAttr.height; if (width && height) { const pipe = this.element.parentNames.includes("table") ? "\\|" : "|"; alt = `${alt}${pipe}${width}x${height}`; } return "![" + alt + "](" + src + ")"; } return ""; } }; } static slice(name, suffix) { return class extends Tag { constructor() { super(name, "", suffix); } decorate(text) { if (!this.element.next) { this.suffix = ""; } return `${text}${this.suffix}`; } }; } static cell(name) { return class extends Tag { constructor() { super(name, "|"); } toMarkdown() { const text = this.element.innerMarkdown().trim(); if (text.includes("\n")) { // Unsupported format inside Markdown table cells let e = this.element; while ((e = e.parent)) { if (e.name === "table") { e.tag().invalid(); break; } } } return this.decorate(text); } }; } static li() { return class extends Tag.slice("li", "\n") { decorate(text) { 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", "") ); indent = Array(level).join("\t") + indent; } finally { if (attrs.class === "MsoListParagraphCxSpFirst") { indent = `\n\n${indent}`; } else if (attrs.class === "MsoListParagraphCxSpLast") { text = `${text}\n`; } } } return super.decorate(`${indent}* ${trimLeft(text)}`); } }; } static code() { return class extends Tag { constructor() { super("code", "`", "`"); } decorate(text) { if (this.element.parentNames.includes("pre")) { this.prefix = "\n\n```\n"; this.suffix = "\n```\n\n"; } else { this.inline = true; } text = $("