UNPKG

@yozora/core-html-renderer

Version:
210 lines (189 loc) 8.05 kB
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 };