113 lines
3.0 KiB
TypeScript
113 lines
3.0 KiB
TypeScript
/* eslint-disable @typescript-eslint/no-var-requires */
|
|
import MarkdownIt from 'markdown-it';
|
|
import { parseHeader } from '../utils/parseHeader';
|
|
import { highlight } from './plugins/highlight';
|
|
import { slugify } from './plugins/slugify';
|
|
import { highlightLinePlugin } from './plugins/highlightLines';
|
|
import { lineNumberPlugin } from './plugins/lineNumbers';
|
|
import { componentPlugin } from './plugins/component';
|
|
import { containerPlugin } from './plugins/containers';
|
|
import { snippetPlugin } from './plugins/snippet';
|
|
import { hoistPlugin } from './plugins/hoist';
|
|
import { preWrapperPlugin } from './plugins/preWrapper';
|
|
import { linkPlugin } from './plugins/link';
|
|
import { extractHeaderPlugin } from './plugins/header';
|
|
import type { Header } from '../../shared';
|
|
|
|
const emoji = require('markdown-it-emoji');
|
|
const anchor = require('markdown-it-anchor');
|
|
const toc = require('markdown-it-table-of-contents');
|
|
|
|
export interface MarkdownOptions extends MarkdownIt.Options {
|
|
lineNumbers?: boolean;
|
|
config?: (md: MarkdownIt) => void;
|
|
anchor?: {
|
|
permalink?: boolean;
|
|
permalinkBefore?: boolean;
|
|
permalinkSymbol?: string;
|
|
};
|
|
// https://github.com/Oktavilla/markdown-it-table-of-contents
|
|
toc?: any;
|
|
externalLinks?: Record<string, string>;
|
|
}
|
|
|
|
export interface MarkdownParsedData {
|
|
hoistedTags?: string[];
|
|
links?: string[];
|
|
headers?: Header[];
|
|
vueCode?: string;
|
|
}
|
|
|
|
export interface MarkdownRenderer {
|
|
__data: MarkdownParsedData;
|
|
render: (src: string, env?: any) => { html: string; data: any };
|
|
}
|
|
|
|
export const createMarkdownRenderer = (options: MarkdownOptions = {}): MarkdownRenderer => {
|
|
const md = MarkdownIt({
|
|
html: true,
|
|
linkify: true,
|
|
highlight,
|
|
...options,
|
|
});
|
|
|
|
// custom plugins
|
|
md.use(componentPlugin)
|
|
.use(highlightLinePlugin)
|
|
.use(preWrapperPlugin)
|
|
.use(snippetPlugin)
|
|
.use(hoistPlugin)
|
|
.use(containerPlugin)
|
|
.use(extractHeaderPlugin)
|
|
.use(linkPlugin, {
|
|
target: '_blank',
|
|
rel: 'noopener noreferrer',
|
|
...options.externalLinks,
|
|
})
|
|
|
|
// 3rd party plugins
|
|
.use(emoji)
|
|
.use(anchor, {
|
|
slugify,
|
|
permalink: anchor.permalink.linkInsideHeader({
|
|
symbol: `
|
|
<span aria-hidden="true" class="anchor">#</span>
|
|
`,
|
|
}),
|
|
permalinkBefore: true,
|
|
permalinkSymbol: '#',
|
|
permalinkAttrs: () => ({ 'aria-hidden': true }),
|
|
tabIndex: false,
|
|
...options.anchor,
|
|
})
|
|
.use(toc, {
|
|
slugify,
|
|
includeLevel: [2, 3],
|
|
format: parseHeader,
|
|
...options.toc,
|
|
});
|
|
|
|
// apply user config
|
|
if (options.config) {
|
|
options.config(md);
|
|
}
|
|
|
|
if (options.lineNumbers) {
|
|
md.use(lineNumberPlugin);
|
|
}
|
|
|
|
// wrap render so that we can return both the html and extracted data.
|
|
const render = md.render;
|
|
const wrappedRender: MarkdownRenderer['render'] = src => {
|
|
(md as any).__data = {};
|
|
const html = render.call(md, src);
|
|
return {
|
|
html,
|
|
data: (md as any).__data,
|
|
};
|
|
};
|
|
(md as any).render = wrappedRender;
|
|
|
|
return md as any;
|
|
};
|