All files / src/lib rehype-embed-wrapper.mjs

0% Statements 0/0
0% Branches 0/0
0% Functions 0/0
0% Lines 0/0

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                                                                                           
import { visit } from 'unist-util-visit';
 
// oEmbed (YouTube 等) の iframe を .oembed-container.video でラップする
// remark-embedder が hName/hChildren/hProperties 経由で構造化された iframe を
// 吐くため、rehype 段階では通常の hast element として扱える。
const VIDEO_SRC_PATTERNS = [
  /\/\/(www\.)?youtube\.com\/embed\//i,
  /\/\/(www\.)?youtube-nocookie\.com\/embed\//i,
  /\/\/player\.vimeo\.com\//i,
];
 
function isVideoIframe(node) {
  if (node.tagName !== 'iframe') return false;
  const src = node.properties?.src;
  if (typeof src !== 'string') return false;
  return VIDEO_SRC_PATTERNS.some((re) => re.test(src));
}
 
export default function rehypeEmbedWrapper() {
  return (tree) => {
    visit(tree, 'element', (node, index, parent) => {
      if (!parent || typeof index !== 'number') return;
      if (!isVideoIframe(node)) return;
 
      // すでにラップ済みならスキップ(冪等性)
      if (
        parent.type === 'element'
        && parent.tagName === 'div'
        && Array.isArray(parent.properties?.className)
        && parent.properties.className.includes('oembed-container')
      ) {
        return;
      }
 
      const wrapper = {
        type: 'element',
        tagName: 'div',
        properties: { className: ['oembed-container', 'video'] },
        children: [node],
      };
 
      parent.children[index] = wrapper;
    });
  };
}