Merge pull request #241 from creative-commoners/pulls/master/fix-headings

FIX Don't break headings when they include sub-elements.
This commit is contained in:
Steve Boyd 2022-05-20 12:27:59 +12:00 committed by GitHub
commit 415351f12e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
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('&', '-and-') .replace('&', '-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) {
@ -52,6 +54,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,
{ {