UNPKG

@podlite/to-jsx

Version:

Render Podlite markup as React JSX components

887 lines (886 loc) 44 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (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 () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.makeTestPodlite = exports.TestPodlite = exports.HookedImage = exports.Podlite = exports.BlockBoundary = 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 schema_3 = require("@podlite/schema"); const entities_1 = require("entities"); const HighlightedCode_1 = __importDefault(require("./HighlightedCode")); // Client-side safety net: a crash inside one block leaves the rest of the // page alive instead of unmounting the whole preview. class BlockBoundary extends React.Component { state = { failed: false }; static getDerivedStateFromError() { return { failed: true }; } componentDidCatch(error) { console.warn(`[to-jsx] block '${this.props.blockName || ''}' failed to render: ${error?.message}`); } render() { if (this.state.failed) { return React.createElement("span", { className: "podlite-render-error" }, "[block ", this.props.blockName || '', " failed to render]"); } return this.props.children; } } exports.BlockBoundary = BlockBoundary; 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') { // Skip wrapping for table-internal nodes. `row` and `cell` always // render as <tr> and <th>/<td>; HTML disallows arbitrary elements as // children of <table>/<tbody>/<tr>, and browsers extract any wrapping // <div> out of the table — collapsing the whole layout. Outer <table> // can still be wrapped for line tracking. const skipName = node.name; if (skipName === 'row' || skipName === 'cell') return result; return wrapElement(node, result, ctx); } return result; }; }; const Podlite = ({ children, ...options }) => { const result = podlite(children, options); return result; }; exports.Podlite = Podlite; // `:folded` on a heading folds the whole section — the heading plus every // following node up to the next same-or-higher-level heading. Detect the // attribute on =head nodes in a container's content array and wrap that // range in a synthetic `_folded_section` block so the JSX renderer can emit // <details> around it. const getFoldedAttr = (node) => { const config = node && node.config; if (!Array.isArray(config)) return null; const entry = config.find((c) => c && c.name === 'folded'); return entry ? entry.value : null; }; const isHeadBlock = (node) => node && node.type === 'block' && node.name === 'head' && node.level !== undefined && node.level !== null; const headLevel = (node) => Number(node.level); const groupFoldedSections = (content) => { if (!Array.isArray(content)) return content; const result = []; let i = 0; while (i < content.length) { const node = content[i]; if (isHeadBlock(node)) { const folded = getFoldedAttr(node); if (folded !== null) { const level = headLevel(node); const sectionNodes = [node]; let j = i + 1; while (j < content.length) { const next = content[j]; if (isHeadBlock(next) && headLevel(next) <= level) break; sectionNodes.push(next); j++; } result.push({ type: 'block', name: '_folded_section', content: sectionNodes, foldedState: folded, location: node.location, }); i = j; continue; } } result.push(node); i++; } return result; }; // Walk the AST and apply `groupFoldedSections` to every block's `content` // array, so :folded heads inside named blocks, defn, nested etc. fold the // same way they do at the pod-level. Inside an already-built // `_folded_section`, the first element is the heading the wrapper belongs // to — skip it when grouping the rest, otherwise the same head would be // re-wrapped on every recursion. Nested folds (a folded heading inside a // folded section's body) are still picked up by grouping the remainder. const applyFoldedSectionsRecursively = (node) => { if (!node || typeof node !== 'object') return node; if (Array.isArray(node)) return node.map(applyFoldedSectionsRecursively); if (!Array.isArray(node.content)) return node; if (node.name === '_folded_section') { const [head, ...rest] = node.content; const grouped = groupFoldedSections(rest); return { ...node, content: [applyFoldedSectionsRecursively(head), ...grouped.map(applyFoldedSectionsRecursively)], }; } const grouped = groupFoldedSections(node.content); return { ...node, content: grouped.map(applyFoldedSectionsRecursively) }; }; const HookedImage = ({ src, alt, hook, baseDir, render }) => { const initial = React.useMemo(() => { const r = hook(src, baseDir); return typeof r === 'string' ? r : null; }, [src, baseDir, hook]); const [resolved, setResolved] = React.useState(initial); React.useEffect(() => { if (initial !== null) return; let alive = true; Promise.resolve(hook(src, baseDir)).then(v => alive && setResolved(v), () => alive && setResolved(null)); return () => { alive = false; }; }, [src, baseDir, hook, initial]); if (resolved == null) return null; return render ? render(resolved) : React.createElement("img", { src: resolved, alt: alt }); }; exports.HookedImage = HookedImage; const isGlobPattern = (s) => /[*?[]/.test(s); const mapToReact = (makeComponent, opts = {}) => { 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); }; // React forbids children on void HTML elements (hr, br, img, ...) const mkVoidComponent = src => (writer, processor) => (node, ctx, interator) => { const id = (0, schema_1.getSafeNodeId)(node, ctx); return makeComponent(src, node, undefined, { 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 :folded attribute - wraps content in collapsible <details> element const handleFolded = defaultHandler => { return (writer, processor) => { const defaultHandlerInited = defaultHandler(writer, processor); return (node, ctx, interator) => { const conf = (0, schema_1.makeAttrs)(node, ctx); const folded = conf.exists('folded') ? conf.getFirstValue('folded') : null; const caption = conf.exists('caption') ? conf.getFirstValue('caption') : null; const children = defaultHandlerInited(node, ctx, interator); // if :folded not specified - return children as is if (folded === null) { return children; } // :folded or :folded(1) = collapsed by default (no open attribute) // :!folded or :folded(0) = expanded by default (open attribute present) const isExpanded = folded === false || folded === 0 || folded === '0'; return makeComponent(({ children, key }) => (React.createElement("details", { className: "folded", key: key, open: isExpanded || undefined }, caption && React.createElement("summary", { className: "folded-summary" }, caption), React.createElement("div", { className: "folded-content" }, children))), node, children, {}, ctx); }; }; }; // 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 folded = conf.exists('folded') ? conf.getFirstValue('folded') : null; const caption = conf.exists('caption') ? conf.getFirstValue('caption') : null; const children = defaultHandlerInited(node, ctx, interator); // if no notify attribute - simply return children if (!notify) { return children; } // Determine the title for the notification const title = caption || notify.charAt(0).toUpperCase() + notify.slice(1); // :folded or :folded(1) = collapsed by default // :!folded or :folded(0) = expanded by default const isExpanded = folded === false || folded === 0 || folded === '0'; // If :folded is specified, wrap in <details> if (folded !== null) { return makeComponent(({ children, key }) => (React.createElement("details", { className: `notify ${notify.toLowerCase()} folded`, key: key, open: isExpanded || undefined }, React.createElement("summary", { className: "notify-title" }, title), React.createElement("div", { className: "folded-content" }, children))), node, children, {}, ctx); } // Default rendering without folding return makeComponent(({ children, key }) => (React.createElement("aside", { className: `notify ${notify.toLowerCase()}`, key: key }, React.createElement("p", { className: "notify-title" }, title), children)), node, children, {}, ctx); }; }; }; return { pod: (writer, processor) => (node, ctx, interator) => { const id = (0, schema_1.getSafeNodeId)(node, ctx); return makeComponent('div', node, interator(node.content, { ...ctx }), { id }, ctx); }, _folded_section: (writer, processor) => (node, ctx, interator) => { const [heading, ...rest] = node.content; const isExpanded = node.foldedState === false || node.foldedState === 0 || node.foldedState === '0'; const headingJsx = interator([heading], { ...ctx }); const bodyJsx = interator(rest, { ...ctx }); const key = (0, schema_1.getSafeNodeId)(node, ctx); return (React.createElement("details", { className: "folded-section", key: key, open: isExpanded || undefined }, React.createElement("summary", { className: "folded-section-summary" }, headingJsx), React.createElement("div", { className: "folded-section-content" }, bodyJsx))); }, 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(HighlightedCode_1.default, { node: node, ctx: ctx, keyProp: key, id: id, wrap: "pre-code" }, children))); }), code: (0, schema_1.setFn)((node, ctx) => { const id = (0, schema_1.getSafeNodeId)(node, ctx); return mkComponent(({ children, key }) => (React.createElement(HighlightedCode_1.default, { node: node, ctx: ctx, keyProp: key, id: id, wrap: "block" }, children))); }), image: schema_1.content, ':image': (0, schema_1.setFn)((node, ctx) => { const hook = opts.imageSrc; if (hook) { return mkComponent(({ key }) => (React.createElement(exports.HookedImage, { key: key, src: node.src, alt: node.alt, hook: hook, baseDir: opts.imageBaseDir }))); } return mkComponent(({ children, key }) => React.createElement("img", { key: key, src: node.src, alt: node.alt })); }), ':text': (writer, processor) => (node, ctx, interator) => { return ctx?.maskMode ? (0, schema_3.maskText)(node.value) : node.value; }, ':verbatim': (writer, processor) => (node, ctx, interator) => { return ctx?.maskMode ? (0, schema_3.maskText)(node.value) : 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); const numberPrefix = node.numberPrefix; return mkComponent(({ level, children, key }) => (0, react_1.createElement)(`h${level}`, { key, id }, numberPrefix ? [(0, react_1.createElement)('span', { key: `${key}-num`, className: 'head-number' }, numberPrefix), ' ', children] : children)); })), ':blankline': (0, schema_1.emptyContent)(), ':para': mkComponent('p'), para: handleNested(mkComponent('div')), 'comment:block': (0, schema_1.emptyContent)(), 'boundary:block': mkVoidComponent('hr'), 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)))), // Resolve =include via injected ctx.includeReader. Without a reader the // directive renders as nothing — preserves the previous emptyContent // behaviour for hosts that don't supply file access (e.g. browser // playground without virtual FS). Glob patterns in the source path // (e.g. `file:**/*.podlite`) require an `expandPaths` callback that // resolves the pattern to a concrete list of file paths; without it // the path is read literally and globs go unresolved. include: (writer, processor) => (node, ctx, interator) => { if (!opts.includeReader || !opts.parser) return null; const selector = (0, schema_3.getTextContentFromNode)(node.content) ?.toString() .trim(); if (!selector) return null; const parsed = (0, schema_3.parseSelector)(selector); if (!parsed || parsed.scheme !== 'file' || !parsed.document) return null; // Resolve target paths: glob is expanded via the host callback (when // present); a literal path is used directly. Hosts without // `expandPaths` get one-file behaviour for everything. const stack = ctx.includeStack ?? []; const paths = isGlobPattern(parsed.document) && opts.expandPaths ? opts.expandPaths(parsed.document, opts.includeBaseDir) : [parsed.document]; const docs = []; for (const p of paths) { if (stack.includes(p)) continue; const source = opts.includeReader(p, opts.includeBaseDir); if (source == null) continue; const subAst = opts.parser.toAst(opts.parser.parse(source, { podMode: 1 })); docs.push({ file: p, node: subAst }); } if (docs.length === 0) return null; const blocks = (0, schema_3.runSelector)(selector, docs); if (!blocks || blocks.length === 0) return null; return interator(blocks, { ...ctx, includeStack: [...stack, ...paths] }); }, // 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; let termString; if (typeof term === 'string') { termString = term; } else if (term && 'value' in term) { termString = term.value; } else { termString = ''; } termString = termString.trim(); //get replacement text if (!(ctx.alias && ctx.alias.hasOwnProperty(termString))) { return makeComponent(({ children, key }) => React.createElement("code", { key: key }, "A<", children, ">"), node, interator(node.content, ctx)); } else { const src = termString && ctx.alias[termString].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(Boolean) .map(element => { if (typeof element == 'string') { return 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};`); } if (element.type == 'text' && 'value' in element) { return element.value; } console.warn(`[jsx] E<> unsupported or unknown element type: ${element.type}`); return ''; }) .join(''); }, 'H<>': mkComponent('sup'), 'I<>': mkComponent('i'), 'J<>': mkComponent('sub'), '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); }, 'O<>': mkComponent('del'), 'G<>': (_writer, _processor) => (node, ctx, interator) => { if (ctx.renderMode === 'draft') { return makeComponent(({ key, children }) => (React.createElement("span", { key: key, className: "masked-draft" }, children)), node, interator(node.content, ctx)); } const masked = (0, schema_3.maskText)((0, schema_3.collectText)(node.content)); return makeComponent(({ key }) => (React.createElement("span", { key: key, className: "masked" }, masked)), node, []); }, // table section table: (writer, processor) => (node, ctx, interator) => { const conf = (0, schema_1.makeAttrs)(node, ctx); const caption = conf.exists('caption') ? conf.getFirstValue('caption') : ''; const folded = conf.exists('folded') ? conf.getFirstValue('folded') : null; 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); // :folded or :folded(1) = collapsed by default // :!folded or :folded(0) = expanded by default const isExpanded = folded === false || folded === 0 || folded === '0'; const tableCtx = { ...ctx, ...(node.align && { 'table.align': node.align }) }; const content = node.content || []; const isHeaderRow = (c) => c && c.name === 'row' && Array.isArray(c.config) && c.config.some((a) => a.name === 'header' && a.value !== false); const hasHeader = content.some(isHeaderRow); let renderTable; if (!hasHeader) { const rendered = interator(content, tableCtx); renderTable = extraKey => (React.createElement("table", { key: extraKey, id: id }, caption ? React.createElement("caption", { className: "caption" }, caption) : null, React.createElement("tbody", null, rendered))); } else { const rowNodes = content.filter((c) => c && c.name === 'row'); const nonRowContent = content.filter((c) => !c || c.name !== 'row'); let headerEnd = 0; while (headerEnd < rowNodes.length && isHeaderRow(rowNodes[headerEnd])) headerEnd++; const headerRows = rowNodes.slice(0, headerEnd); const bodyRows = rowNodes.slice(headerEnd); const nonRowRendered = interator(nonRowContent, tableCtx); const headerRendered = interator(headerRows, tableCtx); const bodyRendered = bodyRows.length > 0 ? interator(bodyRows, tableCtx) : null; renderTable = extraKey => (React.createElement("table", { key: extraKey, id: id }, caption ? React.createElement("caption", { className: "caption" }, caption) : null, nonRowRendered, React.createElement("thead", null, headerRendered), bodyRendered ? React.createElement("tbody", null, bodyRendered) : null)); } // If :folded is specified, wrap table in <details> if (folded !== null) { return makeComponent(({ key }) => (React.createElement("details", { className: "folded table-folded", key: key, open: isExpanded || undefined }, caption && React.createElement("summary", { className: "folded-summary" }, caption), React.createElement("div", { className: "folded-content" }, renderTable(`${key}-table`)))), node, []); } return makeComponent(({ key }) => renderTable(key), node, []); }, ':separator': (0, schema_1.emptyContent)(), row: (0, schema_1.setFn)((node, ctx) => { const conf = (0, schema_1.makeAttrs)(node, ctx); const isHeader = conf.exists('header') && conf.getFirstValue('header') !== false; ctx.__row_header = isHeader; if (ctx['table.align']) ctx['cellinRow'] = 0; return mkComponent(({ children, key }) => React.createElement("tr", { key: key }, children)); }), cell: (0, schema_1.setFn)((node, ctx) => { const colAlign = (alignMap => { if (!Array.isArray(alignMap)) return null; const num = ctx['cellinRow']++; return alignMap[num] || null; })(ctx['table.align']); const isHeader = ctx.__row_header; const conf = (0, schema_1.makeAttrs)(node, ctx); const colSpanRaw = conf.exists('colspan') ? Number(conf.getFirstValue('colspan')) : 0; const rowSpanRaw = conf.exists('rowspan') ? Number(conf.getFirstValue('rowspan')) : 0; const colSpan = colSpanRaw > 1 ? colSpanRaw : undefined; const rowSpan = rowSpanRaw > 1 ? rowSpanRaw : undefined; const style = colAlign && ['left', 'right', 'center', 'justify'].includes(colAlign) ? { textAlign: colAlign } : undefined; return mkComponent(({ children, key }) => isHeader ? (React.createElement("th", { key: key, colSpan: colSpan, rowSpan: rowSpan, style: style }, children)) : (React.createElement("td", { key: key, colSpan: colSpan, rowSpan: rowSpan, style: style }, children))); }), ':list': (0, schema_1.setFn)((node, ctx) => node.list === 'ordered' ? mkComponent('ol') : node.list === 'variable' ? mkComponent('dl') : node.list === 'task' ? mkComponent(({ children, key }) => (React.createElement("ul", { className: "task-list", key: key }, children))) : 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); const isTask = node.checked !== undefined; if (isTask) { const checkbox = React.createElement("input", { key: "checkbox", type: "checkbox", disabled: true, checked: node.checked || undefined }); const content = interator(node.content, { ...ctx }); return makeComponent('li', node, [checkbox, ...[].concat(content)], { id, className: 'task-list-item' }); } return makeComponent('li', node, interator(node.content, { ...ctx }), { id }); }, // table of content ':toc': (0, schema_1.setFn)((node, ctx) => { const tocTitle = node.title; if (node.foldedLevels) { ctx._tocFoldedLevels = node.foldedLevels; } const folded = node.folded; if (folded !== undefined) { const isExpanded = folded === false; return mkComponent(({ children, key }) => (React.createElement("details", { className: "toc toc-fold-all", key: key, open: isExpanded || undefined }, React.createElement("summary", { className: "toctitle" }, tocTitle || 'Contents'), children))); } return mkComponent(({ children, key }) => (React.createElement("div", { className: "toc", key: key }, tocTitle ? React.createElement("div", { className: "toctitle" }, tocTitle) : '', children))); }), ':toc-list': (writer, processor) => (node, ctx, interator) => { const level = node.level; const foldedLevels = ctx._tocFoldedLevels; const content = Array.isArray(node.content) ? node.content : []; const key = (0, schema_1.getSafeNodeId)(node, ctx); const shouldFold = foldedLevels ? foldedLevels[level] === true : false; if (!shouldFold) { return (React.createElement("ul", { className: `toc-list listlevel${level}`, key: key }, interator(content, { ...ctx }))); } // Per-item conditional fold: a toc-item becomes a disclosure only when it // is immediately followed by a nested toc-list (i.e. has sub-headings). // Leaf items render as plain <li> without a fold marker. Summary contains // the rendered link of the item itself — no duplication, no bare triangle. const rendered = []; let i = 0; while (i < content.length) { const it = content[i]; const next = content[i + 1]; const isItem = it && it.type === 'toc-item'; const hasChildren = next && next.type === 'toc-list'; if (isItem && hasChildren) { // Extract inline content of the item's heading para (skip the <p> // wrapper) so the link renders on the same line as the disclosure // triangle. `it.node` is the para built by podlite-toc; its content // is the L<> fcode + text. const innerContent = it.node && Array.isArray(it.node.content) ? it.node.content : it.content; const itemLinkJsx = interator(innerContent, { ...ctx }); const childrenJsx = interator([next], { ...ctx }); rendered.push(React.createElement("li", { className: "toc-item toc-item-foldable", key: `${key}-fold-${i}` }, React.createElement("details", { className: "toc-fold" }, React.createElement("summary", { className: `toc-list-summary listlevel${level}` }, itemLinkJsx), childrenJsx))); i += 2; continue; } rendered.push(interator([it], { ...ctx })); i++; } return (React.createElement("ul", { className: `toc-list listlevel${level}`, key: key }, rendered)); }, ':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, mode = 'pod', includeReader, includeBaseDir, expandPaths, imageSrc, imageBaseDir, renderMode = 'production', }, ...args) { const podliteParser = (0, podlite_1.podlite)({ importPlugins: true }); const astRaw = (tree => { if (tree) return tree.interator; const parseOptions = mode === 'md' ? { mode: 'md' } : { podMode: 1 }; const treeAfterParsed = podliteParser.parse(children || file, parseOptions); return podliteParser.toAst(treeAfterParsed); })(tree); const ast = applyFoldedSectionsRecursively(astRaw); // const ast = parse( children || content ) let i_key_i = 10000; const makeComponent = helperMakeReact({ wrapElement }); const jsxPlugins = (0, schema_1.toAnyRules)('toJSX', podliteParser.getPlugins()); // initialize each plugin const jsxPluginInited = Object.fromEntries(Object.entries(jsxPlugins).map(([key, value]) => [key, value(makeComponent)])); const rules = { ...mapToReact(makeComponent, { includeReader, includeBaseDir, expandPaths, imageSrc, imageBaseDir, parser: podliteParser, }), ...plugins(makeComponent), ...jsxPluginInited, }; const writer = new schema_1.Writer(s => { }); const res = (0, schema_1.toAny)({ processor: schema_1.parse, context: { renderMode, imageSrc, imageBaseDir } }) .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) .use('*', (writer, processor) => (node, ctx, interator, defaultFn) => { // defaultFn chains to a single next rule, so masking and the render // safety net share this one wildcard hook const dispatch = () => { if (!node || node.type !== 'block') return defaultFn(); if (ctx?.maskMode) return defaultFn(); const conf = (0, schema_1.makeAttrs)(node, ctx || {}); if (!conf.exists('masked') || !conf.getFirstValue('masked')) return defaultFn(); if (ctx?.renderMode === 'draft') return defaultFn(); return defaultFn(node, { ...(ctx || {}), maskMode: true }, interator); }; const blockName = node && typeof node === 'object' ? node.name || node.type : undefined; let result; try { result = dispatch(); } catch (error) { console.warn(`[to-jsx] block '${blockName || ''}' failed to render: ${error?.message}`); return (0, react_1.createElement)('span', { key: ++i_key_i, className: 'podlite-render-error' }, `[block ${blockName || ''} failed to render]`); } if (node && typeof node === 'object' && node.type === 'block' && React.isValidElement(result)) { return (0, react_1.createElement)(BlockBoundary, { key: ++i_key_i, blockName }, result); } return result; }) .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