@ant-design/x-markdown
Version:
placeholder for @ant-design/x-markdown
106 lines • 3.06 kB
JavaScript
import { Marked } from 'marked';
export const other = {
escapeTestNoEncode: /[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/,
escapeTest: /[&<>"']/,
notSpaceStart: /^\S*/,
endingNewline: /\n$/,
escapeReplace: /[&<>"']/g,
escapeReplaceNoEncode: /[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/g,
completeFencedCode: /^ {0,3}(`{3,}|~{3,})([\s\S]*?)\n {0,3}\1[ \n\t]*$/
};
const escapeReplacements = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
};
const getEscapeReplacement = ch => escapeReplacements[ch];
export function escapeHtml(html, encode) {
if (encode) {
if (other.escapeTest.test(html)) {
return html.replace(other.escapeReplace, getEscapeReplacement);
}
} else {
if (other.escapeTestNoEncode.test(html)) {
return html.replace(other.escapeReplaceNoEncode, getEscapeReplacement);
}
}
return html;
}
class Parser {
options;
markdownInstance;
constructor(options = {}) {
const {
markedConfig = {}
} = options;
this.options = options;
this.markdownInstance = new Marked();
this.configureLinkRenderer();
this.configureParagraphRenderer();
this.configureCodeRenderer();
// user config at last
this.markdownInstance.use(markedConfig);
}
configureLinkRenderer() {
if (!this.options.openLinksInNewTab) return;
const renderer = {
link({
href,
title,
tokens
}) {
const text = this.parser.parseInline(tokens);
const titleAttr = title ? ` title="${title}"` : '';
return `<a href="${href}"${titleAttr} target="_blank" rel="noopener noreferrer">${text}</a>`;
}
};
this.markdownInstance.use({
renderer
});
}
configureParagraphRenderer() {
const {
paragraphTag
} = this.options;
if (!paragraphTag) return;
const renderer = {
paragraph({
tokens
}) {
return `<${paragraphTag}>${this.parser.parseInline(tokens)}</${paragraphTag}>\n`;
}
};
this.markdownInstance.use({
renderer
});
}
configureCodeRenderer() {
const renderer = {
code({
text,
raw,
lang,
escaped,
codeBlockStyle
}) {
const langString = (lang || '').match(other.notSpaceStart)?.[0];
const code = `${text.replace(other.endingNewline, '')}\n`;
const isIndentedCode = codeBlockStyle === 'indented';
// if code is indented, it's done because it has no end tag
const streamStatus = isIndentedCode || other.completeFencedCode.test(raw) ? 'done' : 'loading';
const escapedCode = escaped ? code : escapeHtml(code, true);
const classAttr = langString ? ` class="language-${escapeHtml(langString)}"` : '';
return `<pre><code data-block="true" data-state="${streamStatus}"${classAttr}>${escapedCode}</code></pre>\n`;
}
};
this.markdownInstance.use({
renderer
});
}
parse(content) {
return this.markdownInstance.parse(content);
}
}
export default Parser;