@yozora/core-html-renderer
Version:
Yozora renderer in html
210 lines (189 loc) • 8.05 kB
JavaScript
import prism from 'prismjs';
import { BlockquoteType, BreakType, CodeType, DefinitionType, DeleteType, EmphasisType, HeadingType, HtmlType, ImageType, ImageReferenceType, InlineCodeType, LinkType, LinkReferenceType, ListType, ListItemType, ParagraphType, StrongType, TableType, TextType, ThematicBreakType } from '@yozora/ast';
import sanitize from 'sanitize-html';
const renderBlockquote = (node, context) => {
const children = context.renderChildren(node.children);
return `<blockquote class="yozora-blockquote">${children}</blockquote>`;
};
const renderBreak = () => {
return '<br class="yozora-break" />';
};
const renderCode = (node, context) => {
const { value, lang } = node;
const highlightedCode = lang != null && prism.languages[lang] != null
? prism.highlight(value, prism.languages[lang], lang)
: context.sanitize(value);
return `<pre class="yozora-code"><code>${highlightedCode}</code></pre>`;
};
const renderDelete = (node, context) => {
const children = context.renderChildren(node.children);
return `<del class="yozora-delete">${children}</del>`;
};
const renderEmphasis = (node, context) => {
const children = context.renderChildren(node.children);
return `<em class="yozora-emphasis">${children}</em>`;
};
const renderHeading = (node, context) => {
const depth = Number(node.depth);
const H = `h${depth}`;
const children = context.renderChildren(node.children);
return `<${H} class="yozora-heading">${children}</${H}>`;
};
const renderImage = (node, context) => {
const url = context.sanitize(node.url);
const alt = context.sanitize(node.alt);
const title = context.sanitize(node.title || alt);
return `<img class="yozora-image" alt="${alt}" src="${url}" title="${title}" />`;
};
const renderImageReference = (node, context) => {
const definition = context.getDefinition(node.identifier);
if (definition == null)
return '';
const url = context.sanitize(definition.url);
const alt = context.sanitize(definition.label);
const title = context.sanitize(definition.title || alt);
return `<img class="yozora-image" alt="${alt}" src="${url}" title="${title}" />`;
};
const renderInlineCode = (node, context) => {
const value = context.sanitize(node.value);
return `<pre class="yozora-inline-code"><code>${value}</code></pre>`;
};
const renderLink = (node, context) => {
const url = context.sanitize(node.url);
const title = context.sanitize(node.title || url);
const children = context.renderChildren(node.children);
return `<a class="yozora-link" href="${url}" title="${title}" target="_blank" rel="noopener,noreferrer">${children}</a>`;
};
const renderLinkReference = (node, context) => {
const definition = context.getDefinition(node.identifier);
if (definition == null)
return '';
const url = context.sanitize(definition.url);
const title = context.sanitize(definition.title || url);
const children = context.renderChildren(node.children);
return `<a class="yozora-link" href="${url}" title="${title}" target="_blank" rel="noopener,noreferrer">${children}</a>`;
};
const renderList = (node, context) => {
const children = context.renderChildren(node.children);
if (node.ordered) {
const { start = 1 } = node;
return `<ol class="yozora-list" start="${Number(start)}">${children}</ol>`;
}
return `<ul class="yozora-list">${children}</ul>`;
};
const renderListItem = (node, context) => {
const children = context.renderChildren(node.children);
let checkbox = '';
if (node.status != null) {
switch (node.status) {
case 'todo':
checkbox = '<input disabled="" type="checkbox" /> ';
break;
case 'done':
checkbox = '<input checked="" disabled="" type="checkbox" /> ';
break;
}
}
return `<li class="yozora-list-item">${checkbox}${children}</li>`;
};
const renderParagraph = (node, context) => {
const children = context.renderChildren(node.children);
return `<p class="yozora-paragraph">${children}</p>`;
};
const renderStrong = (node, context) => {
const children = context.renderChildren(node.children);
return `<strong class="yozora-strong">${children}</strong>`;
};
const ColumnAlignTypeMap = {
left: 'left',
right: 'right',
center: 'center',
};
const renderTable = (node, context) => {
const aligns = node.columns.map(col => {
if (col.align == null)
return null;
return ColumnAlignTypeMap[col.align.toLowerCase()] ?? null;
});
const [tHeadRow, ...tBodyRows] = node.children;
const thead = '<thead class="yozora-table__thead"><tr class="yozora-table-row">' +
tHeadRow.children
.map((th, i) => {
const align = aligns[i] == null ? '' : ` align="${aligns[i]}"`;
const ths = context.renderChildren(th.children);
return `<th class="yozora-table-cell"${align}>${ths}</th>`;
})
.join('') +
'</tr></thead>';
const tbody = '<tbody class="yozora-table__tbody">' +
tBodyRows
.map((row) => {
const tds = row.children
.map((td, i) => {
const align = aligns[i] == null ? '' : ` align="${aligns[i]}"`;
const ths = context.renderChildren(td.children);
return `<td class="yozora-table-cell"${align}>${ths}</td>`;
})
.join('');
return `<tr class="yozora-table-row">${tds}</tr>`;
})
.join('') +
'</tbody>';
return `<table class="yozora-table">${thead}${tbody}</table>`;
};
const renderText = (node, context) => {
const value = context.sanitize(node.value);
return `<span class="yozora-text">${value}</span>`;
};
const renderThematicBreak = () => {
return '<hr class="yozora-thematic-break" />';
};
const defaultRendererMap = {
[BlockquoteType]: renderBlockquote,
[BreakType]: renderBreak,
[CodeType]: renderCode,
[DefinitionType]: () => '',
[DeleteType]: renderDelete,
[EmphasisType]: renderEmphasis,
[HeadingType]: renderHeading,
[HtmlType]: () => '',
[ImageType]: renderImage,
[ImageReferenceType]: renderImageReference,
[InlineCodeType]: renderInlineCode,
[LinkType]: renderLink,
[LinkReferenceType]: renderLinkReference,
[ListType]: renderList,
[ListItemType]: renderListItem,
[ParagraphType]: renderParagraph,
[StrongType]: renderStrong,
[TableType]: renderTable,
[TextType]: renderText,
[ThematicBreakType]: renderThematicBreak,
_fallback: node => {
console.warn(`Cannot find render for \`${node.type}\` type node:`, node);
return '';
},
};
function createNodeRendererContext(definitionMap, footnoteDefinitionMap, rendererMap = defaultRendererMap) {
const context = {
sanitize: value => sanitize(value, { allowedTags: [] }),
getDefinition: (identifier) => definitionMap[identifier],
getFootnoteDefinition: (identifier) => footnoteDefinitionMap[identifier],
renderChildren: (nodes) => {
if (nodes == null || nodes.length < 1)
return '';
return nodes
.map(node => {
const renderNode = rendererMap[node.type];
if (renderNode === undefined) {
console.warn(`Cannot find renderer for node ${node.type}`);
return '';
}
return renderNode(node, context);
})
.join('');
},
};
return context;
}
export { createNodeRendererContext, defaultRendererMap, renderBlockquote, renderBreak, renderCode, renderDelete, renderEmphasis, renderHeading, renderImage, renderImageReference, renderInlineCode, renderLink, renderLinkReference, renderList, renderListItem, renderParagraph, renderStrong, renderTable, renderText, renderThematicBreak };