diff --git a/src/utils/parseHTML.ts b/src/utils/parseHTML.ts index 0d6c48e..df93fb4 100644 --- a/src/utils/parseHTML.ts +++ b/src/utils/parseHTML.ts @@ -29,7 +29,7 @@ const parseHTML = (html: string): ReactElement | ReactElement[] | string => { const domChildren = children || []; if (name && attribs) { if (name === 'a') { - return rewriteLink(attribs, domChildren, parseOptions); + return rewriteLink(attribs, domChildren, parseOptions, domNode); } if (name === 'table') { return rewriteTable(domChildren, parseOptions); diff --git a/src/utils/rewriteHeader.ts b/src/utils/rewriteHeader.ts index a96653d..f3e67f0 100644 --- a/src/utils/rewriteHeader.ts +++ b/src/utils/rewriteHeader.ts @@ -1,6 +1,9 @@ import { createElement, ReactElement } from "react"; -import { DomElement } from 'html-react-parser'; +import { DomElement, domToReact, htmlToDOM } from 'html-react-parser'; +/** + * Generate the ID for a heading + */ const generateID = (title: string): string => { return title .replace('&', '-and-') @@ -11,45 +14,58 @@ const generateID = (title: string): string => { .replace(/-$/g, '') .toLowerCase(); } + +/** + * Get the full plain text of the heading for checking and generating the ID + */ +const getFullHeading = (element: DomElement): string => { + let text = ''; + if (element.type === 'text') { + text += element.data; + } + + if (element.children) { + for (const child of element.children) { + text += getFullHeading(child); + } + } + + return text; +} + /** * If a header has a {#explicit-id}, add it as an attribute - * @param domNode */ const rewriteHeaders = (domNode: DomElement): ReactElement | false => { if (!domNode.name) { return false; } - const firstChild = domNode.children ? domNode.children[0] : null; - if (firstChild && firstChild.type === 'text') { - const { data } = firstChild; - const matches = data.match(/^(.*?){#([A-Za-z0-9_-]+)\}$/); + + const plainText = getFullHeading(domNode); + + if (plainText) { + // const plainText = getFullHeading(firstChild); + const matches = plainText.match(/^(.*?)\{#([A-Za-z0-9_-]+)\}$/); let header; let id; if (matches) { header = matches[1]; id = matches[2]; } else { - header = data; - id = generateID(data); + header = plainText; + id = generateID(plainText); } - const anchor = createElement( - 'a', - { - "aria-hidden": true, - className: 'anchor', - href: `#${id}`, - id, - key: id, - }, - '#' - ); + const anchor = htmlToDOM(``)[0]; - return createElement( - domNode.name, - {}, - [ header, anchor ] - ); + const lastChild = domNode.children ? domNode.children[domNode.children.length - 1] : null; + if (lastChild && lastChild.type === 'text') { + lastChild.data = lastChild.data.replace(/\s*{#([A-Za-z0-9_-]+)\}$/, ''); + } + + domNode.children?.push(anchor); + + return domToReact([domNode]); } diff --git a/src/utils/rewriteLink.ts b/src/utils/rewriteLink.ts index 4e3ed5c..d1fe88c 100644 --- a/src/utils/rewriteLink.ts +++ b/src/utils/rewriteLink.ts @@ -8,6 +8,7 @@ import { SilverstripeDocument } from '../types'; interface LinkAttributes { href?: string; + class?: string; }; @@ -31,7 +32,8 @@ const relativeLink = (currentNode: SilverstripeDocument, href: string): string = const rewriteLink = ( attribs: LinkAttributes, children: DomElement[], - parseOptions: HTMLReactParserOptions + parseOptions: HTMLReactParserOptions, + domNode: DomElement ): ReactElement|false => { const { href } = attribs; if (!href) { @@ -52,6 +54,11 @@ const rewriteLink = ( // hash links if (href.startsWith('#')) { + // Just let normal parsing occur for heading links + if (attribs.class === 'anchor') { + return domToReact(domNode); + } + // rewrite all other hashlinks return createElement( Link, {