UNPKG

rehype-video

Version:

Markdown supports video play with `.mp4` and `.mov` URLs.

91 lines (83 loc) 2.96 kB
import type { Plugin } from 'unified'; import type { Root, Element } from 'hast'; import { visit } from 'unist-util-visit'; import { detailsNode } from './detailsNode.js'; import { trackNode } from './trackNode.js'; export type RehypeVideoOptions = { /** * URL suffix verification. * @default /\/(.*)(.mp4|.mov)$/ */ test?: RegExp | ((url: string) => boolean); /** * Support `<details>` tag to wrap <video>. * @default true */ details?: boolean; /** * Support `<track>` tag to wrap <video>. * @default true */ track?: boolean; } function isValidURL(string: string) { try { new URL(string); return true; } catch (err) { return false; } } const properties = { muted: 'muted', controls: 'controls', style: 'max-height:640px;' }; const queryStringToObject = (url: string) => [...new URLSearchParams(url.split('?')[1])].reduce( (a: Record<string, string>, [k, v]) => ((a[k] = v), a), {} ); function reElement(node: Element, details: boolean, track: boolean, href: string) { const url = isValidURL(href.trim()) ? new URL(href) : null; const pathname = url?.pathname || href; const filename = pathname.split('/').pop()?.replace(/(\?|!|\#|$).+/, ''); const params = queryStringToObject(href.replace(/\?\!/, "?").replace(/\?\!\#/, "?")); const { title = filename } = params; const result = trackNode(params); const searchParams = new URLSearchParams({ ...result.query }); if (url) { url.search = searchParams.toString() ?? ""; } node.tagName = 'video'; node.children = []; node.properties = { ...properties, src: url?.toString() || href }; if (track) { result.element.forEach((tr) => node.children.push({ ...tr })); } if (details) { const reNode = detailsNode(title); reNode.children.push({ ...node }); node.children = reNode.children; node.tagName = reNode.tagName; node.properties = reNode.properties; } } const RehypeVideo: Plugin<[RehypeVideoOptions?], Root> = (options) => { const { test = /\/(.*)(.mp4|.mov)$/i, details = true, track = true } = options || {}; return (tree) => { visit(tree, 'element', (node, index, parent) => { const isChecked = (str: string) => { if (test instanceof RegExp) return test.test(str.replace(/(\?|!|\#|$).+/g, '').toLocaleLowerCase()) if (typeof test === 'function') return test(str) return false; } const child = node.children[0]; if (node.tagName === 'p' && node.children.length === 1) { if (child.type === 'text' && isValidURL(child.value) && isChecked(child.value)) { reElement(node, details, track, child.value); } if (child.type === 'element' && child.tagName === 'a' && child.properties && typeof child.properties.href === 'string' && isChecked(child.properties.href)) { reElement(node, details, track, child.properties.href); } } }); } } export default RehypeVideo;