UNPKG

npf2html

Version:

Converts Tumblr's Neue Post Format to plain HTML

146 lines 5.74 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.renderInlineFormat = renderInlineFormat; exports.formatText = formatText; /** * Pre-processes {@link formatting} to combine any adjacent identical formats. * Tumblr sometimes splits these up when other formatting is also present, so * this produces cleaner HTML. **/ function mergeAdjacentFormats(formatting) { // A map from format types to the index in mergedFormats of the most recent // occurance of those formats. const lastFormatOfType = {}; const mergedFormats = []; for (const format of formatting) { // Never merge links or mentions. if (format.type === 'link' || format.type === 'mention') { mergedFormats.push(format); continue; } const lastIndex = lastFormatOfType[format.type]; if (lastIndex !== undefined) { const last = mergedFormats[lastIndex]; if (last && canMerge(last, format)) { mergedFormats[lastIndex] = { ...last, end: format.end }; continue; } } lastFormatOfType[format.type] = mergedFormats.length; mergedFormats.push(format); } return mergedFormats; } /** Returns whether two {@link InlineFormat}s can be safely merged. */ function canMerge(format1, format2) { if (format1.end !== format2.start) return false; if (format1.type !== format2.type) return false; switch (format1.type) { case 'bold': case 'italic': case 'strikethrough': case 'small': return true; case 'link': case 'mention': return false; case 'color': return format1.hex === format2.hex; } } /** Builds the list of {@link InlineFormatSpan}s for {@link block}. */ function buildFormatSpans(text, formatting) { var _a; // Sort formats first by start (earliest to latest), then by length (longest // to shortest). This ensures that earlier formats are never nested within // later ones. const formats = [...formatting].sort((a, b) => a.start === b.start ? b.end - a.end : a.start - b.start); // A stack of open spans. Because formats is sorted by start, this will be as // well. const open = []; // The fully-closed spans of formatted text. const spans = []; let codeUnitIndex = 0; let codePointIndex = 0; const end = Math.max(...formats.map(format => format.end)); while (codeUnitIndex < end) { while (codePointIndex === ((_a = formats[0]) === null || _a === void 0 ? void 0 : _a.start)) { open.push({ format: formats.shift(), start: codeUnitIndex, children: [] }); } const outermostClosed = open.findIndex(span => span.format.end === codePointIndex + 1); const codePointLength = text.charCodeAt(codeUnitIndex) >> 10 === 0x36 ? 2 : 1; if (outermostClosed !== -1) { // Tumblr allows inline formats to overlap without being subsets of one // another. To handle this in HTML, we track the formats that aren't // closed yet and add them back into `open` afterwards. const stillOpen = []; for (let j = outermostClosed; j < open.length; j++) { const span = open[j]; (j === 0 ? spans : open[j - 1].children).push({ ...span, end: codeUnitIndex + codePointLength, }); if (span.format.end > codePointIndex + 1) { stillOpen.push({ ...span, start: codeUnitIndex + codePointLength }); } } -open.splice(outermostClosed); open.push(...stillOpen); } codeUnitIndex += codePointLength; codePointIndex++; } if (open.length > 0) spans.push({ ...open[0], end }); for (let i = 1; i < open.length; i++) { spans.at(-1).children.push({ ...open[i], end }); } return spans; } /** * Applies the formatting specified by {@link format} to {@link html}, which may * already include nested formatting. * * @category Inline */ function renderInlineFormat(renderer, html, format) { switch (format.type) { case 'bold': return `<strong>${html}</strong>`; case 'italic': return `<em>${html}</em>`; case 'strikethrough': return `<s>${html}</s>`; case 'small': return `<small>${html}</small>`; case 'link': return `<a href="${renderer.escape(format.url)}">${html}</a>`; case 'mention': return (`<a class="${renderer.prefix}-inline-mention"` + ` href="${renderer.escape(format.blog.url)}">${html}</a>`); case 'color': return (`<span style="color: ${renderer.escape(format.hex)}">` + html + '</span>'); } } /** HTML-escapes {@link text} and formats it according to {@link formatting}. */ function formatText(renderer, text, formatting) { if (!formatting) return renderer.escape(text); const renderSpans = (start, end, children) => { let result = ''; let i = start; for (const child of children) { result += renderer.escape(text.substring(i, child.start)) + renderer.renderInlineFormat(renderSpans(child.start, child.end, child.children), child.format); i = child.end; } return result + renderer.escape(text.substring(i, end)); }; return renderSpans(0, text.length, buildFormatSpans(text, mergeAdjacentFormats(formatting))); } //# sourceMappingURL=inline-format.js.map