aboutsummaryrefslogtreecommitdiffstats
path: root/src/utils/helpers/rehype.ts
blob: f061fc21f6afe6af350b888fb750fe80a92f09f6 (plain)
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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
import type Hast from 'hast';
import { classnames } from 'hast-util-classnames';
import rehypeParse from 'rehype-parse';
import rehypeSlug from 'rehype-slug';
import rehypeStringify from 'rehype-stringify';
import { unified, type Plugin as UnifiedPlugin } from 'unified';
import { visit } from 'unist-util-visit';

/**
 * Update a stringified HTML tree using unified plugins.
 *
 * It will parse the provided content to add id to each headings.
 *
 * @param {string} content - The page contents.
 * @returns {string} The updated page contents.
 */
export const updateContentTree = (content: string): string =>
  unified()
    .use(rehypeParse, { fragment: true })
    .use(rehypeSlug)
    .use(rehypeStringify)
    .processSync(content)
    .toString();

const isSubStrIn = (substr: string | RegExp, str: string) => {
  if (typeof substr === 'string') return str.includes(substr);

  return substr.test(str);
};

const isNodeContainsClass = (
  node: Hast.Element,
  className: string | RegExp
) => {
  if (Array.isArray(node.properties.className)) {
    return node.properties.className.some(
      (singleClass) =>
        typeof singleClass === 'string' && isSubStrIn(className, singleClass)
    );
  }

  if (typeof node.properties.className === 'string')
    return isSubStrIn(className, node.properties.className);

  return false;
};

const rehypePrismClass: UnifiedPlugin<
  Record<'className', string>[],
  Hast.Root
> =
  ({ className }) =>
  (tree) => {
    const wpBlockClassName = 'wp-block-code';
    const lineNumbersClassName = className
      .replace('command-line', '')
      .replace(/\s\s+/g, ' ');
    const commandLineClassName = className
      .replace('line-numbers', '')
      .replace(/\s\s+/g, ' ');

    visit(tree, 'element', (node) => {
      if (
        node.tagName === 'pre' &&
        isNodeContainsClass(node, wpBlockClassName)
      ) {
        if (isNodeContainsClass(node, 'language-bash')) {
          classnames(node, commandLineClassName);
          node.properties['data-filter-output'] = '#output#';
        } else if (isNodeContainsClass(node, /language-/)) {
          classnames(node, lineNumbersClassName);
        }
      }
    });
  };

/**
 * Update a stringified HTML tree using unified plugins.
 *
 * It will parse the provided content to update the classnames of WordPress
 * code blocks.
 *
 * @param {string} content - The page contents.
 * @param {string} className - The prism classNames.
 * @returns {string} The updated page contents.
 */
export const updateWordPressCodeBlocks = (
  content: string,
  className: string
): string =>
  unified()
    .use(rehypeParse, { fragment: true })
    .use(rehypePrismClass, { className })
    .use(rehypeStringify)
    .processSync(content)
    .toString();