docxml
Version:
TypeScript (component) library for building and parsing a DOCX file
76 lines (75 loc) • 3.32 kB
JavaScript
import { Component, isComponentDefinition, } from '../classes/Component.js';
import { Text } from '../components/Text.js';
/**
* The JSX pragma with which you can write `<Paragraph>` instead of `new Paragraph({})`.
*
* Also exposed as the `jsx` prop on the (static) class as well as instance of this library's top-
* level API -- see also {@link Api}.
*/
export async function jsx(component, props, ...children) {
const flattenedChildren = await children
// Flatten the children, which may themselves have been wrapped in an array because they
// contained invalid children.
// Moreover, any component might at this point still be only the promise thereof. Resolve all.
.reduce(async function flatten(flatPromise, childPromise) {
const child = await childPromise;
const flat = await flatPromise;
return Array.isArray(child)
? [...flat, ...(await child.reduce(flatten, Promise.resolve([])))]
: [...flat, child];
}, Promise.resolve([]));
return (flattenedChildren
// Add the node, if it is valid, or add the node split into pieces with the invalid children
// vertically inserted between
.reduce((nodes, child) => {
if (typeof child === 'string' && isComponentDefinition(component) && !component.mixed) {
child = new Text({}, child);
}
const isValid = !isComponentDefinition(component) ||
(component.mixed && typeof child === 'string') ||
component.children.includes(child.constructor.name);
if (!isValid) {
if (child.constructor === Text && component === Text) {
Object.assign(child.props, props);
}
nodes.push(child);
}
else {
const lastQueuedItem = nodes[nodes.length - 1];
if (typeof lastQueuedItem === 'string' || lastQueuedItem instanceof Component) {
// Queue this item as a simple object, so that its children can be changed
// in the next iteration.
nodes.push({
component,
props,
children: [child],
});
}
else {
lastQueuedItem.children.push(child);
}
}
return nodes;
}, [{ component, props, children: [] }])
// Instantiate the "queued" items (props/children that haven't been instantiated yet so that
// their children could be shuffled around).
.map((node) => {
if (typeof node === 'string') {
return node;
}
if (node instanceof Component) {
return node;
}
if (isComponentDefinition(component)) {
return new component(node.props || {}, ...(node.children || []));
}
else {
const x = component({ ...props, children: node.children || [] });
return x;
}
})
// Flatten again, no telling what came out of a ComponentFunction
.reduce((flat, thing) => (Array.isArray(thing) ? [...flat, ...thing] : [...flat, thing]), [])
// Remove empty Text components, they don't do anything
.filter((node) => !(node.constructor === Text && !node.children.length)));
}