Skip to content
This repository has been archived by the owner on Feb 28, 2022. It is now read-only.

Support Internal Embeds #270

Merged
merged 5 commits into from
Apr 25, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 64 additions & 1 deletion src/html/find-embeds.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
const map = require('unist-util-map');
const URI = require('uri-js');
const mm = require('micromatch');
const p = require('path');

/**
* Finds embeds like `video: https://www.youtube.com/embed/2Xc9gXyf2G4`
Expand All @@ -26,6 +27,16 @@ function gatsbyEmbed(text) {
return false;
}

function internalGatsbyEmbed(text, base, contentext, resourceext) {
const matches = new RegExp(`^(markdown|html|embed): ?(.*(${contentext}|${resourceext}))$`)
.exec(text);
if (matches && matches[2]) {
const uri = URI.parse(URI.resolve(base, matches[2]));
return uri.reference === 'relative' && uri.path ? uri : false;
}
return false;
}

/**
* Finds embeds that are single absolute links in a paragraph
* @param {*} node An MDAST node
Expand All @@ -40,6 +51,21 @@ function iaEmbed({ type, children }) {
return false;
}

function internalIaEmbed({ type, children }, base, contentext, resourceext) {
if (type === 'paragraph'
&& children.length === 1
&& children[0].type === 'text'
&& children[0].value
&& !children[0].value.match(/\n/)
&& !children[0].value.match(/ /)
&& (children[0].value.endsWith(contentext) || (children[0].value.endsWith(resourceext)))
) {
const uri = URI.parse(URI.resolve(base, children[0].value));
return uri.reference === 'relative' && uri.path ? uri : false;
}
return false;
}

function imgEmbed({ type, children }) {
if (type === 'paragraph'
&& children.length === 1
Expand All @@ -51,6 +77,18 @@ function imgEmbed({ type, children }) {
return false;
}

function internalImgEmbed({ type, children }, base, contentext, resourceext) {
if (type === 'paragraph'
&& children.length === 1
&& children[0].type === 'image'
&& URI.parse(children[0].url).reference === 'relative'
&& (children[0].url.endsWith(contentext) || (children[0].url.endsWith(resourceext)))) {
const uri = URI.parse(URI.resolve(base, children[0].url));
return uri.reference === 'relative' && uri.path ? uri : false;
}
return false;
}

function embed(uri, node, whitelist = '', warn = () => {}) {
if (mm.some(uri.host, whitelist.split(','))) {
const children = [Object.assign({}, node)];
Expand All @@ -65,18 +103,43 @@ function embed(uri, node, whitelist = '', warn = () => {}) {
}
}

function find({ content: { mdast } }, { logger, secrets: { EMBED_WHITELIST } }) {
function internalembed(uri, node, extension) {
const children = [Object.assign({}, node)];
node.type = 'embed';
node.children = children;
node.url = p.resolve(p.dirname(uri.path), p.basename(uri.path, p.extname(uri.path)) + extension);
if (node.value) {
delete node.value;
}
}

function find({ content: { mdast }, request: { extension, url } },
{ logger, secrets: { EMBED_WHITELIST, EMBED_SELECTOR }, request: { params: { path } } }) {
const resourceext = `.${extension}`;
const contentext = p.extname(path);
map(mdast, (node) => {
if (node.type === 'inlineCode' && gatsbyEmbed(node.value)) {
embed(gatsbyEmbed(node.value), node, EMBED_WHITELIST, logger.warn);
} else if (node.type === 'paragraph' && iaEmbed(node)) {
embed(iaEmbed(node), node, EMBED_WHITELIST, logger.warn);
} else if (node.type === 'paragraph' && imgEmbed(node)) {
embed(imgEmbed(node), node, EMBED_WHITELIST, logger.warn);
} else if (node.type === 'inlineCode'
&& internalGatsbyEmbed(node.value, url, contentext, resourceext)) {
internalembed(internalGatsbyEmbed(node.value, url, contentext, resourceext), node, `.${EMBED_SELECTOR}.${extension}`);
} else if (node.type === 'paragraph'
&& internalIaEmbed(node, url, contentext, resourceext)) {
internalembed(internalIaEmbed(node, url, contentext, resourceext), node, `.${EMBED_SELECTOR}.${extension}`);
} else if (node.type === 'paragraph'
&& internalImgEmbed(node, url, contentext, resourceext)) {
internalembed(internalImgEmbed(node, url, contentext, resourceext), node, `.${EMBED_SELECTOR}.${extension}`);
}
});

return { content: { mdast } };
}

module.exports = find;
module.exports.internalGatsbyEmbed = internalGatsbyEmbed;
module.exports.internalIaEmbed = internalIaEmbed;
module.exports.internalImgEmbed = internalImgEmbed;
5 changes: 5 additions & 0 deletions src/schemas/secrets.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@
"description": "URL of an Embed Service that takes the appended URL and returns an embeddable HTML representation.",
"default": "https://adobeioruntime.net/api/v1/web/helix/default/embed/"
},
"EMBED_SELECTOR": {
"type": "string",
"description": "Selector to be used when resolving internal embeds.",
"default": "embed"
},
"IMAGES_MIN_SIZE": {
"type": "integer",
"description": "Minimum physical width of responsive images to generate",
Expand Down
16 changes: 13 additions & 3 deletions src/utils/embed-handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,23 @@
* Handles `embed` MDAST nodes by converting them into `<esi:include>` tags
* @param {string} EMBED_SERVICE the URL of an embedding service compatible with https://github.com/adobe/helix-embed that returns HTML
*/
const URI = require('uri-js');

function embed({ EMBED_SERVICE }) {
return function handler(h, node) {
return function handler(h, node, _, handlechild) {
const { url } = node;
const props = {
src: EMBED_SERVICE + url,
// prepend the embed service for absolute URLs
src: (URI.parse(url).reference === 'absolute' ? EMBED_SERVICE : '') + url,
};
const retval = h(node, 'esi:include', props);
const retval = [h(node, 'esi:include', props)];

if (node.children && node.children.length) {
const rem = h(node, 'esi:remove', {});
node.children.forEach(childnode => handlechild(h, childnode, node, rem));
retval.push(rem);
}

return retval;
};
}
Expand Down
18 changes: 17 additions & 1 deletion src/utils/mdast-to-vdom.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,23 @@ class VDOMTransformer {
const handlefn = that.matches(node);

// process the node
const result = handlefn(cb, node, parent);

/**
* A function that enables the recursive processing of MDAST child nodes
* in handler functions.
* @param {function} callback the HAST-constructing callback function
* @param {Node} childnode the MDAST child node that should be handled
* @param {Node} mdastparent the MDAST parent node, usually the current MDAST node
* processed by the handler function
* @param {*} hastparent the HAST parent node that the transformed child will be appended to
*/
function handlechild(callback, childnode, mdastparent, hastparent) {
if (hastparent && hastparent.children) {
hastparent.children.push(VDOMTransformer.handle(callback, childnode, mdastparent, that));
}
}

const result = handlefn(cb, node, parent, handlechild);

if (result && typeof result === 'string') {
return VDOMTransformer.toHTAST(result, cb, node);
Expand Down
Loading