diff options
| author | Armand Philippot <git@armandphilippot.com> | 2023-11-30 19:30:43 +0100 |
|---|---|---|
| committer | Armand Philippot <git@armandphilippot.com> | 2023-12-01 16:08:54 +0100 |
| commit | 5b762b1b669454a89899c4bdf6008027d9615acf (patch) | |
| tree | 37087f4ee9d14ae131bde15a48d7d04e83ae6cbd /src/utils/helpers/rehype.ts | |
| parent | f7e6f42216c3cbeab9add475a61bb407c6be3519 (diff) | |
refactor(pages): refine Article pages
* use rehype to update code blocks class names
* fix widget heading level (after a level 1 it should always be a level
2 and not 3)
* replace Spinner with LoadingPage and LoadingPageComments components to
keep layout coherent
* refactor useArticle and useComments hooks
* fix URLs in JSON LD schema
* add Cypress tests
Diffstat (limited to 'src/utils/helpers/rehype.ts')
| -rw-r--r-- | src/utils/helpers/rehype.ts | 89 |
1 files changed, 82 insertions, 7 deletions
diff --git a/src/utils/helpers/rehype.ts b/src/utils/helpers/rehype.ts index fc51da1..f061fc2 100644 --- a/src/utils/helpers/rehype.ts +++ b/src/utils/helpers/rehype.ts @@ -1,3 +1,11 @@ +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. * @@ -6,16 +14,83 @@ * @param {string} content - The page contents. * @returns {string} The updated page contents. */ -export const updateContentTree = async (content: string): Promise<string> => { - const { unified } = await import('unified'); - const rehypeParse = (await import('rehype-parse')).default; - const rehypeSlug = (await import('rehype-slug')).default; - const rehypeStringify = (await import('rehype-stringify')).default; - - return unified() +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(); |
