@diplodoc/transform
Version:
A simple transformer of text in YFM (Yandex Flavored Markdown) to HTML
206 lines (163 loc) • 5.79 kB
text/typescript
import type {EnvType, MarkdownIt, MarkdownItPluginOpts, OptionsType} from './typings';
import type Token from 'markdown-it/lib/token';
import DefaultMarkdownIt from 'markdown-it';
import attrs from 'markdown-it-attrs';
import DefaultPlugins from './plugins';
import {preprocess} from './preprocessors';
import {log} from './log';
import makeHighlight from './highlight';
import extractTitle from './title';
import getHeadings from './headings';
import sanitizeHtml, {defaultOptions, sanitizeStyles} from './sanitize';
import {olAttrConversion} from './plugins/ol-attr-conversion';
function initMarkdownIt(options: OptionsType) {
const {
allowHTML = false,
linkify = false,
breaks = true,
highlightLangs = {},
disableRules = [],
} = options;
const highlight = makeHighlight(highlightLangs);
const md = new DefaultMarkdownIt({html: allowHTML, linkify, highlight, breaks}) as MarkdownIt;
if (disableRules?.length) {
md.disable(disableRules);
}
const env = {
// TODO: move md.meta directly to env
get meta() {
return md.meta;
},
set meta(value) {
md.meta = value;
},
// TODO: move md.assets directly to env
get assets() {
return md.assets;
},
set assets(value) {
md.assets = value;
},
headings: [],
title: '',
} as EnvType;
// Plugin options is the plugin context that remains during the build of one file
const pluginOptions = getPluginOptions(options);
// Init the plugins. Which install the md rules (core, block, ...)
initPlugins(md, options, pluginOptions);
// Init preprocessor and MD parser
const parse = initParser(md, options, env, pluginOptions);
// Init render to HTML compiler
const compile = initCompiler(md, options, env);
return {parse, compile, env};
}
function getPluginOptions(options: OptionsType) {
const {
vars = {},
path,
extractTitle,
conditionsInCode = false,
disableLiquid = false,
...customOptions
} = options;
return {
...customOptions,
conditionsInCode,
vars,
path,
extractTitle,
disableLiquid,
log,
} as MarkdownItPluginOpts;
}
function initPlugins(md: MarkdownIt, options: OptionsType, pluginOptions: MarkdownItPluginOpts) {
const {
linkify = false,
linkifyTlds,
leftDelimiter = '{',
rightDelimiter = '}',
plugins = DefaultPlugins,
enableMarkdownAttrs,
} = options;
// TODO: set enableMarkdownAttrs to false by default in next major
if (enableMarkdownAttrs !== false) {
// Need for ids of headers
md.use(attrs, {leftDelimiter, rightDelimiter});
}
md.use(olAttrConversion);
plugins.forEach((plugin) => md.use(plugin, pluginOptions));
if (linkify && linkifyTlds) {
md.linkify.tlds(linkifyTlds, true);
}
}
function initParser(
md: MarkdownIt,
options: OptionsType,
env: EnvType,
pluginOptions: MarkdownItPluginOpts,
) {
return (input: string) => {
const {
extractTitle: extractTitleOption,
needTitle,
needFlatListHeadings = false,
getPublicPath,
} = options;
// Run preprocessor
input = preprocess(input, pluginOptions, options, md);
// Generate global href link
const href = getPublicPath ? getPublicPath(options) : '';
// Generate MD tokens
let tokens = md.parse(input, env);
if (extractTitleOption) {
const {title, tokens: slicedTokens, titleTokens} = extractTitle(tokens);
tokens = slicedTokens;
// title tokens include other tokens that need to be transformed
if (titleTokens.length > 1) {
env.title = md.renderer.render(titleTokens, md.options, env);
} else {
env.title = title;
}
}
if (needTitle) {
env.title = extractTitle(tokens).title;
}
env.headings = getHeadings(tokens, needFlatListHeadings, href);
return tokens;
};
}
function initCompiler(md: MarkdownIt, options: OptionsType, env: EnvType) {
const {needToSanitizeHtml = true, renderInline = false, sanitizeOptions, sanitize} = options;
return (tokens: Token[]) => {
// Remove inline tokens if inline mode is activated
if (renderInline) {
tokens = tokens.filter((token) => token.type === 'inline');
}
// Generate HTML
let html = md.renderer.render(tokens, md.options, env);
if (!needToSanitizeHtml) {
return html;
}
// If a custom sanitizer was used, we need to ensure styles are sanitized
// unless explicitly disabled via disableStyleSanitizer option
if (sanitize && !(sanitizeOptions?.disableStyleSanitizer ?? false)) {
const baseOptions = sanitizeOptions || defaultOptions;
const mergedOptions = {
...baseOptions,
cssWhiteList: {
...(defaultOptions.cssWhiteList || {}),
...(baseOptions.cssWhiteList || {}),
...(env.additionalOptionsCssWhiteList || {}),
},
};
html = sanitizeStyles(html, mergedOptions);
}
const sanitizedHtml = sanitize
? sanitize(html, sanitizeOptions)
: sanitizeHtml(html, sanitizeOptions, {
cssWhiteList: env.additionalOptionsCssWhiteList,
});
return sanitizedHtml;
};
}
export = initMarkdownIt;