All files index.ts

100% Statements 28/28
100% Branches 23/23
100% Functions 7/7
100% Lines 27/27

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63                                    1x 1x 8x 1x         8x 8x 8x 8x 8x 8x 2x 2x 2x 2x 2x       1x 9x 9x 9x 31x 31x 31x     31x 11x 3x   11x 5x                
import type { Plugin } from 'unified';
import type { Root, Element } from 'hast';
import { visit } from 'unist-util-visit';
import { detailsNode } from './detailsNode.js';
 
export type RehypeVideoOptions = {
  /**
   * URL suffix verification.
   * @default /\/(.*)(.mp4|.mov)$/
   */
  test?: RegExp;
  /**
   * Support `<details>` tag to wrap <video>.
   * @default true
   */
  details?: boolean;
}
 
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, href: string) {
  const filename = href.split('/').pop()?.replace(/(\?|!|\#|$).+/, '');
  node.properties = { ...properties, src: href };
  node.tagName = 'video';
  node.children = [];
  const { title = filename }= queryStringToObject(href);
  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)$/, details = true } = options || {};
  return (tree) => {
    visit(tree, 'element', (node, index, parent) => {
      const isChecked = (str: string) => test.test(str.replace(/(\?|!|\#|$).+/g, '').toLocaleLowerCase())
      const child = node.children[0];
      const delimiter = /((?:https?:\/\/)(?:(?:[a-z0-9]?(?:[a-z0-9\-]{1,61}[a-z0-9])?\.[^\.|\s])+[a-z\.]*[a-z]+|(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3})(?::\d{1,5})*[a-z0-9.,_\/~#&=;%+?\-\\(\\)]*)/g;
      // const delimiter = /((?:https?:\/\/)?(?:(?:[a-z0-9]?(?:[a-z0-9\-]{1,61}[a-z0-9])?\.[^\.|\s])+[a-z\.]*[a-z]+|(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3})(?::\d{1,5})*[a-z0-9.,_\/~#&=;%+?\-\\(\\)]*)/g;
 
      if (node.tagName === 'p' && node.children.length === 1) {
        if (child.type === 'text' && delimiter.test(child.value) && isChecked(child.value)) {
          reElement(node, details, child.value);
        }
        if (child.type === 'element' && child.tagName === 'a' && child.properties && typeof child.properties.href === 'string' && isChecked(child.properties.href)) {
          reElement(node, details, child.properties.href);
        }
      }
    });
  }
}
 
export default RehypeVideo;