@nacelle/rich-text-utils
Version:
A set of Typescript types and helpers to work with Rich Text fields.
117 lines • 5.08 kB
JavaScript
import { hasChildren, isDocument, isRichText, isRoot, isParagraph, isList, isListItem, isBlockquote, isCode, isLink, isHeading, isThematicBreak, isSpan } from './guards';
import { flatten } from 'array-flatten';
export class RenderError extends Error {
constructor(message, node) {
super(message);
this.node = node;
Object.setPrototypeOf(this, RenderError.prototype);
}
}
export const renderRule = (guard, transform) => ({
appliable: guard,
apply: (ctx) => transform(ctx)
});
export function markToTagName(mark) {
switch (mark) {
case 'emphasis':
return 'em';
case 'underline':
return 'u';
case 'strikethrough':
return 'del';
case 'highlight':
return 'mark';
default:
return mark;
}
}
export const defaultMetaTransformer = ({ meta }) => {
const attributes = {};
meta.forEach((entry) => {
if (['target', 'title', 'rel'].includes(entry.id)) {
attributes[entry.id] = entry.value;
}
});
return attributes;
};
export function transformNode(adapter, node, key, ancestors, renderRules) {
const children = hasChildren(node)
? flatten(node.children
.map((node, index) => transformNode(adapter, node, `t-${index}`, [...ancestors, node], renderRules))
.filter((x) => !!x))
: undefined;
const matchingTransform = renderRules.find((transform) => transform.appliable(node));
if (matchingTransform) {
return matchingTransform.apply({ adapter, node, children, key, ancestors });
}
else {
throw new RenderError(`Don't know how to render a node with type "${node.type}". Please specify a custom renderRule for it!`, node);
}
}
export function render(adapter, richTextOrNode, renderRules) {
if (!richTextOrNode) {
return null;
}
const result = transformNode(adapter, isRichText(richTextOrNode)
? richTextOrNode.value.document
: isDocument(richTextOrNode)
? richTextOrNode.document
: richTextOrNode, 't-0', [], renderRules);
return result;
}
export function genericHtmlRender(adapter, richTextOrNode, customRules, metaTransformer = defaultMetaTransformer) {
return render(adapter, richTextOrNode, [
...customRules,
renderRule(isRoot, ({ adapter: { renderFragment }, key, children }) => {
return renderFragment(children, key);
}),
renderRule(isParagraph, ({ adapter: { renderNode }, key, children }) => {
return renderNode('p', { key }, children);
}),
renderRule(isList, ({ adapter: { renderNode }, node, key, children }) => {
return renderNode(node.style === 'bulleted' ? 'ul' : 'ol', { key }, children);
}),
renderRule(isListItem, ({ adapter: { renderNode }, key, children }) => {
return renderNode('li', { key }, children);
}),
renderRule(isBlockquote, ({ adapter: { renderNode }, key, node, children }) => {
const childrenWithAttribution = node.attribution
? [
...(children || []),
renderNode(`footer`, { key: 'footer' }, node.attribution),
]
: children;
return renderNode('blockquote', { key }, childrenWithAttribution);
}),
renderRule(isCode, ({ adapter: { renderNode, renderText }, key, node }) => {
return renderNode('pre', { key, 'data-language': node.language }, renderNode('code', null, renderText(node.code)));
}),
renderRule(isLink, ({ adapter: { renderNode }, key, children, node }) => {
const meta = node.meta ? metaTransformer({ node, meta: node.meta }) : {};
return renderNode('a', Object.assign(Object.assign({}, (meta || {})), { key, href: node.url }), children);
}),
renderRule(isThematicBreak, ({ adapter: { renderNode }, key }) => {
return renderNode('hr', { key });
}),
renderRule(isHeading, ({ node, adapter: { renderNode }, children, key }) => {
return renderNode(`h${node.level}`, { key }, children);
}),
renderRule(isSpan, ({ adapter: { renderNode, renderText }, key, node }) => {
const marks = node.marks || [];
const lines = node.value.split(/\n/);
const textWithNewlinesConvertedToBr = lines.length > 0
? lines.slice(1).reduce((acc, line, index) => {
return acc.concat([
renderNode('br', { key: `${key}-br-${index}` }),
renderText(line, `${key}-line-${index}`),
]);
}, [renderText(lines[0], `${key}-line-first`)])
: renderText(node.value, key);
return marks.reduce((children, mark) => {
return renderNode(markToTagName(mark), { key }, children);
}, textWithNewlinesConvertedToBr);
}),
]);
}
;
//# sourceMappingURL=render.js.map