UNPKG

@autoanki/plugin-source-markdown

Version:

Extract Anki cards from Markdown files

122 lines (108 loc) 3.65 kB
import { unified } from 'unified'; import { Code, Root } from 'mdast'; import remarkParse from 'remark-parse'; import remarkStringify from 'remark-stringify'; import { visit } from 'unist-util-visit'; import type { AutoankiPlugin, AutoankiPluginApi, SourcePlugin, SourcePluginParsingOutput, } from '@autoanki/core'; import yamlPlugin from '@autoanki/plugin-source-yaml'; interface Metadata { index: number; yamlMetadata: unknown; } const mdProcessor = unified().use(remarkParse); const mdStringifyProcessor = unified().use(remarkStringify); const encoder = new TextEncoder(); const decoder = new TextDecoder('utf8'); export class MarkdownSourcePlugin implements SourcePlugin { static pluginName = '@autoanki/plugin-source-markdown'; private markdownParseCache: Record< string, { ast: Root; yamlBlocks: Code[] } > = {}; private yamlPlugin; constructor(api: AutoankiPluginApi) { this.yamlPlugin = new yamlPlugin.source!(api); } async writeBackToInput( inputKey: string, originalInputContent: ArrayBufferLike, notes: SourcePluginParsingOutput[] ): Promise<ArrayBufferLike> { const thisFileCache = this.markdownParseCache[inputKey]; const notesPerYamlBlock = notes.reduce((group, note) => { const metadata = note.metadata as Metadata; (group[metadata.index] = group[metadata.index] || []).push(note); return group; }, {} as Record<number, SourcePluginParsingOutput[]>); const modifiedMarkdownBlocks = await Promise.all( Object.entries(notesPerYamlBlock).map( async ([i, notesOfThisYamlBlock]) => { const ithBlock = Number.parseInt(i, 10); const newContent = await this.yamlPlugin.writeBackToInput( `${inputKey}-${i}`, encoder.encode(thisFileCache.yamlBlocks[ithBlock].value), notesOfThisYamlBlock.map((note) => { return { note: note.note, metadata: (note.metadata as Metadata).yamlMetadata, } as SourcePluginParsingOutput; }) ); return { ithBlock, newContent, }; } ) ); for (const block of modifiedMarkdownBlocks) { thisFileCache.yamlBlocks[block.ithBlock].value = decoder.decode( block.newContent ); } return encoder.encode(mdStringifyProcessor.stringify(thisFileCache.ast)); } async parseFromInput(inputKey: string, inputContent: ArrayBufferLike) { const enc = new TextDecoder('utf8'); const input = enc.decode(inputContent); if (!this.markdownParseCache[inputKey]) { this.markdownParseCache[inputKey] = { ast: mdProcessor.parse(input), yamlBlocks: [], }; visit(this.markdownParseCache[inputKey].ast, 'code', (node, _) => { if (node.lang === 'autoanki') { this.markdownParseCache[inputKey].yamlBlocks.push(node); } }); const result = await Promise.all( this.markdownParseCache[inputKey].yamlBlocks.map((yaml, i) => { return this.yamlPlugin.parseFromInput( `${inputKey}-${i}`, encoder.encode(yaml.value) ); }) ); return result.flatMap((outputs, ithYamlBlock) => { return outputs.map((output) => { return { note: output.note, metadata: { index: ithYamlBlock, yamlMetadata: output.metadata, } as Metadata, } as SourcePluginParsingOutput; }); }); } return []; } } export default { source: MarkdownSourcePlugin, } as AutoankiPlugin;