UNPKG

@r4ai/remark-embed

Version:

[![JSR](https://jsr.io/badges/@r4ai/remark-embed)](https://jsr.io/@r4ai/remark-embed) [![codecov](https://codecov.io/gh/r4ai/remark-embed/graph/badge.svg?token=B9EZXC0PR8)](https://codecov.io/gh/r4ai/remark-embed) [![CI](https://github.com/r4ai/remark-emb

121 lines (120 loc) 4.1 kB
/// <reference types="mdast-util-to-hast" /> import { defu } from "defu"; import { visit } from "unist-util-visit"; /** * Default options for the {@link remarkEmbed} plugin. */ export const defaultRemarkEmbedOptions = { transformers: [], }; /** * A remark plugin to embed the content of the URL. * * @example * ```ts * import rehypeStringify from "rehype-stringify" * import remarkParse from "remark-parse" * import remarkRehype from "remark-rehype" * import { unified } from "unified" * import remarkEmbed from "./src/index.js" * import { transformerOEmbed } from "./src/transformers" * * const md = ` * <https://www.youtube.com/watch?v=jNQXAC9IVRw> * ` * * const html = ( * await unified() * .use(remarkParse) * .use(remarkRehype) * .use(remarkEmbed, { * transformers: [transformerOEmbed()], * }) * .use(rehypeStringify) * .process(md) * ).toString() * * console.log(html) * ``` * * Yields: * * ```html * <p> * <div class="oembed-video"> * <iframe * width="200" * height="150" * src="https://www.youtube.com/embed/jNQXAC9IVRw?feature=oembed" * frameborder="0" * allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" * referrerpolicy="strict-origin-when-cross-origin" * allowfullscreen * title="Me at the zoo"> * </iframe> * </div> * </p> * ``` */ export const remarkEmbed = (_options) => { const options = defu(_options, defaultRemarkEmbedOptions); return async (tree, file) => { const transforming = []; visit(tree, "link", (link, _, paragraph) => { // Check if the paragraph only contains a single url link // e.g. OK: `https://example.com/hoge` // NG: `according to example.com/hoge` // NG: `[example](https://example.com/hoge)` if (paragraph?.type !== "paragraph" || paragraph.children.length !== 1 || (link.data?.hName != null && link.data?.hName !== "a") || link.children.length !== 1 || link.children[0].type !== "text" || link.children[0].value !== link.url) { return; } const url = new URL(link.url); const transform = async () => { for (const transformer of options.transformers) { try { if (!(await transformer.match(url))) continue; if (!link.data) link.data = {}; link.data.hName = await getHName(transformer, url); link.data.hProperties = { ...(link.data?.hProperties ?? {}), ...(await getHProperties(transformer, url)), }; link.data.hChildren = await getHChildren(transformer, url); return; } catch (e) { const msg = `[transformer:${transformer.name}] Failed to embed ${link.url} in ${file.path} at line ${link.position?.start?.line}.`; file.message(`${msg}; ${JSON.stringify(e)}`, link.position, "@r4ai/remark-embed"); } } }; transforming.push(transform()); }); await Promise.all(transforming); }; }; const getHName = async (transformer, url) => { if (typeof transformer.tagName === "function") { return transformer.tagName(url); } return transformer.tagName; }; const getHProperties = async (transformer, url) => { if (typeof transformer.properties === "function") { return transformer.properties(url); } return transformer.properties; }; const getHChildren = async (transformer, url) => { if (typeof transformer.children === "function") { return transformer.children(url); } return transformer.children; };