FIX Don't break headings when they include sub-elements.

<code> tags added to headings via backticks are the main culprit,
but ultimately if any elements other than text were being added to the
heading, only part of the heading would display, and any custom ID for
the heading anchor link would not be used.
This commit is contained in:
Guy Sartorelli 2022-05-05 17:39:43 +12:00
parent 6fe74b1fd8
commit a8169164b4
3 changed files with 49 additions and 26 deletions

View File

@ -29,7 +29,7 @@ const parseHTML = (html: string): ReactElement | ReactElement[] | string => {
const domChildren = children || []; const domChildren = children || [];
if (name && attribs) { if (name && attribs) {
if (name === 'a') { if (name === 'a') {
return rewriteLink(attribs, domChildren, parseOptions); return rewriteLink(attribs, domChildren, parseOptions, domNode);
} }
if (name === 'table') { if (name === 'table') {
return rewriteTable(domChildren, parseOptions); return rewriteTable(domChildren, parseOptions);

View File

@ -1,6 +1,9 @@
import { createElement, ReactElement } from "react"; 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 => { const generateID = (title: string): string => {
return title return title
.replace('&amp;', '-and-') .replace('&amp;', '-and-')
@ -11,45 +14,58 @@ const generateID = (title: string): string => {
.replace(/-$/g, '') .replace(/-$/g, '')
.toLowerCase(); .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 * If a header has a {#explicit-id}, add it as an attribute
* @param domNode
*/ */
const rewriteHeaders = (domNode: DomElement): ReactElement | false => { const rewriteHeaders = (domNode: DomElement): ReactElement | false => {
if (!domNode.name) { if (!domNode.name) {
return false; return false;
} }
const firstChild = domNode.children ? domNode.children[0] : null;
if (firstChild && firstChild.type === 'text') { const plainText = getFullHeading(domNode);
const { data } = firstChild;
const matches = data.match(/^(.*?){#([A-Za-z0-9_-]+)\}$/); if (plainText) {
// const plainText = getFullHeading(firstChild);
const matches = plainText.match(/^(.*?)\{#([A-Za-z0-9_-]+)\}$/);
let header; let header;
let id; let id;
if (matches) { if (matches) {
header = matches[1]; header = matches[1];
id = matches[2]; id = matches[2];
} else { } else {
header = data; header = plainText;
id = generateID(data); id = generateID(plainText);
} }
const anchor = createElement( const anchor = htmlToDOM(`<a id="${id}" class="anchor" aria-hidden="true" href="#${id}">#</a>`)[0];
'a',
{
"aria-hidden": true,
className: 'anchor',
href: `#${id}`,
id,
key: id,
},
'#'
);
return createElement( const lastChild = domNode.children ? domNode.children[domNode.children.length - 1] : null;
domNode.name, if (lastChild && lastChild.type === 'text') {
{}, lastChild.data = lastChild.data.replace(/\s*{#([A-Za-z0-9_-]+)\}$/, '');
[ header, anchor ] }
);
domNode.children?.push(anchor);
return domToReact([domNode]);
} }

View File

@ -8,6 +8,7 @@ import { SilverstripeDocument } from '../types';
interface LinkAttributes { interface LinkAttributes {
href?: string; href?: string;
class?: string;
}; };
@ -31,7 +32,8 @@ const relativeLink = (currentNode: SilverstripeDocument, href: string): string =
const rewriteLink = ( const rewriteLink = (
attribs: LinkAttributes, attribs: LinkAttributes,
children: DomElement[], children: DomElement[],
parseOptions: HTMLReactParserOptions parseOptions: HTMLReactParserOptions,
domNode: DomElement
): ReactElement|false => { ): ReactElement|false => {
const { href } = attribs; const { href } = attribs;
if (!href) { if (!href) {
@ -43,6 +45,11 @@ const rewriteLink = (
// hash links // hash links
if (href.startsWith('#')) { if (href.startsWith('#')) {
// Just let normal parsing occur for heading links
if (attribs.class === 'anchor') {
return domToReact(domNode);
}
// rewrite all other hashlinks
return createElement( return createElement(
Link, Link,
{ {