@f-fjs/tidy-markdown
Version:
Fix ugly markdown.
300 lines • 10.9 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const indent_1 = __importDefault(require("indent"));
const parse5_1 = require("parse5");
const language_code_rewrites_json_1 = __importDefault(require("./language-code-rewrites.json"));
const node_1 = require("./node");
const tables_1 = require("./tables");
const tree_adapter_1 = __importDefault(require("./tree-adapter"));
const utils_1 = require("./utils");
const CODE_HIGHLIGHT_REGEX = /(?:highlight highlight|lang(?:uage)?)-(\S+)/;
const { insertTextBefore, insertText, isTextNode } = tree_adapter_1.default;
function indentChildren(node) {
let allChildrenAreElements = true;
if (utils_1.isParentNode(node))
for (const child of Array.from(node.childNodes)) {
if (isTextNode(child)) {
allChildrenAreElements = false;
}
}
if (allChildrenAreElements) {
if (utils_1.isParentNode(node))
Array.from(node.childNodes)
.forEach(child => insertTextBefore(node, '\n ', child));
return insertText(node, '\n');
}
}
// TODO: handle indenting nested children
// regex taken from https://github.com/chjj/marked/blob/8f9d0b/lib/marked.js#L452
function isValidLink(link) {
return /.+(?:@|:\/).+/.test(link);
}
const fallback = () => true;
/**
* This array holds a set of "converters" that process DOM nodes and output
* Markdown. The `filter` property determines what nodes the converter is run
* on. The `replacement` function takes the content of the node and the node
* itself and returns a string of Markdown. The `surroundingBlankLines` option
* determines whether or not the block should have a blank line before and after
* it. Converters are matched to nodes starting from the top of the converters
* list and testing each one downwards.
* @type {Array}
*/
exports.Converters = new Array({
filter(node) {
return node_1.isConverterNode(node.parentNode) && node.parentNode._converter.filter === fallback;
},
surroundingBlankLines: false,
replacement(_content, node) {
indentChildren(node);
return '';
}
}, {
filter: 'p',
surroundingBlankLines: true,
replacement(content) {
return content;
}
}, {
filter: ['td', 'th'],
surroundingBlankLines: false,
replacement(content) {
return content;
}
}, {
filter: ['tbody', 'thead', 'tr'],
surroundingBlankLines: false,
replacement() {
return '';
}
}, {
filter: ['del', 's', 'strike'],
surroundingBlankLines: false,
replacement(content) {
return `~~${content}~~`;
}
}, {
filter: ['em', 'i'],
surroundingBlankLines: false,
replacement(content) {
if (content.indexOf('_') >= 0) {
return `*${content.replace(/\*/g, '\\*')}*`;
}
else {
return `_${content.replace(/_/g, '\\_')}_`;
}
}
}, {
filter: ['strong', 'b'],
surroundingBlankLines: false,
replacement(content) {
return `**${content.replace(/\*/g, '\\*')}**`;
}
}, {
filter: 'br',
surroundingBlankLines: false,
trailingWhitespace(node) {
if (utils_1.isElement(node.nextSibling) && node.nextSibling.tagName === 'br')
return '';
return '\n';
},
replacement() {
return '<br>';
}
}, {
filter: 'a',
surroundingBlankLines: false,
replacement(content, node, links) {
const refUrl = utils_1.getAttribute(node, 'href') || '';
const refTitle = utils_1.getAttribute(node, 'title');
const referenceLink = links.find(({ url, title }) => url == refUrl && title == refTitle);
if (referenceLink) {
if (content.toLowerCase() === referenceLink.name) {
return `[${content}]`;
}
else {
return `[${content}][${referenceLink.name}]`;
}
}
else if (refTitle) {
return `[${content}](${refUrl} \"${refTitle}\")`;
}
else if (isValidLink(refUrl) && (content === refUrl || content === refUrl.replace(/^mailto:/, ''))) {
return `<${content}>`;
}
else {
return `[${content}](${refUrl})`;
}
}
}, {
filter(node) {
// Ignore img nodes that have custom styling or other attributes
return utils_1.isElement(node) && node.tagName === 'img' && utils_1.noExtraAttributes(node, 'alt', 'src', 'title');
},
surroundingBlankLines: false,
replacement(_content, node, links) {
const alt = utils_1.getAttribute(node, 'alt') || '';
const refUrl = utils_1.getAttribute(node, 'src') || '';
const refTitle = utils_1.getAttribute(node, 'title');
const referenceLink = links.find(({ url, title }) => url == refUrl && title == refTitle);
if (referenceLink) {
if (alt.toLowerCase() === referenceLink.name) {
return `![${alt}]`;
}
else {
return `![${alt}][${referenceLink.name}]`;
}
}
else if (refTitle) {
return ``;
}
else {
return ``;
}
}
}, {
filter(node) {
return node.type === 'checkbox' && utils_1.isElement(node.parentNode) && node.parentNode.tagName === 'li';
},
surroundingBlankLines: false,
replacement(_content, node) {
return (node['checked'] ? '[x]' : '[ ]') + ' ';
}
}, {
filter: 'table',
surroundingBlankLines: true,
replacement(_content, node) {
const { alignments, rows } = tables_1.extractRows(node);
const columnWidths = tables_1.getColumnWidths(rows);
// const totalCols = rows[0].length
const out = [
tables_1.formatRow(rows[0], alignments, columnWidths),
tables_1.formatHeaderSeparator(alignments, columnWidths),
...rows.slice(1).map(row => tables_1.formatRow(row, alignments, columnWidths))
];
return out.join('\n');
}
}, {
filter: 'pre',
surroundingBlankLines: true,
replacement(content, node) {
var _a, _b, _c, _d;
let language;
if (utils_1.isParentNode(node)) {
const first = node.childNodes[0];
if (utils_1.isElement(first) && first.tagName === 'code') {
language = (_b = (_a = utils_1.getAttribute(node.childNodes[0], 'class')) === null || _a === void 0 ? void 0 : _a.match(CODE_HIGHLIGHT_REGEX)) === null || _b === void 0 ? void 0 : _b[1];
}
}
if (language == null && utils_1.isElement(node.parentNode) && node.parentNode.tagName === 'div') {
language = (_d = (_c = utils_1.getAttribute(node.parentNode, 'class')) === null || _c === void 0 ? void 0 : _c.match(CODE_HIGHLIGHT_REGEX)) === null || _d === void 0 ? void 0 : _d[1];
}
if (language != null) {
language = language.toLowerCase();
if (language_code_rewrites_json_1.default[language] != null) {
language = language_code_rewrites_json_1.default[language];
}
}
return utils_1.delimitCode(`${language || ''}\n${content}\n`, '```');
}
}, {
filter: 'code',
surroundingBlankLines: false,
replacement(content, node) {
if ((utils_1.isElement(node.parentNode) && node.parentNode.tagName) !== 'pre') {
return utils_1.delimitCode(content, '`'); // inline code
}
else {
// code that we'll handle once it reaches the pre tag. we only bother
// passing it through this converter to avoid it being serialized before
// it gets to the pre tag
return content;
}
}
}, {
filter(node) {
return utils_1.isElement(node) && node.tagName === 'div' && CODE_HIGHLIGHT_REGEX.test(node['className']);
},
surroundingBlankLines: true,
replacement(content) {
return content;
}
}, {
filter: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
surroundingBlankLines: true,
replacement(content, node) {
utils_1.assertIsElement(node);
const hLevel = parseInt(node.tagName.charAt(1), 10);
return `${'#'.repeat(hLevel)} ${content}`;
}
}, {
filter: 'hr',
surroundingBlankLines: true,
replacement() {
return '-'.repeat(80);
}
}, {
filter: 'blockquote',
surroundingBlankLines: true,
replacement(content) {
return indent_1.default(content, '> ');
}
}, {
filter: 'li',
surroundingBlankLines: false,
trailingWhitespace: '\n',
replacement(content, node) {
if (content.indexOf('\n') >= 0) {
// the indent here is for all the lines after the first, so we only need
// do it if there's a linebreak in the content
content = indent_1.default(content, ' ').trimLeft();
}
const parent = node.parentNode;
const prefix = utils_1.isElement(parent) && parent.tagName === 'ol' ? parent.childNodes.indexOf(node) + 1 + '. ' : '- ';
return prefix + content;
}
}, {
filter: ['ul', 'ol'],
surroundingBlankLines(node) {
let p = node;
while (p.parentNode) {
p = p.parentNode;
if (utils_1.isElement(p) && p.tagName === 'li')
break;
}
return !p
? true
: {
leading: '\n',
trailing: ''
};
},
replacement(content, node) {
let p = node;
while (p.parentNode) {
p = p.parentNode;
if (utils_1.isElement(p) && p.tagName === 'li')
break;
}
if (utils_1.isElement(p === null || p === void 0 ? void 0 : p.parentNode) && p.parentNode.tagName === 'ol' && !(utils_1.isElement(node) && node.tagName === 'ol')) {
content = indent_1.default(content, ' ');
}
return content.trimRight();
}
}, {
filter: '_comment',
replacement(content) {
return `<!-- ${content} -->`;
}
}, {
filter: fallback,
surroundingBlankLines: true,
replacement(_content, node) {
indentChildren(node);
return parse5_1.serialize({ children: [node], nodeName: '#document-fragment', quirksMode: false }, { treeAdapter: tree_adapter_1.default });
}
});
//# sourceMappingURL=converters.js.map