From 221193a587b18ad15c9537d30eb28118691ec00d Mon Sep 17 00:00:00 2001 From: linonetwo Date: Sun, 23 Mar 2025 20:57:19 +0800 Subject: [PATCH] WIP: ast transformers --- .../editorjs/ast/wikiAstFromEditorJSAst.js | 68 +++++++++++++++ .../editorjs/ast/wikiAstToEditorJSAst.js | 83 +++++++++++++++++++ 2 files changed, 151 insertions(+) create mode 100644 plugins/tiddlywiki/editorjs/ast/wikiAstFromEditorJSAst.js create mode 100644 plugins/tiddlywiki/editorjs/ast/wikiAstToEditorJSAst.js diff --git a/plugins/tiddlywiki/editorjs/ast/wikiAstFromEditorJSAst.js b/plugins/tiddlywiki/editorjs/ast/wikiAstFromEditorJSAst.js new file mode 100644 index 000000000..eeb26bd7f --- /dev/null +++ b/plugins/tiddlywiki/editorjs/ast/wikiAstFromEditorJSAst.js @@ -0,0 +1,68 @@ +/*\ +title: $:/plugins/tiddlywiki/editorjs/ast/wikiAstFromEditorJSAst.js +type: application/javascript +module-type: library + +Get the EditorJS AST from a Wiki AST + +\*/ + + +/** + * Key is `node.type`, value is node converter function. + */ +const builders = { + // auto parse basic element nodes + // eslint-disable-next-line unicorn/prefer-object-from-entries + ...(htmlTags).reduce( + (previousValue, currentValue) => { + previousValue[currentValue] = element; + return previousValue; + }, + // eslint-disable-next-line @typescript-eslint/prefer-reduce-type-parameter, @typescript-eslint/consistent-type-assertions + {}, + ), + [ELEMENT_CODE_BLOCK]: codeblock, + [ELEMENT_LIC]: lic, + text, + widget, + macro: widget, + set, +}; + +function wikiAstFromEditorJSAst(input) { + return convertNodes(builders, Array.isArray(input) ? input : [input]); +} + +exports.wikiAstFromEditorJSAst = wikiAstFromEditorJSAst; + +function convertNodes(builders, nodes) { + if (nodes === undefined || nodes.length === 0) { + return []; + } + + return nodes.reduce((accumulator, node) => { + return [...accumulator, ...convertWikiAstNode(builders, node)]; + }, []); +} + +function convertWikiAstNode(builders, node) { + // only text and root node don't have a `type` field, deal with it first + if (isText(node)) { + return [builders.text(builders, node)]; + } + if (isElement(node)) { + const builder = builders[node.type]; + if (typeof builder === 'function') { + const builtSlateNodeOrNodes = builder(builders, node); + return Array.isArray(builtSlateNodeOrNodes) + ? builtSlateNodeOrNodes.map((child) => ({ ...getSlatePlateASTAdditionalProperties(node), ...child })) + : ([{ ...getSlatePlateASTAdditionalProperties(node), ...builtSlateNodeOrNodes }]); + } + } + // it might be a root or pure parent node, reduce it + if ('children' in node) { + return convertNodes(builders, node.children); + } + return []; +} diff --git a/plugins/tiddlywiki/editorjs/ast/wikiAstToEditorJSAst.js b/plugins/tiddlywiki/editorjs/ast/wikiAstToEditorJSAst.js new file mode 100644 index 000000000..37e953580 --- /dev/null +++ b/plugins/tiddlywiki/editorjs/ast/wikiAstToEditorJSAst.js @@ -0,0 +1,83 @@ +/*\ +title: $:/plugins/tiddlywiki/editorjs/ast/wikiAstToEditorJSAst.js +type: application/javascript +module-type: library + +Get the EditorJS AST from a Wiki AST + +\*/ +function wikiAstToEditorJSAst(node, options) { + return convertNodes({ ...initialContext, ...options }, Array.isArray(node) ? node : [node]); +} + +exports.wikiAstToEditorJSAst = wikiAstToEditorJSAst; + +const initialContext = { + builders, + marks: {}, +}; + +function convertNodes(context, nodes) { + if (nodes === undefined || nodes.length === 0) { + return [{ text: '' }]; + } + + return nodes.reduce((accumulator, node) => { + return [...accumulator, ...editorJSNode(context, node)]; + }, []); +} + +function editorJSNode(context, node) { + const id = context.idCreator?.(); + const withId = (nodeToAddId) => (id === undefined ? nodeToAddId : { ...nodeToAddId, id }); + if ('rule' in node && node.rule !== undefined && node.rule in context.builders) { + const builder = context.builders[node.rule]; + if (typeof builder === 'function') { + // basic elements + const builtEditorJSNodeOrNodes = builder(context, node); + return Array.isArray(builtEditorJSNodeOrNodes) + ? builtEditorJSNodeOrNodes.map((child) => withId(child)) + : ([withId(builtEditorJSNodeOrNodes)]); + } + } else if ('text' in node) { + // text node + return [withId({ text: node.text })]; + } else { + console.warn(`WikiAst get Unknown node type: ${JSON.stringify(node)}`); + return []; + } + return []; +} + +const builders = { + element, + text, +}; + +/** Slate node is compact, we need to filter out some keys from wikiast */ +const textLevelKeysToOmit = ['type', 'start', 'end']; + +function text(context, text) { + return { + text: '', // provides default text + ...omit(text, textLevelKeysToOmit), + ...context.marks, + }; +} + +const elementBuilders = { ul, ol: ul, li, ...marks }; + +function element(context, node) { + const { tag, children } = node; + if (typeof elementBuilders[tag] === 'function') { + return elementBuilders[tag](context, node); + } + const result = { + type: tag, + children: convertNodes(context, children), + }; + if (node.rule) { + result.rule = node.rule; + } + return result; +}