UNPKG

@backstage/plugin-techdocs

Version:

The Backstage plugin that renders technical documentation for your components

228 lines (225 loc) • 8.88 kB
import { useState, useCallback, useEffect, useLayoutEffect } from 'react'; import useMediaQuery from '@material-ui/core/useMediaQuery'; import { useTheme } from '@material-ui/core/styles'; import { useAnalytics, useApi, configApiRef } from '@backstage/core-plugin-api'; import { scmIntegrationsApiRef } from '@backstage/integration-react'; import { techdocsStorageApiRef, useShadowDomStylesLoading } from '@backstage/plugin-techdocs-react'; import { useTechDocsReader } from '../TechDocsReaderProvider.esm.js'; import { useSanitizerTransformer } from '../../transformers/html/transformer.esm.js'; import { useStylesTransformer } from '../../transformers/styles/transformer.esm.js'; import { addBaseUrl } from '../../transformers/addBaseUrl.esm.js'; import { addGitFeedbackLink } from '../../transformers/addGitFeedbackLink.esm.js'; import { addSidebarToggle } from '../../transformers/addSidebarToggle.esm.js'; import { rewriteDocLinks } from '../../transformers/rewriteDocLinks.esm.js'; import { addLinkClickListener } from '../../transformers/addLinkClickListener.esm.js'; import { copyToClipboard } from '../../transformers/copyToClipboard.esm.js'; import { removeMkdocsHeader } from '../../transformers/removeMkdocsHeader.esm.js'; import { simplifyMkdocsFooter } from '../../transformers/simplifyMkdocsFooter.esm.js'; import { onCssReady } from '../../transformers/onCssReady.esm.js'; import { scrollIntoNavigation } from '../../transformers/scrollIntoNavigation.esm.js'; import { transform } from '../../transformers/transformer.esm.js'; import { handleMetaRedirects } from '../../transformers/handleMetaRedirects.esm.js'; import { addNavLinkKeyboardToggle } from '../../transformers/addNavLinkKeyboardToggle.esm.js'; import { useNavigateUrl } from './useNavigateUrl.esm.js'; import { useParams, useLocation, useNavigate } from 'react-router-dom'; const MOBILE_MEDIA_QUERY = "screen and (max-width: 76.1875em)"; const useInitialRedirect = (defaultPath) => { const location = useLocation(); const navigate = useNavigate(); const { "*": currPath = "" } = useParams(); useLayoutEffect(() => { if (currPath === "" && defaultPath) { navigate(`${location.pathname}${defaultPath}`, { replace: true }); } }, []); }; const useTechDocsReaderDom = (entityRef, defaultPath) => { const navigate = useNavigateUrl(); const theme = useTheme(); const isMobileMedia = useMediaQuery(MOBILE_MEDIA_QUERY); const sanitizerTransformer = useSanitizerTransformer(); const stylesTransformer = useStylesTransformer(); const analytics = useAnalytics(); const techdocsStorageApi = useApi(techdocsStorageApiRef); const scmIntegrationsApi = useApi(scmIntegrationsApiRef); const configApi = useApi(configApiRef); const { state, path, content: rawPage } = useTechDocsReader(); const { "*": currPath = "" } = useParams(); const [dom, setDom] = useState(null); const isStyleLoading = useShadowDomStylesLoading(dom); useInitialRedirect(defaultPath); const updateSidebarPositionAndHeight = useCallback(() => { if (!dom) return; const sidebars = dom.querySelectorAll(".md-sidebar"); sidebars.forEach((element) => { if (isMobileMedia) { element.style.top = "0px"; } else { const page = document?.querySelector(".techdocs-reader-page"); const pageTop = page?.getBoundingClientRect().top ?? 0; let domTop = dom.getBoundingClientRect().top ?? 0; const tabs = dom.querySelector(".md-container > .md-tabs"); const tabsHeight = tabs?.getBoundingClientRect().height ?? 0; if (domTop < pageTop) { domTop = pageTop; } const scrollbarTopPx = Math.max(domTop, 0) + tabsHeight; element.style.top = `${scrollbarTopPx}px`; const footer = dom.querySelector(".md-container > .md-footer"); const scrollbarEndPx = footer?.getBoundingClientRect().top ?? window.innerHeight; element.style.height = `${scrollbarEndPx - scrollbarTopPx}px`; } element.style.setProperty("opacity", "1"); }); }, [dom, isMobileMedia]); useEffect(() => { window.addEventListener("resize", updateSidebarPositionAndHeight); window.addEventListener("scroll", updateSidebarPositionAndHeight, true); return () => { window.removeEventListener("resize", updateSidebarPositionAndHeight); window.removeEventListener( "scroll", updateSidebarPositionAndHeight, true ); }; }, [dom, updateSidebarPositionAndHeight]); const updateFooterWidth = useCallback(() => { if (!dom) return; const footer = dom.querySelector(".md-footer"); if (footer) { footer.style.width = `${dom.getBoundingClientRect().width}px`; } }, [dom]); useEffect(() => { window.addEventListener("resize", updateFooterWidth); return () => { window.removeEventListener("resize", updateFooterWidth); }; }, [dom, updateFooterWidth]); useEffect(() => { if (!isStyleLoading) { updateFooterWidth(); updateSidebarPositionAndHeight(); } }, [ state, isStyleLoading, updateFooterWidth, updateSidebarPositionAndHeight ]); const preRender = useCallback( (rawContent, contentPath) => transform(rawContent, [ sanitizerTransformer, addBaseUrl({ techdocsStorageApi, entityId: entityRef, path: contentPath }), rewriteDocLinks(), addSidebarToggle(), removeMkdocsHeader(), simplifyMkdocsFooter(), addGitFeedbackLink(scmIntegrationsApi), stylesTransformer ]), [ // only add dependencies that are in state or memorized variables to avoid unnecessary calls between re-renders entityRef, scmIntegrationsApi, techdocsStorageApi, sanitizerTransformer, stylesTransformer ] ); const postRender = useCallback( async (transformedElement) => transform(transformedElement, [ handleMetaRedirects(navigate, entityRef.name), scrollIntoNavigation(), copyToClipboard(theme), addLinkClickListener({ baseUrl: configApi.getOptionalString("app.baseUrl") || window.location.origin, onClick: (event, url) => { const modifierActive = event.ctrlKey || event.metaKey; const parsedUrl = new URL(url); const linkText = event.target?.innerText || url; const to = url.replace(window.location.origin, ""); analytics.captureEvent("click", linkText, { attributes: { to } }); if (parsedUrl.hash) { if (modifierActive) { window.open(url, "_blank"); } else { if (window.location.pathname !== parsedUrl.pathname) { navigate(url); } else { window.history.pushState( null, document.title, parsedUrl.hash ); } transformedElement?.querySelector(`[id="${parsedUrl.hash.slice(1)}"]`)?.scrollIntoView(); transformedElement?.querySelector(`[id="${parsedUrl.hash.slice(1)}"]`)?.querySelector("a, button, [tabindex]")?.focus(); } } else { if (modifierActive) { window.open(url, "_blank"); } else { navigate(url); } } } }), // disable MkDocs drawer toggling ('for' attribute => checkbox mechanism) onCssReady({ onLoading: () => { }, onLoaded: () => { transformedElement.querySelector(".md-nav__title")?.removeAttribute("for"); } }), // hide sidebars until their positions are updated onCssReady({ onLoading: () => { const sidebars = Array.from( transformedElement.querySelectorAll(".md-sidebar") ); sidebars.forEach((element) => { element.style.setProperty("opacity", "0"); }); }, onLoaded: () => { } }), addNavLinkKeyboardToggle() ]), [theme, navigate, analytics, entityRef.name, configApi] ); useEffect(() => { if (!rawPage) return () => { }; let shouldReplaceContent = true; preRender(rawPage, path).then(async (preTransformedDomElement) => { if (!preTransformedDomElement?.innerHTML) { return; } if (!shouldReplaceContent) { return; } if (currPath !== path) { return; } window.scroll({ top: 0 }); const postTransformedDomElement = await postRender( preTransformedDomElement ); setDom(postTransformedDomElement); }); return () => { shouldReplaceContent = false; }; }, [rawPage, currPath, path, preRender, postRender]); return dom; }; export { useInitialRedirect, useTechDocsReaderDom }; //# sourceMappingURL=dom.esm.js.map