Fix code block highlighting not working reliably with many code blocks (#28613)

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
Michael Telatynski 2024-12-02 14:03:14 +00:00 committed by GitHub
parent 2c3e01a31c
commit e75ff818d3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 28 additions and 20 deletions

View file

@ -52,8 +52,6 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
private tooltips = new ReactRootManager(); private tooltips = new ReactRootManager();
private reactRoots = new ReactRootManager(); private reactRoots = new ReactRootManager();
private ref = createRef<HTMLDivElement>();
public static contextType = RoomContext; public static contextType = RoomContext;
declare public context: React.ContextType<typeof RoomContext>; declare public context: React.ContextType<typeof RoomContext>;
@ -86,7 +84,7 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
if (this.props.mxEvent.getContent().format === "org.matrix.custom.html") { if (this.props.mxEvent.getContent().format === "org.matrix.custom.html") {
// Handle expansion and add buttons // Handle expansion and add buttons
const pres = this.ref.current?.getElementsByTagName("pre"); const pres = [...content.getElementsByTagName("pre")];
if (pres && pres.length > 0) { if (pres && pres.length > 0) {
for (let i = 0; i < pres.length; i++) { for (let i = 0; i < pres.length; i++) {
// If there already is a div wrapping the codeblock we want to skip this. // If there already is a div wrapping the codeblock we want to skip this.
@ -115,13 +113,14 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
root.className = "mx_EventTile_pre_container"; root.className = "mx_EventTile_pre_container";
// Insert containing div in place of <pre> block // Insert containing div in place of <pre> block
pre.parentNode?.replaceChild(root, pre); pre.replaceWith(root);
this.reactRoots.render( this.reactRoots.render(
<StrictMode> <StrictMode>
<CodeBlock onHeightChanged={this.props.onHeightChanged}>{pre}</CodeBlock> <CodeBlock onHeightChanged={this.props.onHeightChanged}>{pre}</CodeBlock>
</StrictMode>, </StrictMode>,
root, root,
pre,
); );
} }
@ -196,10 +195,9 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
</StrictMode> </StrictMode>
); );
this.reactRoots.render(spoiler, spoilerContainer); this.reactRoots.render(spoiler, spoilerContainer, node);
node.parentNode?.replaceChild(spoilerContainer, node);
node.replaceWith(spoilerContainer);
node = spoilerContainer; node = spoilerContainer;
} }
@ -479,12 +477,7 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
if (isEmote) { if (isEmote) {
return ( return (
<div <div className="mx_MEmoteBody mx_EventTile_content" onClick={this.onBodyLinkClick} dir="auto">
className="mx_MEmoteBody mx_EventTile_content"
onClick={this.onBodyLinkClick}
dir="auto"
ref={this.ref}
>
*&nbsp; *&nbsp;
<span className="mx_MEmoteBody_sender" onClick={this.onEmoteSenderClick}> <span className="mx_MEmoteBody_sender" onClick={this.onEmoteSenderClick}>
{mxEvent.sender ? mxEvent.sender.name : mxEvent.getSender()} {mxEvent.sender ? mxEvent.sender.name : mxEvent.getSender()}
@ -497,7 +490,7 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
} }
if (isNotice) { if (isNotice) {
return ( return (
<div className="mx_MNoticeBody mx_EventTile_content" onClick={this.onBodyLinkClick} ref={this.ref}> <div className="mx_MNoticeBody mx_EventTile_content" onClick={this.onBodyLinkClick}>
{body} {body}
{widgets} {widgets}
</div> </div>
@ -505,14 +498,14 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
} }
if (isCaption) { if (isCaption) {
return ( return (
<div className="mx_MTextBody mx_EventTile_caption" onClick={this.onBodyLinkClick} ref={this.ref}> <div className="mx_MTextBody mx_EventTile_caption" onClick={this.onBodyLinkClick}>
{body} {body}
{widgets} {widgets}
</div> </div>
); );
} }
return ( return (
<div className="mx_MTextBody mx_EventTile_content" onClick={this.onBodyLinkClick} ref={this.ref}> <div className="mx_MTextBody mx_EventTile_content" onClick={this.onBodyLinkClick}>
{body} {body}
{widgets} {widgets}
</div> </div>

View file

@ -15,23 +15,38 @@ import { createRoot, Root } from "react-dom/client";
export class ReactRootManager { export class ReactRootManager {
private roots: Root[] = []; private roots: Root[] = [];
private rootElements: Element[] = []; private rootElements: Element[] = [];
private revertElements: Array<null | Element> = [];
public get elements(): Element[] { public get elements(): Element[] {
return this.rootElements; return this.rootElements;
} }
public render(children: ReactNode, element: Element): void { /**
const root = createRoot(element); * Render a React component into a new root based on the given root element
* @param children the React component to render
* @param rootElement the root element to render the component into
* @param revertElement the element to replace the root element with when unmounting
*/
public render(children: ReactNode, rootElement: Element, revertElement?: Element): void {
const root = createRoot(rootElement);
this.roots.push(root); this.roots.push(root);
this.rootElements.push(element); this.rootElements.push(rootElement);
this.revertElements.push(revertElement ?? null);
root.render(children); root.render(children);
} }
/**
* Unmount all roots and revert the elements they were rendered into
*/
public unmount(): void { public unmount(): void {
while (this.roots.length) { while (this.roots.length) {
const root = this.roots.pop()!; const root = this.roots.pop()!;
this.rootElements.pop(); const rootElement = this.rootElements.pop();
const revertElement = this.revertElements.pop();
root.unmount(); root.unmount();
if (revertElement) {
rootElement?.replaceWith(revertElement);
}
} }
} }
} }