UNPKG

@automattic/rtjson-to-wpblocks

Version:

Javascript code to convert Day One RTJson to WordPress Gutenberg Blocks

286 lines (266 loc) 8.52 kB
const { buildNodeTree } = require("./build-node-tree.js"); // @ts-check /// <reference path="./types.d.ts" /> // MARK: Text node converter /** * * @param {string} blockType * @param {any} attributes * @param {string} htmlContent * @returns string */ function makeGutenbergSerializedBlock(blockType, attributes, htmlContent) { let attrString = attributes && Object.keys(attributes).length > 0 ? JSON.stringify(attributes) + " " : ""; return `<!-- wp:${blockType} ${attrString}--> ${htmlContent} <!-- /wp:${blockType} -->`; } function getTextContent(node) { if (node.attributes) { return applyAttributeTags(node.text.replace(/\n$/, ""), node.attributes); } else { return node.text.replace(/\n$/, ""); } } function convertParagraphToHtml(node, gbHtml = true) { const text = node.content .map((c) => getTextContent(c)) .join("") .replace(/\n$/, ""); const html = `<p>${text}</p>`; return gbHtml ? makeGutenbergSerializedBlock("paragraph", {}, html) : html; } function convertQuoteToHtml(node, gbHtml = true) { let html = `<blockquote class="wp-block-quote">`; for (let paragraph of node.content) { html += convertParagraphToHtml(paragraph, gbHtml); } html += "</blockquote>"; return gbHtml ? makeGutenbergSerializedBlock("quote", {}, html) : html; } function convertCodeToHtml(node, gbHtml = true) { let html = `<pre class="wp-block-code"><code>${node.content .map(getTextContent) .join("")}</code></pre>`; return gbHtml ? makeGutenbergSerializedBlock("code", {}, html) : html; } function convertHeaderToHtml(node, gbHtml = true) { const headerLevel = node.content[0].attributes.line.header; let html = `<h${headerLevel} class="wp-block-heading">${node.content .map(getTextContent) .join("")}</h${headerLevel}>`; const attributes = { level: headerLevel }; return gbHtml ? makeGutenbergSerializedBlock("heading", attributes, html) : html; } function convertListItemToHtml(node, gbHtml = true) { let html = `<li>`; for (let item of node.content) { if (item.type === "text") { const text = getTextContent(item); html += text; } else if (["bulleted", "numbered", "checked"].includes(item.type)) { html += convertListToHtml(item, gbHtml); } } html += `</li>`; return gbHtml ? makeGutenbergSerializedBlock("list-item", {}, html) : html; } function convertListToHtml(node, gbHtml = true) { let listType = node.type === "bulleted" ? "ul" : "ol"; let html = `<${listType}>`; for (let item of node.content) { html += convertListItemToHtml(item, gbHtml); } html += `</${listType}>`; const listMarkup = gbHtml ? makeGutenbergSerializedBlock("list", {}, html) : html; return listMarkup; } /** * @param {string} text * @param {RTJ.TextNode['attributes']} attributes */ function applyAttributeTags(text, attributes = {}) { let accumulator = text; if (attributes.italic) { accumulator = `<em>${accumulator}</em>`; } if (attributes.bold) { accumulator = `<strong>${accumulator}</strong>`; } if (attributes.strikethrough) { accumulator = `<del>${accumulator}</del>`; } if (attributes.highlightedColor) { accumulator = `<mark data-rich-text-format-boundary="true" style="background-color:${attributes.highlightedColor}" class="has-inline-color">${accumulator}</mark>`; } if (attributes.linkURL) { accumulator = `<a href="${attributes.linkURL}">${accumulator}</a>`; } if (attributes.inlineCode) { accumulator = `<code>${accumulator}</code>`; } return accumulator; } // MARK: Embedded content converter /** * * @param {RTJ.EmbeddedContentNode} node * @param {MediaLookup} mediaLookup * @returns {string} */ function convertEmbeddedNode(node, mediaLookup = {}, gbHtml = false) { let html = ""; for (let object of node.embeddedObjects) { if ( object.type === "photo" || object.type === "video" || object.type === "audio" || object.type === "pdfAttachment" ) { let mediaItem = mediaLookup[object.identifier]; if (!mediaItem) { console.error( `Media item with ID ${object.identifier} not found in mediaLookup` ); html += `Error: Media item with ID ${object.identifier} not found in mediaLookup\n`; continue; } // Let's make sure the id is always a number, even if the identifier is passed as a string mediaItem.id = parseInt(String(mediaItem.id)); if (object.type === "photo") { // MARK: Convert a Photo const nodeHtml = `<figure class="wp-block-image size-large"> <img src="${mediaItem.url}" alt="" class="wp-image-${mediaItem.id}"/> </figure>`; html += gbHtml ? makeGutenbergSerializedBlock( "image", { id: mediaItem.id, sizeSlug: "large", linkDestination: "none", }, nodeHtml ) : nodeHtml; } else if (object.type === "video" && gbHtml) { // MARK: Convert a Video const nodeHtml = `<figure class="wp-block-video"> <video controls src="${mediaItem.url}"></video> </figure>`; html += gbHtml ? makeGutenbergSerializedBlock( "video", { id: mediaItem.id }, nodeHtml ) : nodeHtml; } else if (object.type === "audio" && gbHtml) { const nodeHtml = `<figure class="wp-block-audio"> <audio controls src="${mediaItem.url}"></audio> </figure>`; // MARK: Convert an Audio html += gbHtml ? makeGutenbergSerializedBlock( "audio", { id: mediaItem.id }, nodeHtml ) : nodeHtml; } else if (object.type === "pdfAttachment" && gbHtml) { // MARK: Convert a PDF const nodeHtml = `<div class="wp-block-file"> <object class="wp-block-file__embed" data="${mediaItem.url}" type="application/pdf" style="width:100%;height:600px"></object> </div>`; html += gbHtml ? makeGutenbergSerializedBlock( "file", { id: mediaItem.id, href: mediaItem.url, displayPreview: true, }, nodeHtml ) : nodeHtml; } } else if ( object.type === "externalAudio" || object.type === "externalVideo" ) { // MARK: External Audio or Video html += "WARNING: External audio and video not yet supported for publishing from Day One"; } else if (object.type === "horizontalRuleLine") { const nodeHtml = `<hr class="wp-block-separator has-alpha-channel-opacity"/>`; html += gbHtml ? makeGutenbergSerializedBlock("separator", {}, nodeHtml) : nodeHtml; } else { console.warn( `Embedded object of type ${object.type} not yet implemented` ); html += `Error: Embedded object of type ${object.type} not yet implemented\n`; } } if ( node.embeddedObjects.length > 1 && node.embeddedObjects.every((n) => n.type === "photo") ) { html = `<figure class="wp-block-gallery has-nested-images">${html}</figure>`; html = gbHtml ? makeGutenbergSerializedBlock("gallery", {}, html) : html; } return html; } const rtjNodesToGBHtml = (nodes, mediaLookup = {}) => { return rtjNodesToHtml(nodes, mediaLookup, true); }; const rtjNodesToHtml = (nodes, mediaLookup = {}, gbHtml = false) => { const nodeTree = buildNodeTree(nodes); let html = ""; for (let node of nodeTree) { switch (node.type) { case "embedded": html += convertEmbeddedNode(node.content, mediaLookup, gbHtml); break; case "header": html += convertHeaderToHtml(node, gbHtml); break; case "quote": html += convertQuoteToHtml(node, gbHtml); break; case "code": html += convertCodeToHtml(node, gbHtml); break; case "bulleted": case "numbered": case "checked": html += convertListToHtml(node, gbHtml); break; default: html += convertParagraphToHtml(node, gbHtml); } } return html; }; /** * * @param {RTJDoc} rtj * @param {MediaLookup} mediaLookup * @returns string */ const rtjDocToGBHtml = (rtj, mediaLookup = {}) => { return rtjNodesToGBHtml(rtj.contents, mediaLookup); }; module.exports = { rtjDocToGBHtml, rtjNodesToGBHtml, rtjNodesToHtml, };