import { unified } from 'unified' import parse from 'remark-parse' import { visit } from 'unist-util-visit' import remarkgfm from 'remark-gfm' //是markdown支持表格、选择框、连接等相关 const compile = (text: any) => { let content = text || '' const re = /`\{\{(.*?)\}\}/ let exec: any; while ((exec = re.exec(content))) { content = content.replace(re, '`\n' + `#${exec[0].substring(1)}\n`); } exec = null const reg = /#[desc]\s+/ while ((exec = reg.exec(content))) { const s = exec[0] content = content.replace(reg, `# -${s.substring(1)}`) } // content = content.replace(/\n/g, '
') return content; } const code = (): any => { return (tree: any) => { visit(tree, 'paragraph', (node: any, index: any, parent: any) => { const str = node?.children[0]?.value; if (/#\{\{(.*?)\}\}/.test(str)) { const code = parent?.children[index - 1]; if (code) { code.properties = properties(str); parent.children.splice(index, 1); return; } } }); return tree; }; } const inlineCode = (): any => { return (tree: any) => { visit(tree, 'inlineCode', (node: any, index: any, parent: any) => { const next = index < parent?.children?.length - 1 ? parent?.children[index + 1] : null; if (next) { const str = next.value; if (/#\{\{(.*?)\}\}/.test(str)) { node.properties = properties(str); parent.children.splice(index + 1, 1); } } }); return tree; }; } const captions = (): any => { return (tree: any) => { visit(tree, 'text', (_node: any, _index: any, _parent: any) => { if ('paragraph' === _parent?.type) { const value = _node?.value const re = /(\[.*\].*)/g const exsit = re.test(value) if (!exsit) { return } const children: any[] = [] const regex = /(\[.*\].*)/g const list = value?.split(regex) for (let s of list) { if (!regex.test(s)) { const n = { type: 'text', value: s } children.push(n) continue } const reg = /(\[.*\])(.*)/g const ts = (reg?.exec(s) ?? ['', '[]'])[1]?.replace(/[\[\]]/g, '')?.split('-') const n = { type: 'captions', properties: { st: ts[0] ?? '', et: ts[1] ?? '' }, value: s?.replace(/\[.*\]/g, '') } children.push(n) } _parent.children.splice(_index, 1, ...children) } }); return tree; }; } const heading = (): any => { return (tree: any) => { visit(tree, 'text', (_node: any, _index: any, _parent: any) => { if ('heading' === _parent?.type) { const value = _node?.value const re = /^(-[desc])/g if (!re.test(value)) { return } const args = value?.split(re).filter((it: any) => it) if (!args || args?.length == 0) { return } const tag = args[0]?.substring(1)?.trim() ?? '' const text = args[1]?.trim() ?? '' _node.value = text _parent.properties = { tag: tag } } }) return tree } } const traverse = (children: any, data: any) => { for (let i = 0; i < children?.length; i++) { if ('paragraph' === children[i]?.type) { traverse(children[i]?.children, data); } else { data.push(children[i]) } } } const build = (target: any, root: any) => { if (!root?.next) { root.next = [] root.next.push(target) return } const last = root?.next[root?.next?.length - 1] if (last?.depth < target?.depth) { build(target, last) } else { root?.next?.push(target) } } const deepClone = (obj: any) => { if (obj === null || typeof obj !== 'object') { return obj; } let newObj: any = Array.isArray(obj) ? [] : {}; for (let key in obj) { newObj[key] = deepClone(obj[key]); } return newObj; } const timer = (str: string) => { try { if (!str) { return undefined } if (str.endsWith('.')) { str = str.substring(0, str.length - 1) } let ms: number = 0 let s: string = str if (str.includes('.')) { ms = Number(str.substring(str.lastIndexOf('.') + 1)) s = str.substring(0, str.lastIndexOf('.')) } const t = s.split(':') let ts: number = 0 for (let i = 0; i < t.length; i++) { ts += Number(t[t.length - 1 - i]) * Math.pow(60, i) } return ts + ms / 1000 } catch (error) { console.error(error) return undefined } } const properties = (str: any) => { const match = str?.match(/#\{\{(.*?)\}\}/); return { ext: match[1].trim(), }; } const Mdast = { parse: (text?: string) => { const content = compile(text || '') const processer = unified() .use(parse) .use(remarkgfm) .use(code) .use(inlineCode) .use(heading) .use(captions) const tree = processer.parse(content) const ast = processer.runSync(tree) processer.freeze() return ast }, captions: (mdast: any) => { const _mdast = deepClone(mdast) const data: any = []; const processer = unified().use(() => (tree: any) => { visit(tree, 'captions', (node: any) => { const properties = node?.properties; const item = { st: timer(properties?.st), et: timer(properties?.et), value: node?.value, }; data.push(item); }); }); processer.runSync(_mdast); processer.freeze(); return data; }, actives: (mdast: any) => { const _mdast = deepClone(mdast) const data: any = [] const processer = unified() .use(() => (tree: any) => { visit(tree, 'code', (node: any) => { const ext: string = node.properties?.ext if (ext?.startsWith('active')) { const item = { lang: node?.lang, st: ext?.startsWith('active ') ? timer(ext?.substring(7).trim()) : undefined, et: undefined, value: node?.value?.trim(), position: node?.position } data.push(item) } }) }) processer.runSync(_mdast) processer.freeze() return data }, heading: { list: (mdast: any) => { const _mdast = deepClone(mdast) let curr: any = null const children: any = [] traverse(_mdast.children, children) children?.forEach((node: any) => { if ('heading' === node?.type) { node.properties = node?.properties || {} node.properties.stlist = [] node.properties.etlist = [] curr = null curr = node } if ('captions' === node?.type && curr && curr?.properties) { curr?.properties?.stlist.push(node?.properties?.st) curr?.properties?.etlist.push(node?.properties?.et) } }); children.forEach((node: any, _i: number) => { if ('heading' === node?.type && node?.properties?.stlist?.length === 0) { let j = _i + 1; while (j < _mdast?.children?.length) { const child = _mdast?.children[j] if ('heading' === child?.type && child?.properties?.stlist?.length > 0) { node?.properties?.stlist.push(...(child?.properties?.stlist ?? [])) node?.properties?.etlist.push(...(child?.properties?.etlist ?? [])) } j++; } } }) const arry: any = [] children.forEach((node: any) => { if ('heading' === node?.type) { const st = node?.properties?.stlist.map((s: any) => timer(s)).sort((a: any, b: any) => a - b)[0] const et = node?.properties?.etlist.map((s: any) => timer(s)).sort((a: any, b: any) => b - a)[0] node.properties.st = st node.properties.et = et delete node?.properties?.stlist delete node?.properties?.etlist arry.push(node) } }) return arry; }, tree: (mdast: any) => { const heading: Array = Mdast.heading.list(mdast) let c: any = null let children: any = [] heading.forEach((h: any, _i: number) => { if (c === null || h?.depth <= c?.depth) { c = deepClone(h) c.next = [] children?.push(c) } else { if (c?.next?.length === 0) { c?.next?.push(deepClone(h)) } else { build(deepClone(h), c) } } }); return children } } } export default Mdast