UNPKG

@podlite/markdown

Version:

=markdown - markdown text block

212 lines (206 loc) 8.45 kB
import { unified } from 'unified' import markdown from 'remark-parse' import remarkGfm from 'remark-gfm' import remarkMath from 'remark-math' import { toAny, AstTree, BlockImage, mkBlock, mkFomattingCode, mkFomattingCodeL, mkVerbatim, Location, mkMarkupCodeF, mkFormulaBlock, } from '@podlite/schema' import { mkRootBlock } from '@podlite/schema' import { mkImage } from '@podlite/schema' import { mkCaption } from '@podlite/schema' import { mkFomattingCodeDelete } from '@podlite/schema' type Md2astArgs = { lineOffset?: number } export const md2ast = (src: string, { lineOffset }: Md2astArgs = { lineOffset: 0 }): AstTree => { // first convert mardown to ast const md_tree = unified().use(markdown).use(remarkGfm).use(remarkMath).parse(src) // first pass : collect linkRefs let definitionMap = {} const applyLineOffset = ( (offset: number) => (location: Location): Location => { const { start, end } = location return { start: { ...start, line: start.line + offset, }, end: { ...end, line: end.line + offset, }, } } )(lineOffset) toAny({ processor: 1 }) .use({ '*:*': (writer, processor) => (node, ctx, interator) => { if (node.children) interator(node.children, { ...ctx }) }, }) .use({ ':definition': (writer, processor) => (node, ctx, interator) => { definitionMap[node.identifier] = node }, }) .run(md_tree) // second stage - make Universal AST nodes const uast = [] const res = toAny({ processor: 1 }) .use({ '*:*': (writer, processor) => (node, ctx, interator) => { console.warn('[Podlite:md2ast]- unhandled MD node') console.warn(JSON.stringify(node, null, 2)) if (node.children) interator(node.children, { ...ctx }) }, }) .use({ ':root': (writer, processor) => (node, ctx, interator) => { const { children, position, ...attr } = node return mkRootBlock({}, interator(children, ctx)) }, ':math': (writer, processor) => (node, ctx, interator) => { const { value, children, position, ...attr } = node return mkFormulaBlock({ value, location: applyLineOffset(position) }) }, ':inlineMath': (writer, processor) => (node, ctx, interator) => { const { value, children, position, ...attr } = node return mkMarkupCodeF(value) }, ':heading': (writer, processor) => (node, ctx, interator) => { const { children, position, ...attr } = node return mkBlock( { type: 'block', level: node.depth, name: 'head', location: applyLineOffset(position) }, interator(children, ctx), ) }, ':text': (writer, processor) => (node, ctx, interator) => { const { children, position, ...attr } = node return node.value // return mkNode({type:"text", value: node.value}) }, ':list': (writer, processor) => (node, ctx, interator) => { const savedListLevel = ctx['listLevel'] || 0 const { children, position, ...attr } = node return mkBlock( { type: 'list', level: savedListLevel + 1, list: node.ordered ? 'ordered' : 'itemized' }, interator(children, { ...ctx, listLevel: savedListLevel + 1, ordered: node.ordered }), ) }, ':listItem': (writer, processor) => (node, ctx, interator) => { const savedListLevel = ctx['listLevel'] || 0 const { children, position, ...attr } = node return mkBlock( { name: 'item', type: 'block', level: ctx['listLevel'], location: applyLineOffset(position) }, interator(children, { ...ctx, listLevel: savedListLevel, ordered: node.ordered }), ) }, ':paragraph': (writer, processor) => (node, ctx, interator) => { const { children, position, ...attr } = node return mkBlock( { type: 'para', location: applyLineOffset(position), text: 'text', margin: '' }, interator(children, { ...ctx }), ) }, ':linkReference': (writer, processor) => (node, ctx, interator) => { const { children, position, ...attr } = node const definition = definitionMap[attr.identifier] const meta = definition?.url || '' return mkFomattingCodeL({ meta }, interator(children, { ...ctx })) }, ':definition': (writer, processor) => (node, ctx, interator) => { return null }, ':inlineCode': (writer, processor) => (node, ctx, interator) => { const { children, position, ...attr } = node return mkFomattingCode({ name: 'C' }, [node.value]) }, ':strong': (writer, processor) => (node, ctx, interator) => { const { children, position, ...attr } = node return mkFomattingCode({ name: 'B' }, interator(children, { ...ctx })) }, ':emphasis': (writer, processor) => (node, ctx, interator) => { const { children, position, ...attr } = node return mkFomattingCode({ name: 'I' }, interator(children, { ...ctx })) }, ':link': (writer, processor) => (node, ctx, interator) => { const { children, position, ...attr } = node return mkFomattingCodeL({ meta: node.url }, interator(children, { ...ctx })) }, ':html': (writer, processor) => (node, ctx, interator) => { const { children, position, ...attr } = node return mkBlock({ type: 'block', name: 'Html', location: applyLineOffset(position) }, [mkVerbatim(node.value)]) }, ':code': (writer, processor) => (node, ctx, interator) => { const { children, position, lang, meta, ...attr } = node if (lang === 'mermaid' || lang === 'diagram') { return mkBlock({ type: 'block', name: 'Mermaid', config: [], location: applyLineOffset(position) }, [ mkVerbatim(node.value), ]) } let config = [] if (lang) { config.push({ name: 'lang', value: lang, type: 'string' }) } if (meta) { config.push({ name: 'meta', value: meta, type: 'string' }) } // filled as is from markdown return mkBlock({ type: 'block', name: 'code', config, location: applyLineOffset(position) }, [ mkVerbatim(node.value), ]) }, ':image': (writer, processor) => (node, ctx, interator): BlockImage => { const { title, alt, url, position, ...attr } = node let config = [] const content: any[] = [mkImage(url, alt)] if (title) content.push(mkCaption([title])) return mkBlock({ type: 'block', name: 'Image', config, location: applyLineOffset(position) }, content) }, ':thematicBreak': (writer, processor) => (node, ctx, interator) => { return null // TODO: add support for "thematic break" }, ':blockquote': (writer, processor) => (node, ctx, interator) => { const { children, position, ...attr } = node return mkBlock({ name: 'nested', location: applyLineOffset(position) }, interator(children, { ...ctx })) }, //table section ':table': (writer, processor) => (node, ctx, interator) => { const { children, position, align, ...attr } = node return mkBlock( { name: 'table', location: applyLineOffset(position), align }, interator(children, { ...ctx, isHeaderUsed: 0 }), ) }, ':tableRow': (writer, processor) => (node, ctx, interator) => { const { children, position, ...attr } = node const isHeaderUsed = ctx.isHeaderUsed++ return mkBlock( { name: isHeaderUsed ? 'table_row' : 'table_head', location: applyLineOffset(position) }, interator(children, { ...ctx }), ) }, ':tableCell': (writer, processor) => (node, ctx, interator) => { const { children, position, ...attr } = node return mkBlock({ name: 'table_cell', location: applyLineOffset(position) }, interator(children, { ...ctx })) }, ':delete': (writer, processor) => (node, ctx, interator) => { const { children, position, ...attr } = node return mkFomattingCodeDelete(interator(children, { ...ctx })) }, ':break': (writer, processor) => (node, ctx, interator) => { return null }, }) .run(md_tree) return res.interator }