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

Commit

Permalink
Merge pull request #270 from adobe/internal-embed
Browse files Browse the repository at this point in the history
Support Internal Embeds
  • Loading branch information
trieloff authored Apr 25, 2019
2 parents eabcfc9 + 575391d commit cacce13
Show file tree
Hide file tree
Showing 11 changed files with 608 additions and 1,037 deletions.
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

0 comments on commit cacce13

Please sign in to comment.