@podlite/to-jsx
Version:
Render Podlite markup as React JSX components
887 lines (886 loc) • 44 kB
JavaScript
;
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