UNPKG

@podlite/to-jsx

Version:

JSX converter for Podlite markup language

501 lines (500 loc) 22.9 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.makeTestPodlite = exports.TestPodlite = exports.Podlite = void 0; const React = __importStar(require("react")); const react_1 = require("react"); const podlite_1 = require("podlite"); const schema_1 = require("@podlite/schema"); const schema_2 = require("@podlite/schema"); const entities_1 = require("entities"); const helperMakeReact = ({ wrapElement }) => { let i_key_i = 0; let mapByType = {}; const getIdForNode = ({ type = 'notype', name = 'noname' }) => { const type_idx = `${type}_${name}`; if (!mapByType[type_idx]) { mapByType[type_idx] = 0; } ++mapByType[type_idx]; return `${type_idx}_${mapByType[type_idx]}`; }; return function (src, node, children, extraProps = {}, ctx = {}) { // for string return it if (typeof node == 'string') { return node; } const { ...attr } = node; const key = 'type' in node && node.type ? getIdForNode(node) : ++i_key_i; const result = typeof src === 'function' ? src({ ...attr, key, children }, children) : (0, react_1.createElement)(src, { ...extraProps, key }, children); // // create react element and pass key // if (!isValidElementType(src)) { // throw new Error(`Bad React element for ${ruleName} rule `) // } if (typeof wrapElement === 'function') { return wrapElement(node, result, ctx); } return result; }; }; const Podlite = ({ children, ...options }) => { const result = podlite(children, options); return result; }; exports.Podlite = Podlite; const mapToReact = (makeComponent) => { const mkComponent = src => (writer, processor) => (node, ctx, interator) => { // prepare extraProps for createElement // add id attribute if exists const id = (0, schema_1.getSafeNodeId)(node, ctx); // check if node.content defined return makeComponent(src, node, 'content' in node ? interator(node.content, { ...ctx }) : [], { id }, ctx); }; // Handle nested block and :nested block attribute const handleNested = (defaultHandler, implicitLevel) => { return (writer, processor) => { const defaultHandlerInited = defaultHandler(writer, processor); return (node, ctx, interator) => { const nesting = (0, schema_1.makeAttrs)(node, ctx).getFirstValue('nested') || implicitLevel; const children = defaultHandlerInited(node, ctx, interator); // if no nesting needs - simply return children if (!nesting) { return children; } const arr = [...Array(nesting).keys()]; return arr.reduce(acc => makeComponent('blockquote', node, acc), children); }; }; }; // Handle nested block and :nested block attribute const handleNotificationBlock = defaultHandler => { return (writer, processor) => { const defaultHandlerInited = defaultHandler(writer, processor); return (node, ctx, interator) => { const conf = (0, schema_1.makeAttrs)(node, ctx); const notify = conf.getFirstValue('notify'); const caption = conf.exists('caption') ? conf.getFirstValue('caption') : null; const children = defaultHandlerInited(node, ctx, interator); // if no nesting needs - simply return children if (!notify) { return children; } return makeComponent(({ children, key }) => (React.createElement("aside", { className: `notify ${notify.toLowerCase()}`, key: key }, React.createElement("p", { className: "notify-title" }, caption || notify.charAt(0).toUpperCase() + notify.slice(1)), children)), node, children, {}, ctx); }; }; }; return { pod: mkComponent('div'), root: schema_1.content, data: (0, schema_1.emptyContent)(), ':ambient': (0, schema_1.emptyContent)(), ':code': (0, schema_1.setFn)((node, ctx) => { const id = (0, schema_1.getSafeNodeId)(node, ctx); return mkComponent(({ children, key }) => (React.createElement("pre", { key: key, id: id }, React.createElement("code", null, children)))); }), code: (0, schema_1.setFn)((node, ctx) => { const id = (0, schema_1.getSafeNodeId)(node, ctx); const conf = (0, schema_1.makeAttrs)(node, ctx); const caption = conf.exists('caption') ? conf.getFirstValue('caption') : null; return mkComponent(({ children, key }) => (React.createElement("div", { className: "code-block", key: `${key}-code-div` }, React.createElement("pre", { key: key, id: id }, React.createElement("code", null, children)), caption ? (React.createElement("div", { key: `${key}-caption`, className: "caption" }, caption)) : null))); }), image: schema_1.content, ':image': (0, schema_1.setFn)((node, ctx) => { return mkComponent(({ children, key }) => React.createElement("img", { key: key, src: node.src, alt: node.alt })); }), ':text': (writer, processor) => (node, ctx, interator) => { return node.value; }, ':verbatim': (writer, processor) => (node, ctx, interator) => { return node.value; }, 'head:block': (0, schema_1.subUse)({ // inside head don't wrap into <p> ':para': schema_1.content, }, (0, schema_1.setFn)((node, ctx) => { const { level } = node; // TODO: refactor linking for blocks const id = (0, schema_1.getSafeNodeId)(node, ctx); return mkComponent(({ level, children, key }) => (0, react_1.createElement)(`h${level}`, { key, id }, children)); })), ':blankline': (0, schema_1.emptyContent)(), ':para': mkComponent('p'), para: handleNested(mkComponent('div')), 'comment:block': (0, schema_1.emptyContent)(), defn: (0, schema_1.subUse)([ // to avoid overlap para blocks handlers // define general :para at first { ':para': mkComponent('dd') }, { 'term:para': mkComponent('dt') }, ], schema_1.content), nested: handleNotificationBlock(handleNested(schema_1.content, 1)), output: mkComponent(({ children, key }) => (React.createElement("pre", { key: key }, React.createElement("samp", null, children)))), input: mkComponent(({ children, key }) => (React.createElement("pre", { key: key }, React.createElement("kbd", null, children)))), // TODO: add support for selectors, something like "data provider" include: (0, schema_1.emptyContent)(), // Directives ':config': (0, schema_1.setFn)((node, ctx) => { // setup context if (!ctx.hasOwnProperty('config')) ctx.config = {}; //collect configs in context ctx.config[node.name] = node.config; return (0, schema_1.emptyContent)(); }), ':alias': (0, schema_1.setFn)((node, ctx) => { // set alias if (!ctx.hasOwnProperty('alias')) ctx.alias = {}; //collect configs in context ctx.alias[node.name] = node.replacement; return (0, schema_1.emptyContent)(); }), // Markup codes 'A<>': (writer, processor) => (node, ctx, interator) => { let term = node.content; if (typeof term !== 'string' && 'value' in term) { term = term.value; } //get replacement text if (!(ctx.alias && ctx.alias.hasOwnProperty(term))) { return makeComponent(({ children, key }) => React.createElement("code", { key: key }, "A<", children, ">"), node, interator(node.content, ctx)); } else { const src = ctx.alias[term].join('\n'); const tree_1 = processor(src); // now clean locations const tree = (0, schema_2.pluginCleanLocation)()(tree_1); if (tree[0].type === 'para') { return interator(tree[0].content, ctx); } else { return interator(tree, ctx); } } }, 'B<>': mkComponent('strong'), 'C<>': mkComponent('code'), 'E<>': (writer, processor) => (node, ctx, interator) => { if ('content' in node && Array.isArray(node.content)) return node.content .filter(e => e && e.type) .map(element => { if (element.type == 'number' && 'value' in element) { return String.fromCharCode(element.value); } if (element.type == 'html_named' && 'value' in element) { return (0, entities_1.decodeHTMLStrict)(`&${element.value};`); } console.warn(`[jsx] E<> unsupported or unknown element type: ${element.type}`); return ''; }) .join(''); }, 'I<>': mkComponent('i'), 'K<>': mkComponent('kbd'), /** * CSS rules for footnotes .footnote a { text-decoration: none; } .footnotes { border-top-style: solid; border-top-width: 1px; border-top-color: #eee; } */ 'N<>': (writer, processor) => { writer.addListener('end', () => { if (!writer.hasOwnProperty('FOOTNOTES')) { return; } const footnotes = writer.FOOTNOTES; if (footnotes.length < 1) { return; } // if empty footnotes if (!writer.hasOwnProperty('postInterator')) { writer.postInterator = []; } const FootNotes = makeComponent(({ children, key }) => (React.createElement("div", { key: `${key}_FOOTNOTES`, className: "footnotes" }, footnotes.map((footnote, id) => { return (React.createElement("p", { key: id }, React.createElement("sup", { id: footnote.fnId, className: "footnote" }, React.createElement("a", { href: `#${footnote.fnRefId}` }, "[", footnote.gid, "]")), footnote.make())); }))), {}, []); writer.postInterator.push(FootNotes); }); return (node, ctx, interator) => { // skip empty notes if (node.content.length < 1) { return; } if (!writer.hasOwnProperty('gid')) { writer.gid = 1; } // get foot note id const gid = writer.gid++; const fnRefId = `fnref:${gid}`; const fnId = `fn:${gid}`; if (!writer.hasOwnProperty('FOOTNOTES')) { writer.FOOTNOTES = []; } writer.FOOTNOTES.push({ gid, fnRefId, fnId, node, make: () => { return interator(node.content, ctx); }, }); return makeComponent(({ children, key }) => (React.createElement("sup", { key: key, id: fnRefId, className: "footnote" }, React.createElement("a", { href: `#${fnId}` }, "[", gid, "]"))), node, []); }; }, 'R<>': mkComponent('var'), 'T<>': mkComponent('samp'), 'D<>': (writer, processor) => (node, ctx, interator) => { let { synonyms } = node; let definition = [node.content[0]]; if (synonyms) { definition = synonyms; } if (!writer.hasOwnProperty('DEFINITIONS')) { writer.DEFINITIONS = []; } writer.DEFINITIONS.push({ definition }); return makeComponent('dfn', node, interator(node.content, ctx)); }, 'L<>': (0, schema_1.setFn)((node, ctx) => { let { meta } = node; if (meta === null) { meta = node.content; } //TODO: extract text from content array if (Array.isArray(meta)) { meta = meta[0]; } if (meta && typeof meta !== 'string' && 'value' in meta) { meta = meta.value; } return mkComponent(({ children, key }) => (React.createElement("a", { href: meta, key: key }, children))); }), 'S<>': (writer, processor) => (node, ctx, interator) => { let content = node.content || ''; if (typeof content !== 'string' && 'value' in content) { content = content.value; } const Content = content.split('').map((symbol, index) => { if (symbol === ' ') return '\u00a0'; if (symbol === '\n') return React.createElement("br", { key: index }); return symbol; }); return makeComponent(({ children, key }) => children, {}, Content); }, 'V<>': schema_1.content, 'Z<>': (0, schema_1.emptyContent)(), 'U<>': mkComponent('u'), 'X<>': (writer, processor) => (node, ctx, interator) => { let { entry } = node; if (entry === null && node.content.length > 0) { //@ts-ignore entry = [node.content[0]]; } // else { return } if (!writer.hasOwnProperty('INDEXTERMS')) { writer.INDEXTERMS = []; } writer.INDEXTERMS.push({ entry, }); return interator(node.content, ctx); }, 'Delete<>': mkComponent('del'), // table section table: (writer, processor) => (node, ctx, interator) => { const conf = (0, schema_1.makeAttrs)(node, ctx); let attr = { caption: '' }; if (conf.exists('caption')) { const caption = conf.getFirstValue('caption'); attr.caption = caption; } if (typeof node === 'string') { return node; } if (!('content' in node)) { console.warn('[jsx] no content in node'); return ''; } const id = (0, schema_1.getSafeNodeId)(node, ctx); return makeComponent(({ key, children }) => { return (React.createElement("table", { key: key, id: id }, React.createElement("caption", { className: "caption" }, attr.caption), React.createElement("tbody", null, children))); }, node, interator(node.content, { ...ctx, ...(node.align && { 'table.align': node.align }) })); }, ':separator': (0, schema_1.emptyContent)(), table_row: (0, schema_1.setFn)((node, ctx) => { if (ctx['table.align']) { ctx['cellinRow'] = 0; } return mkComponent(({ children, key }) => React.createElement("tr", { key: key }, children)); }), table_cell: (0, schema_1.setFn)((node, ctx) => { const align = (alignMap => { if (!alignMap) return null; const num = ctx['cellinRow']++; return alignMap[num % alignMap.length]; })(ctx['table.align']); return mkComponent(({ children, key }) => (React.createElement("td", { align: align, key: key }, children))); }), table_head: (0, schema_1.subUse)({ table_cell: (0, schema_1.setFn)((node, ctx) => { const align = (alignMap => { if (!alignMap) return null; const num = ctx['cellinRow']++; return alignMap[num % alignMap.length]; })(ctx['table.align']); return mkComponent(({ children, key }) => React.createElement("th", { key: key }, children)); }), }, (0, schema_1.setFn)((node, ctx) => { if (ctx['table.align']) { ctx['cellinRow'] = 0; } return mkComponent(({ children, key }) => React.createElement("tr", { key: key }, children)); })), ':list': (0, schema_1.setFn)((node, ctx) => node.list === 'ordered' ? mkComponent('ol') : node.list === 'variable' ? mkComponent('dl') : mkComponent('ul')), 'item:block': (writer, processor) => (node, ctx, interator) => { if (typeof node === 'string') { return node; } if (!('content' in node)) { console.warn('[jsx] no content in node'); return ''; } // make text from first para if (!(node.content instanceof Array)) { console.error(node); } const id = (0, schema_1.getSafeNodeId)(node, ctx); return makeComponent('li', node, interator(node.content, { ...ctx }), { id }); }, // table of content ':toc': (0, schema_1.setFn)((node, ctx) => { const tocTitle = node.title; return mkComponent(({ children, key }) => (React.createElement("div", { className: "toc", key: key }, tocTitle ? React.createElement("div", { className: "toctitle" }, tocTitle) : '', children))); }), ':toc-list': (0, schema_1.setFn)((node, ctx) => { const { level } = node; return mkComponent(({ children, key }) => (React.createElement("ul", { className: `toc-list listlevel${level}`, key: key }, children))); }), ':toc-item': (0, schema_1.subUse)({ // inside head don't wrap into <p> ':para': schema_1.content, }, (0, schema_1.setFn)((node, ctx) => { return mkComponent(({ children, key }) => (React.createElement("li", { className: "toc-item", key: key }, children))); })), }; }; function podlite(children, { file, plugins = () => { }, wrapElement, tree, }, ...args) { const ast = (tree => { if (tree) return tree.interator; let podlite = (0, podlite_1.podlite)({ importPlugins: true }); let treeAfterParsed = podlite.parse(children || file); return podlite.toAst(treeAfterParsed); })(tree); // const ast = parse( children || content ) let i_key_i = 10000; const makeComponent = helperMakeReact({ wrapElement }); const jsxPlugins = (0, schema_1.toAnyRules)('toJSX', (0, podlite_1.podlite)({ importPlugins: true }).getPlugins()); // initialize each plugin const jsxPluginInited = Object.fromEntries(Object.entries(jsxPlugins).map(([key, value]) => [key, value(makeComponent)])); const rules = { ...mapToReact(makeComponent), ...plugins(makeComponent), ...jsxPluginInited, }; const writer = new schema_1.Writer(s => { }); const res = (0, schema_1.toAny)({ processor: schema_1.parse }) .use({ '*:*': () => (node, ctx, interator) => { // skip named blocks if ((0, schema_1.isNamedBlock)(node.name)) { return null; } if ((0, schema_1.isSemanticBlock)(node)) { return makeComponent(({ key, children }) => { return (React.createElement("div", { key: key }, React.createElement("h1", { className: node.name, key: key }, node.name), interator(node.content, { ...ctx }))); }, node, interator(node.content, { ...ctx })); } console.warn('[to-jsx] Not supported: ' + JSON.stringify(node, null, 2)); return (0, react_1.createElement)('code', { key: ++i_key_i }, `not supported node:${JSON.stringify(node, null, 2)}`); }, }) .use(rules) .run(ast, writer); // union main react elements and post processed via onEnd event return new Array().concat(res.interator, writer.postInterator); } // this is a helper function for using in unit test const TestPodlite = ({ children, ...options }) => { let podlite = (0, podlite_1.podlite)({ importPlugins: true }); // its replace all ids with "id" const tree = (0, schema_1.frozenIds)()(podlite.toAst(podlite.parse(children))); return React.createElement(exports.Podlite, { ...{ children, ...options, tree: { interator: tree } } }); }; exports.TestPodlite = TestPodlite; const makeTestPodlite = (podlite = (0, podlite_1.podlite)({ importPlugins: true })) => ({ children, ...options }) => { let treeAfterParsed = podlite.parse(children); // its replace all ids with "id" const tree = (0, schema_1.frozenIds)()(podlite.toAst(treeAfterParsed)); return React.createElement(exports.Podlite, { ...{ children, ...options, tree: { interator: tree } } }); }; exports.makeTestPodlite = makeTestPodlite; exports.default = exports.Podlite; //# sourceMappingURL=index.js.map