1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181
| import { RpcWorkerServer } from './rpc-worker'; import marked, { Tokens } from 'marked'; import { TAB_CONTENT } from '../multi-tab'; import prettier from 'prettier/standalone'; import prettierParserMarkdown from 'prettier/parser-markdown'; import highlight from 'highlight.js'; import sanitizeHtml from 'sanitize-html'; import md5 from 'md5';
export type MarkdownWorkerToc = { id: string; anchor: string; level: number; text: string };
export type MarkdownWorkerMethod = { markdownRender: ({ text }: { text: string }) => { content: string; toc: MarkdownWorkerToc[] }; prettierFormat: ({ text, bigTextLength, formatMd5List }: { text: string; bigTextLength: number; formatMd5List: string[] }) => { text: string; formatMd5List: string[]; }; };
const longPrettierHeaderCount = 200;
const ctx: Worker = self as any;
const server = new RpcWorkerServer<MarkdownWorkerMethod>(ctx);
marked.setOptions({ highlight: function (code, lang) { return highlight.highlight(highlight.getLanguage(lang) ? lang : 'plaintext', code, true).value; }, });
const UNIT_KEY = `//tab=unit_key_for_tab`;
server.addHandler('markdownRender', ({ text }) => { const renderer = new marked.Renderer(); const _oldCode = renderer.code.bind(renderer); renderer.code = (code, language, ...args) => { if (code.indexOf(UNIT_KEY) > -1) { const tabStr = _.nth(_.split(code, '-'), 1); const finalCode = _.replace(code, `${UNIT_KEY}-${tabStr}-`, ''); return `<div class="${TAB_CONTENT}" data-language="${tabStr}"><div class='code-container'><div class='copy-code-btn'></div>${_oldCode( finalCode, language, ...args, )}</div></div>`; }
if (language === 'mermaid') { return '<div class="mermaid">' + code + '</div>'; }
return `<div class='code-container'><div class='copy-code-btn'></div>${_oldCode(code, language, ...args)}</div>`; }; const toc: MarkdownWorkerToc[] = []; const levelNIndex: Record<number, number> = {}; renderer.heading = function (text, level, raw, slugger) { if (!levelNIndex[level]) { levelNIndex[level] = 1; } else { levelNIndex[level] = levelNIndex[level] + 1; }
const headerIdMatch = /\s+\{#([a-zA-Z0-9_-]+)\}$/gi.exec(text); const anchor = encodeURIComponent(headerIdMatch ? headerIdMatch[1] : slugger.slug(raw)); text = text.replace(/\s+\{#([a-zA-Z0-9_-]+)\}$/gi, ''); toc.push({ id: anchor, anchor: `#${anchor}`, level: level, text: text, }); if (level > 3) { return `<h${level} id='${anchor}' class='toc-id'>${text}</h${level}>\n`; } else { return `<h${level} id='${anchor}' class='toc-id'><a id='user-content-${anchor}' class='anchor' href='#${anchor}'><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a>${text}</h${level}>\n`; } };
const _oldImage = renderer.image.bind(renderer); renderer.image = function (href: string, title: string, text: string) { if (href.indexOf('.....') > -1) { href = `${'...'}?target=${encodeURIComponent(href)}`; } return _oldImage(href, title, text); }; const _oldLink = renderer.link.bind(renderer); renderer.link = function (href: string, title: string, text: string) { if (href.indexOf('.....') > -1) { href = `${'...'}?target=${encodeURIComponent(href)}`; } return _oldLink(href, title, text); };
let preLineTab = ''; const html = marked(text, { renderer: renderer, walkTokens: (tokens) => { const token = _.first(_.castArray(tokens)) as Tokens.HTML | Tokens.Code; if (token.type === 'html') { const tabMatch = token.raw.match(/<!-- tab=(\S+) -->/); if (tabMatch) { preLineTab = tabMatch[1]; } } else if (preLineTab && token.type === 'code') { token.text = `${UNIT_KEY}-${preLineTab}-${token.text}`; preLineTab = ''; } else if (preLineTab && token.type !== 'code') { preLineTab = ''; } }, });
const endHtml = `\n<div class='markdown-preview-padding-bottom'></div>\n`; return { content: sanitizeHtml(html + endHtml, { allowedTags: sanitizeHtml.defaults.allowedTags.concat(['input', 'img', 'svg', 'path', 'details', 'summary', 'del']), allowedAttributes: { '*': ['class', 'id', 'href', 'style', 'width', 'height', 'data-*'], input: ['checked', 'type', 'disabled'], img: ['src', 'alt'], svg: ['xmlns', 'viewBox', 'version', 'aria-hidden'], path: ['d', 'fill-rule'], }, parser: { lowerCaseAttributeNames: false, }, }), toc: toc, }; });
const longTextPritter = (text: string, formatMd5List: Set<string>): { text: string; formatMd5List: string[] } => { console.info(`in long text pritter`); const textLines = text.split('\n'); let inCodeBlock = false; const headerBlocks: string[] = []; let currentHeaderLines: string[] = []; const newMd5List = Array.from(formatMd5List); for (let i = textLines.length - 1; i >= 0; i--) { const line = textLines[i]; currentHeaderLines.unshift(line); if (!inCodeBlock && currentHeaderLines.length > longPrettierHeaderCount && /^#{1,6} /.test(line)) { headerBlocks.push(currentHeaderLines.join('\n')); currentHeaderLines = []; } if (line.startsWith('```')) { inCodeBlock = !inCodeBlock; } } if (currentHeaderLines.length) { headerBlocks.push(currentHeaderLines.join('\n')); } headerBlocks.reverse(); _.map(headerBlocks, (headerText, index) => { const headerMd5 = md5(headerText); if (formatMd5List.has(headerMd5)) { return; } else { headerBlocks[index] = prettier.format(headerText, { parser: 'markdown', plugins: [prettierParserMarkdown], tabWidth: 2 }); newMd5List.push(md5(headerBlocks[index])); } }); return { text: headerBlocks.join('\n'), formatMd5List: newMd5List }; };
server.addHandler('prettierFormat', ({ text, bigTextLength, formatMd5List }) => { console.time('prettier'); text = text.replace(/\t/gi, ' '); const result: { text: string; formatMd5List: string[] } = text.length > bigTextLength ? longTextPritter(text, new Set(formatMd5List)) : { text: prettier.format(text, { parser: 'markdown', plugins: [prettierParserMarkdown], tabWidth: 2 }), formatMd5List: Array.from(formatMd5List), }; console.timeEnd('prettier'); return result; });
export default null as any;
|