const MarkdownIt = require('markdown-it'); const md5 = require('md5'); const noteStyle = require('./noteStyle'); const { fileExtension } = require('./pathUtils'); const memoryCache = require('memory-cache'); // /!\/!\ Note: the order of rules is important!! /!\/!\ const rules = { fence: require('./MdToHtml/rules/fence').default, sanitize_html: require('./MdToHtml/rules/sanitize_html').default, image: require('./MdToHtml/rules/image'), checkbox: require('./MdToHtml/rules/checkbox').default, katex: require('./MdToHtml/rules/katex'), link_open: require('./MdToHtml/rules/link_open'), html_image: require('./MdToHtml/rules/html_image'), highlight_keywords: require('./MdToHtml/rules/highlight_keywords'), code_inline: require('./MdToHtml/rules/code_inline'), fountain: require('./MdToHtml/rules/fountain'), mermaid: require('./MdToHtml/rules/mermaid').default, }; const setupLinkify = require('./MdToHtml/setupLinkify'); const hljs = require('highlight.js'); const uslug = require('uslug'); const markdownItAnchor = require('markdown-it-anchor'); // The keys must match the corresponding entry in Setting.js const plugins = { mark: { module: require('markdown-it-mark') }, footnote: { module: require('markdown-it-footnote') }, sub: { module: require('markdown-it-sub') }, sup: { module: require('markdown-it-sup') }, deflist: { module: require('markdown-it-deflist') }, abbr: { module: require('markdown-it-abbr') }, emoji: { module: require('markdown-it-emoji') }, insert: { module: require('markdown-it-ins') }, multitable: { module: require('markdown-it-multimd-table'), options: { multiline: true, rowspan: true, headerless: true } }, toc: { module: require('markdown-it-toc-done-right'), options: { listType: 'ul', slugify: uslugify } }, expand_tabs: { module: require('markdown-it-expand-tabs'), options: { tabWidth: 4 } }, }; const defaultNoteStyle = require('./defaultNoteStyle'); function uslugify(s) { return uslug(s); } class MdToHtml { constructor(options = null) { if (!options) options = {}; // Must include last "/" this.resourceBaseUrl_ = 'resourceBaseUrl' in options ? options.resourceBaseUrl : null; this.cachedOutputs_ = {}; this.lastCodeHighlightCacheKey_ = null; this.cachedHighlightedCode_ = {}; this.ResourceModel_ = options.ResourceModel; this.pluginOptions_ = options.pluginOptions ? options.pluginOptions : {}; this.contextCache_ = new memoryCache.Cache(); this.tempDir_ = options.tempDir; this.fsDriver_ = { writeFile: (/* path, content, encoding = 'base64'*/) => { throw new Error('writeFile not set'); }, exists: (/* path*/) => { throw new Error('exists not set'); }, cacheCssToFile: (/* cssStrings*/) => { throw new Error('cacheCssToFile not set'); }, }; if (options.fsDriver) { if (options.fsDriver.writeFile) this.fsDriver_.writeFile = options.fsDriver.writeFile; if (options.fsDriver.exists) this.fsDriver_.exists = options.fsDriver.exists; if (options.fsDriver.cacheCssToFile) this.fsDriver_.cacheCssToFile = options.fsDriver.cacheCssToFile; } } fsDriver() { return this.fsDriver_; } tempDir() { return this.tempDir_; } static pluginNames() { const output = []; for (const n in rules) output.push(n); for (const n in plugins) output.push(n); return output; } pluginOptions(name) { let o = this.pluginOptions_[name] ? this.pluginOptions_[name] : {}; o = Object.assign({ enabled: true, }, o); return o; } pluginEnabled(name) { return this.pluginOptions(name).enabled; } processPluginAssets(pluginAssets) { const files = []; const cssStrings = []; for (const pluginName in pluginAssets) { for (const asset of pluginAssets[pluginName]) { let mime = asset.mime; if (!mime && asset.inline) throw new Error('Mime type is required for inline assets'); if (!mime) { const ext = fileExtension(asset.name).toLowerCase(); // For now it's only useful to support CSS and JS because that's what needs to be added // by the caller with