Make sure all old transformations are cancelled when the hook dependencies changed
Signed-off-by: Dominik Henneke <dominik.henneke@sda-se.com>
This commit is contained in:
@@ -28,7 +28,6 @@ import {
|
||||
import { Alert } from '@material-ui/lab';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { useAsync } from 'react-use';
|
||||
import { techdocsStorageApiRef } from '../../api';
|
||||
import {
|
||||
addBaseUrl,
|
||||
@@ -110,32 +109,26 @@ export const Reader = ({ entityId, onReady }: Props) => {
|
||||
// an update to "state" might lead to an updated UI so we include it as a trigger
|
||||
}, [updateSidebarPosition, state]);
|
||||
|
||||
useAsync(async () => {
|
||||
if (!rawPage || !shadowDomRef.current) {
|
||||
return;
|
||||
}
|
||||
if (onReady) {
|
||||
onReady();
|
||||
}
|
||||
|
||||
// Pre-render
|
||||
const transformedElement = await transformer(rawPage, [
|
||||
sanitizeDOM(),
|
||||
addBaseUrl({
|
||||
techdocsStorageApi,
|
||||
entityId: {
|
||||
kind,
|
||||
name,
|
||||
namespace,
|
||||
},
|
||||
path,
|
||||
}),
|
||||
rewriteDocLinks(),
|
||||
removeMkdocsHeader(),
|
||||
simplifyMkdocsFooter(),
|
||||
addGitFeedbackLink(scmIntegrationsApi),
|
||||
injectCss({
|
||||
css: `
|
||||
// a function that performs transformations that are executed prior to adding it to the DOM
|
||||
const preRender = useCallback(
|
||||
(rawContent: string, contentPath: string) =>
|
||||
transformer(rawContent, [
|
||||
sanitizeDOM(),
|
||||
addBaseUrl({
|
||||
techdocsStorageApi,
|
||||
entityId: {
|
||||
kind,
|
||||
name,
|
||||
namespace,
|
||||
},
|
||||
path: contentPath,
|
||||
}),
|
||||
rewriteDocLinks(),
|
||||
removeMkdocsHeader(),
|
||||
simplifyMkdocsFooter(),
|
||||
addGitFeedbackLink(scmIntegrationsApi),
|
||||
injectCss({
|
||||
css: `
|
||||
body {
|
||||
font-family: ${theme.typography.fontFamily};
|
||||
--md-text-color: ${theme.palette.text.primary};
|
||||
@@ -192,21 +185,21 @@ export const Reader = ({ entityId, onReady }: Props) => {
|
||||
}
|
||||
}
|
||||
`,
|
||||
}),
|
||||
injectCss({
|
||||
// Disable CSS animations on link colors as they lead to issues in dark
|
||||
// mode. The dark mode color theme is applied later and theirfore there
|
||||
// is always an animation from light to dark mode when navigation
|
||||
// between pages.
|
||||
css: `
|
||||
}),
|
||||
injectCss({
|
||||
// Disable CSS animations on link colors as they lead to issues in dark
|
||||
// mode. The dark mode color theme is applied later and theirfore there
|
||||
// is always an animation from light to dark mode when navigation
|
||||
// between pages.
|
||||
css: `
|
||||
.md-nav__link, .md-typeset a, .md-typeset a::before, .md-typeset .headerlink {
|
||||
transition: none;
|
||||
}
|
||||
`,
|
||||
}),
|
||||
injectCss({
|
||||
// Properly style code blocks.
|
||||
css: `
|
||||
}),
|
||||
injectCss({
|
||||
// Properly style code blocks.
|
||||
css: `
|
||||
.md-typeset pre > code::-webkit-scrollbar-thumb {
|
||||
background-color: hsla(0, 0%, 0%, 0.32);
|
||||
}
|
||||
@@ -214,17 +207,17 @@ export const Reader = ({ entityId, onReady }: Props) => {
|
||||
background-color: hsla(0, 0%, 0%, 0.87);
|
||||
}
|
||||
`,
|
||||
}),
|
||||
injectCss({
|
||||
// Admonitions and others are using SVG masks to define icons. These
|
||||
// masks are defined as CSS variables.
|
||||
// As the MkDocs output is rendered in shadow DOM, the CSS variable
|
||||
// definitions on the root selector are not applied. Instead, the have
|
||||
// to be applied on :host.
|
||||
// As there is no way to transform the served main*.css yet (for
|
||||
// example in the backend), we have to copy from main*.css and modify
|
||||
// them.
|
||||
css: `
|
||||
}),
|
||||
injectCss({
|
||||
// Admonitions and others are using SVG masks to define icons. These
|
||||
// masks are defined as CSS variables.
|
||||
// As the MkDocs output is rendered in shadow DOM, the CSS variable
|
||||
// definitions on the root selector are not applied. Instead, the have
|
||||
// to be applied on :host.
|
||||
// As there is no way to transform the served main*.css yet (for
|
||||
// example in the backend), we have to copy from main*.css and modify
|
||||
// them.
|
||||
css: `
|
||||
:host {
|
||||
--md-admonition-icon--note: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20.71 7.04c.39-.39.39-1.04 0-1.41l-2.34-2.34c-.37-.39-1.02-.39-1.41 0l-1.84 1.83 3.75 3.75M3 17.25V21h3.75L17.81 9.93l-3.75-3.75L3 17.25z"/></svg>');
|
||||
--md-admonition-icon--abstract: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M4 5h16v2H4V5m0 4h16v2H4V9m0 4h16v2H4v-2m0 4h10v2H4v-2z"/></svg>');
|
||||
@@ -250,97 +243,125 @@ export const Reader = ({ entityId, onReady }: Props) => {
|
||||
--md-tasklist-icon--checked: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10 10-4.5 10-10S17.5 2 12 2m-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/></svg>');
|
||||
}
|
||||
`,
|
||||
}),
|
||||
]);
|
||||
}),
|
||||
]),
|
||||
[
|
||||
kind,
|
||||
name,
|
||||
namespace,
|
||||
scmIntegrationsApi,
|
||||
techdocsStorageApi,
|
||||
theme.palette.background.default,
|
||||
theme.palette.background.paper,
|
||||
theme.palette.primary.main,
|
||||
theme.palette.text.primary,
|
||||
theme.typography.fontFamily,
|
||||
],
|
||||
);
|
||||
|
||||
if (!transformedElement?.innerHTML) {
|
||||
return; // An unexpected error occurred
|
||||
// a function that performs transformations that are executed after adding it to the DOM
|
||||
const postRender = useCallback(
|
||||
async (shadowRoot: ShadowRoot) =>
|
||||
transformer(shadowRoot.children[0], [
|
||||
dom => {
|
||||
setTimeout(() => {
|
||||
// Scoll to the desired anchor on initial navigation
|
||||
if (window.location.hash) {
|
||||
const hash = window.location.hash.slice(1);
|
||||
shadowRoot?.getElementById(hash)?.scrollIntoView();
|
||||
}
|
||||
}, 200);
|
||||
return dom;
|
||||
},
|
||||
addLinkClickListener({
|
||||
baseUrl: window.location.origin,
|
||||
onClick: (_: MouseEvent, url: string) => {
|
||||
const parsedUrl = new URL(url);
|
||||
|
||||
if (parsedUrl.hash) {
|
||||
navigate(`${parsedUrl.pathname}${parsedUrl.hash}`);
|
||||
|
||||
// Scroll to hash if it's on the current page
|
||||
shadowRoot
|
||||
?.getElementById(parsedUrl.hash.slice(1))
|
||||
?.scrollIntoView();
|
||||
} else {
|
||||
navigate(parsedUrl.pathname);
|
||||
}
|
||||
},
|
||||
}),
|
||||
onCssReady({
|
||||
docStorageUrl: await techdocsStorageApi.getApiOrigin(),
|
||||
onLoading: (dom: Element) => {
|
||||
(dom as HTMLElement).style.setProperty('opacity', '0');
|
||||
},
|
||||
onLoaded: (dom: Element) => {
|
||||
(dom as HTMLElement).style.removeProperty('opacity');
|
||||
// disable MkDocs drawer toggling ('for' attribute => checkbox mechanism)
|
||||
(dom as HTMLElement)
|
||||
.querySelector('.md-nav__title')
|
||||
?.removeAttribute('for');
|
||||
const sideDivs: HTMLElement[] = Array.from(
|
||||
shadowRoot!.querySelectorAll('.md-sidebar'),
|
||||
);
|
||||
setSidebars(sideDivs);
|
||||
// set sidebar height so they don't initially render in wrong position
|
||||
const docTopPosition = (dom as HTMLElement).getBoundingClientRect()
|
||||
.top;
|
||||
const mdTabs = dom.querySelector('.md-container > .md-tabs');
|
||||
sideDivs!.forEach(sidebar => {
|
||||
sidebar.style.top = mdTabs
|
||||
? `${docTopPosition + mdTabs.getBoundingClientRect().height}px`
|
||||
: `${docTopPosition}px`;
|
||||
});
|
||||
},
|
||||
}),
|
||||
]),
|
||||
[navigate, techdocsStorageApi],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!rawPage || !shadowDomRef.current) {
|
||||
return () => {};
|
||||
}
|
||||
if (onReady) {
|
||||
onReady();
|
||||
}
|
||||
|
||||
const shadowDiv: HTMLElement = shadowDomRef.current!;
|
||||
const shadowRoot =
|
||||
shadowDiv.shadowRoot || shadowDiv.attachShadow({ mode: 'open' });
|
||||
Array.from(shadowRoot.children).forEach(child =>
|
||||
shadowRoot.removeChild(child),
|
||||
);
|
||||
shadowRoot.appendChild(transformedElement);
|
||||
// if false, there is already a newer execution of this effect
|
||||
let shouldReplaceContent = true;
|
||||
|
||||
// Scroll to top after render
|
||||
window.scroll({ top: 0 });
|
||||
// Pre-render
|
||||
preRender(rawPage, path).then(async transformedElement => {
|
||||
if (!transformedElement?.innerHTML) {
|
||||
return; // An unexpected error occurred
|
||||
}
|
||||
|
||||
// Post-render
|
||||
await transformer(shadowRoot.children[0], [
|
||||
dom => {
|
||||
setTimeout(() => {
|
||||
// Scoll to the desired anchor on initial navigation
|
||||
if (window.location.hash) {
|
||||
const hash = window.location.hash.slice(1);
|
||||
shadowRoot?.getElementById(hash)?.scrollIntoView();
|
||||
}
|
||||
}, 200);
|
||||
return dom;
|
||||
},
|
||||
addLinkClickListener({
|
||||
baseUrl: window.location.origin,
|
||||
onClick: (_: MouseEvent, url: string) => {
|
||||
const parsedUrl = new URL(url);
|
||||
// don't manipulate the shadow dom if this isn't the latest effect execution
|
||||
if (!shouldReplaceContent) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (parsedUrl.hash) {
|
||||
navigate(`${parsedUrl.pathname}${parsedUrl.hash}`);
|
||||
const shadowDiv: HTMLElement = shadowDomRef.current!;
|
||||
const shadowRoot =
|
||||
shadowDiv.shadowRoot || shadowDiv.attachShadow({ mode: 'open' });
|
||||
Array.from(shadowRoot.children).forEach(child =>
|
||||
shadowRoot.removeChild(child),
|
||||
);
|
||||
shadowRoot.appendChild(transformedElement);
|
||||
|
||||
// Scroll to hash if it's on the current page
|
||||
shadowRoot
|
||||
?.getElementById(parsedUrl.hash.slice(1))
|
||||
?.scrollIntoView();
|
||||
} else {
|
||||
navigate(parsedUrl.pathname);
|
||||
}
|
||||
},
|
||||
}),
|
||||
onCssReady({
|
||||
docStorageUrl: await techdocsStorageApi.getApiOrigin(),
|
||||
onLoading: (dom: Element) => {
|
||||
(dom as HTMLElement).style.setProperty('opacity', '0');
|
||||
},
|
||||
onLoaded: (dom: Element) => {
|
||||
(dom as HTMLElement).style.removeProperty('opacity');
|
||||
// disable MkDocs drawer toggling ('for' attribute => checkbox mechanism)
|
||||
(dom as HTMLElement)
|
||||
.querySelector('.md-nav__title')
|
||||
?.removeAttribute('for');
|
||||
const sideDivs: HTMLElement[] = Array.from(
|
||||
shadowRoot!.querySelectorAll('.md-sidebar'),
|
||||
);
|
||||
setSidebars(sideDivs);
|
||||
// set sidebar height so they don't initially render in wrong position
|
||||
const docTopPosition = (dom as HTMLElement).getBoundingClientRect()
|
||||
.top;
|
||||
const mdTabs = dom.querySelector('.md-container > .md-tabs');
|
||||
sideDivs!.forEach(sidebar => {
|
||||
sidebar.style.top = mdTabs
|
||||
? `${docTopPosition + mdTabs.getBoundingClientRect().height}px`
|
||||
: `${docTopPosition}px`;
|
||||
});
|
||||
},
|
||||
}),
|
||||
]);
|
||||
}, [
|
||||
path,
|
||||
kind,
|
||||
namespace,
|
||||
name,
|
||||
rawPage,
|
||||
navigate,
|
||||
onReady,
|
||||
shadowDomRef,
|
||||
techdocsStorageApi,
|
||||
theme.typography.fontFamily,
|
||||
theme.palette.text.primary,
|
||||
theme.palette.primary.main,
|
||||
theme.palette.background.paper,
|
||||
theme.palette.background.default,
|
||||
scmIntegrationsApi,
|
||||
]);
|
||||
// Scroll to top after render
|
||||
window.scroll({ top: 0 });
|
||||
|
||||
// Post-render
|
||||
await postRender(shadowRoot);
|
||||
});
|
||||
|
||||
// cancel this execution
|
||||
return () => {
|
||||
shouldReplaceContent = false;
|
||||
};
|
||||
}, [onReady, path, postRender, preRender, rawPage]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
Reference in New Issue
Block a user