Skip to content

Commit

Permalink
feat: add support to mdx
Browse files Browse the repository at this point in the history
  • Loading branch information
sergioramos committed May 18, 2020
1 parent 8992031 commit de54725
Show file tree
Hide file tree
Showing 22 changed files with 1,692 additions and 288 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,18 @@ remark()

> Take a look at [how it would look](test/outputs/async-img-all.png) before executing the script, and the unchanged [HTML output](test/outputs/async-img-all.html).
### `jsx`

To use with [@mdx-js/mdx](https://mdxjs.com/advanced/api), set `jsx` as true.

```javascript
const mdx = require('@mdx-js/mdx');

await mdx(src, {
remarkPlugins: [[require('@sergioramos/remark-oembed'), { jsx: true }],
});
```

## license

BSD-3-Clause
139 changes: 93 additions & 46 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ const h = require('hastscript');
const Intercept = require('apr-intercept');
const { getType } = require('mime');
const toHtml = require('hast-util-to-html');
const { selectAll, matches } = require('hast-util-select');
const { format, parse } = require('url');

const OEMBED_PROVIDERS_URL = 'https://oembed.com/providers.json';
Expand All @@ -29,7 +28,7 @@ const ATTRS = {
};

const Script = () => {
return `
const script = `
document.querySelectorAll('div[data-oembed]').forEach((el) => {
const template = el.querySelector('[data-oembed-template]').content.cloneNode(true);
el.innerHTML = '';
Expand All @@ -45,10 +44,8 @@ const Script = () => {
el.removeAttribute('data-oembed');
});
`;
};

const RequestAnimationFrame = ({ script }) => {
const __html = `
return `
const isDocumentReady = () => {
if (document.readyState !== 'complete') {
document.addEventListener('readystatechange', isDocumentReady);
Expand All @@ -66,10 +63,16 @@ const RequestAnimationFrame = ({ script }) => {
document.addEventListener('readystatechange', isDocumentReady);
}
`;
};

const RequestAnimationFrame = ({ script: __html }) => {
return h('script', { async: true, defer: true }, [__html]);
};

const JsxScript = ({ script: __html }) => {
return `<script defer async dangerouslySetInnerHTML={{ __html: \`${__html}\` }} />`;
};

const Img = ({ id, url, title, width, height, dataSrc }) => {
return h('img', {
id,
Expand All @@ -96,18 +99,26 @@ const Anchor = (props, children = []) => {
);
};

const StaticPhotoOembed = ({ emptyUrl, url, href, ...rest }) => {
const StaticPhotoOembed = ({ emptyUrl, url, href, jsx = false, ...rest }) => {
const src = emptyUrl || url;
const isImage = /^image\//.test(getType(href) || '');

const img = Img({ ...rest, url: src, dataSrc: url });
const anchor = isImage ? img : Anchor({ href }, [img]);

if (!emptyUrl) {
if (!jsx) {
return anchor;
}

return anchor;
const html = toHtml(anchor, {
allowDangerousHtml: true,
});

return {
type: 'jsx',
properties: { dataOembed: true },
value: `<wrapper dangerouslySetInnerHTML={{ __html: \`${html}\` }} />`,
};
};

const resolvePreview = ({ isImage, thumbnail, url, emptyUrl }) => {
Expand All @@ -123,26 +134,43 @@ const resolvePreview = ({ isImage, thumbnail, url, emptyUrl }) => {
return '';
};

const Oembed = ({ type, source, ...rest }) => {
const Oembed = ({ type, source, jsx = false, ...rest }) => {
if (type === 'photo' && !source) {
return StaticPhotoOembed(rest);
return StaticPhotoOembed({ jsx, ...rest });
}

const imgSrc = resolvePreview(rest);
const img = imgSrc ? Img({ ...rest, url: imgSrc }) : '';
const anchor = img ? Anchor({ href: rest.href }, [img]) : img;

return h(
const node = h(
'div',
ATTRS.inline,
[anchor, h('template', ATTRS.template, [source])].filter(Boolean),
);

if (!jsx) {
return node;
}

const html = toHtml(node, {
allowDangerousHtml: true,
});

return {
type: 'jsx',
properties: { dataOembed: true },
value: `<wrapper dangerouslySetInnerHTML={{ __html: \`${html}\` }} />`,
};
};

const transformImage = async (ctx = {}) => {
const { url, width, height, asyncImg = false } = ctx;
if (!url) {
return {};
}

const isImage = /^image\//.test(getType(url) || '');
const isImage = /^image\//.test(getType(parse(url).pathname) || '');
if (!isImage) {
return {};
}
Expand All @@ -157,15 +185,27 @@ const transformImage = async (ctx = {}) => {
};

const processOembed = async (oembed) => {
const { source, url, syncWidget = false } = oembed;
const { source, url, syncWidget = false, jsx = false } = oembed;
const isImage = /^image\//.test(getType(url) || '');

if (!isImage && !source) {
return;
}

if (syncWidget && source) {
return h('div', ATTRS.inline, [source]);
const newNode = h('div', ATTRS.inline, [source]);
if (!jsx) {
return newNode;
}

const html = toHtml(newNode, {
allowDangerousHtml: true,
});

return {
type: 'jsx',
value: `<wrapper dangerouslySetInnerHTML={{ __html: \`${html}\` }} />`,
};
}

return Oembed({
Expand Down Expand Up @@ -229,7 +269,7 @@ const processNode = async (node, { providers = [], ...options }) => {

const { html, ...rest } = await fetchOembed(endpoint);
const source = html ? { type: 'raw', value: html } : undefined;
const oembed = { href: child.url, ...rest, ...options, source };
const oembed = { ...rest, ...options, source, href: child.url };
const newNode = await processOembed(oembed);
return newNode ? newNode : node;
};
Expand All @@ -241,7 +281,29 @@ const fetchOembedProviders = async () => {
});
};

// https://github.com/syntax-tree/unist-util-map/blob/bb0567f651517b2d521af711d7376475b3d8446a/index.js
const map = async (tree, iteratee) => {
const bound = (node) => async (child, index) => {
return preorder(child, index, node);
};

const preorder = async (node, index, parent) => {
const [, newNode = {}] = await Intercept(iteratee(node, index, parent));
const { children = [] } = newNode || node;

return {
...node,
...newNode,
children: await Promise.all(children.map(bound(node))),
};
};

return preorder(tree, null, null);
};

module.exports = (opts = {}) => {
const { jsx = false } = opts;

return async (tree) => {
const providers = await fetchOembedProviders();

Expand All @@ -250,26 +312,6 @@ module.exports = (opts = {}) => {
providers,
};

// https://github.com/syntax-tree/unist-util-map/blob/bb0567f651517b2d521af711d7376475b3d8446a/index.js
const map = async (tree, iteratee) => {
const bound = (node) => async (child, index) => {
return preorder(child, index, node);
};

const preorder = async (node, index, parent) => {
const [, newNode = {}] = await Intercept(iteratee(node, index, parent));
const { children = [] } = newNode || node;

return {
...node,
...newNode,
children: await Promise.all(children.map(bound(node))),
};
};

return preorder(tree, null, null);
};

const newTree = await map(tree, async (node) => {
const isParagraph = node.type === 'paragraph';
const hasChildren = Array.isArray(node.children);
Expand All @@ -288,13 +330,15 @@ module.exports = (opts = {}) => {
return processNode(node, ctx);
});

const hasOembed = Boolean(selectAll('[data-oembed]', newTree).length);
if (!hasOembed) {
return newTree;
}

let count = 0;
const newSerializedTree = await map(newTree, async (node) => {
if (!matches('[data-oembed]', node)) {
const isOembed = ((node || {}).properties || {}).dataOembed;

if (isOembed) {
count += 1;
}

if (!isOembed || jsx) {
return node;
}

Expand All @@ -306,18 +350,21 @@ module.exports = (opts = {}) => {
};
});

if (ctx.syncWidget || !hasOembed) {
if (ctx.syncWidget || !count) {
return newSerializedTree;
}

const script = Script();
const { children = [] } = newSerializedTree;
return Object.assign(newSerializedTree, {
children: children.concat([
{
type: 'html',
value: toHtml(RequestAnimationFrame({ script: Script() }), {
allowDangerousHtml: true,
}),
type: jsx ? 'jsx' : 'html',
value: jsx
? JsxScript({ script })
: toHtml(RequestAnimationFrame({ script }), {
allowDangerousHtml: true,
}),
},
]),
});
Expand Down
17 changes: 12 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,21 @@
"scripts": {
"eslint": "eslint . --ext .js",
"fmt": "prettier --config .prettierrc --write '**/*'",
"test": "NODE_ENV=test c8 -r lcovonly -r text ava --timeout 9999 --serial"
"test": "NODE_ENV=test c8 -r lcovonly -r html -r text ava --timeout 9999 --serial"
},
"dependencies": {
"apr-intercept": "^3.0.3",
"got": "^11.1.2",
"got": "^11.1.4",
"hast-util-select": "^4.0.0",
"hast-util-to-html": "^7.1.1",
"hastscript": "^5.1.2",
"mime": "^2.4.5",
"unist-util-select": "^3.0.1"
"mime": "^2.4.5"
},
"devDependencies": {
"@babel/core": "^7.9.6",
"@babel/preset-react": "^7.9.4",
"@mdx-js/mdx": "^1.6.1",
"@rollup/plugin-virtual": "^2.0.2",
"ava": "^3.8.2",
"c8": "^7.1.2",
"eslint": "^6.8.0",
Expand All @@ -44,8 +47,12 @@
"npm-run-all": "^4.1.5",
"prettier": "^2.0.5",
"puppeteer": "^3.0.4",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"remark": "^12.0.0",
"remark-html": "^11.0.1"
"remark-html": "^11.0.2",
"rollup": "^2.10.2",
"rollup-plugin-babel": "^4.4.0"
},
"husky": {
"hooks": {
Expand Down
Loading

0 comments on commit de54725

Please sign in to comment.