@automattic/rtjson-to-wpblocks
Version:
Javascript code to convert Day One RTJson to WordPress Gutenberg Blocks
286 lines (266 loc) • 8.52 kB
JavaScript
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,
};