mirror of
https://github.com/silverstripe/doc.silverstripe.org
synced 2024-10-22 15:05:50 +00:00
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:
parent
6fe74b1fd8
commit
a8169164b4
@ -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);
|
||||||
|
@ -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]);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user