diff --git a/src/components/SideBar.tsx b/src/components/SideBar.tsx index 8d23b6a..ebee564 100644 --- a/src/components/SideBar.tsx +++ b/src/components/SideBar.tsx @@ -63,7 +63,7 @@ export function SideBar() { : intelligentToggle, ) const shouldShow = $shouldShow.value - React.useEffect(() => { + const toggleBodyIndent = React.useCallback(() => { if (sidebarToggleMode === 'persistent') { DOMHelper.setBodyIndent(shouldShow) } else { @@ -75,6 +75,12 @@ export function SideBar() { } }, [shouldShow, sidebarToggleMode]) + React.useEffect(() => { + toggleBodyIndent() + }, [toggleBodyIndent]) + + useOnPJAXDone(toggleBodyIndent) + // Save expand state on toggle if auto expand is off React.useEffect(() => { if (intelligentToggle !== null) { diff --git a/src/content.tsx b/src/content.tsx index cc11087..71cb78e 100644 --- a/src/content.tsx +++ b/src/content.tsx @@ -4,6 +4,11 @@ import { addMiddleware } from 'driver/connect' import { platform } from 'platforms' import * as React from 'react' import * as ReactDOM from 'react-dom' +import { + insertLogoMountPoint, + insertSideBarMountPoint, + persistGitakoElements +} from 'utils/DOMHelper' import './content.scss' if (platform.resolvePartialMetaData()) { @@ -18,8 +23,9 @@ if (platform.resolvePartialMetaData()) { async function init() { await injectStyles(browser.runtime.getURL('content.css')) - const SideBarElement = document.createElement('div') - document.body.appendChild(SideBarElement) + const SideBarElement = insertSideBarMountPoint() + const logoElement = insertLogoMountPoint() + persistGitakoElements(SideBarElement, logoElement) ReactDOM.render(, SideBarElement) } diff --git a/src/platforms/GitHub/index.ts b/src/platforms/GitHub/index.ts index c9a8d15..c7d5592 100644 --- a/src/platforms/GitHub/index.ts +++ b/src/platforms/GitHub/index.ts @@ -112,6 +112,7 @@ const pathSHAMap = new Map() const pjaxContainerSelector = ['#repo-content-pjax-container', '#js-repo-pjax-container'].find( selector => document.querySelector(selector), ) +const turboContainerId = 'repo-content-turbo-frame' export const GitHub: Platform = { isEnterprise, @@ -213,6 +214,7 @@ export const GitHub: Platform = { if (configRef.pjaxMode === 'native' && (!options?.node || options.node.type === 'blob')) return { 'data-pjax': pjaxContainerSelector, + 'data-turbo-frame': turboContainerId, onClick() { /* Overwriting default onClick */ }, diff --git a/src/utils/DOMHelper.ts b/src/utils/DOMHelper.ts index b369b46..3b7eff6 100644 --- a/src/utils/DOMHelper.ts +++ b/src/utils/DOMHelper.ts @@ -36,17 +36,17 @@ export function setBodyIndent(shouldShowGitako: boolean) { } export function $(selector: string): HTMLElement | null -export function $(selector: string, existCallback: (element: HTMLElement) => T1): T1 +export function $(selector: string, existCallback: (element: HTMLElement) => T1): T1 | null export function $( selector: string, existCallback: (element: HTMLElement) => T1, otherwise: () => T2, -): T1 | T2 +): T1 | T2 | null export function $( selector: string, existCallback: undefined | null, otherwise: () => T2, -): HTMLElement | null | T2 +): HTMLElement | T2 export function $(selector: string, existCallback?: any, otherwise?: any) { const element = document.querySelector(selector) if (element) { @@ -55,6 +55,15 @@ export function $(selector: string, existCallback?: any, otherwise?: any) { return otherwise ? otherwise() : null } +export function insertSideBarMountPoint() { + const mountPointID = 'gitako-mount-point-wrapper' + const sideBarElement = document.createElement('div') + sideBarElement.setAttribute('data-turbo-permanent', '') + sideBarElement.setAttribute('id', mountPointID) + document.body.appendChild(sideBarElement) + return sideBarElement +} + /** * add the logo element into DOM */ @@ -64,6 +73,7 @@ export function insertLogoMountPoint() { return $(logoSelector, undefined, function createLogoMountPoint() { const logoMountElement = document.createElement('div') logoMountElement.setAttribute('id', logoID) + logoMountElement.setAttribute('data-turbo-permanent', '') document.body.appendChild(logoMountElement) return logoMountElement }) @@ -142,3 +152,46 @@ export function setCSSVariable(name: string, value: string | undefined, element: if (value === undefined) element.style.removeProperty(name) else element.style.setProperty(name, value) } + +/** + * Unlike the good-old-PJAX-time, now GitHub replaces whole body element after redirecting using turbo. + * If move Gitako mount point from `body` to `html`, Gitako style would break because it inherits style from GitHub body. + * The temporary solution is recovery Gitako elements once the body is removed. + */ +export function persistGitakoElements(SideBarElement: HTMLElement, logoElement: HTMLElement) { + const observer = new MutationObserver(mutations => { + for (const { addedNodes, removedNodes } of mutations) { + const [addedBody, removedBody] = [addedNodes, removedNodes].map(findBodyElement) + if (addedBody && removedBody) { + // hard-coded list due to limited time + // TODO: refactor in a better practice + + // migrate gitako attributes, e.g. class + const propertiesNeedToMigrate = ['--gitako-width'] + for (const property of propertiesNeedToMigrate) { + const oldValue = removedBody.style.getPropertyValue(property) + if (oldValue) addedBody.style.setProperty(property, oldValue) + } + const cssClassesNeedToMigrate = ['with-gitako-spacing'] + for (const cssClass of cssClassesNeedToMigrate) { + if (removedBody.classList.contains(cssClass)) addedBody.classList.add(cssClass) + } + + // move gitako elements + if (!addedBody.contains(SideBarElement)) addedBody.appendChild(SideBarElement) + if (removedBody.contains(SideBarElement)) removedBody.removeChild(SideBarElement) + if (!addedBody.contains(logoElement)) addedBody.appendChild(logoElement) + if (removedBody.contains(logoElement)) removedBody.removeChild(logoElement) + } + } + + function findBodyElement(addedNodes: NodeList) { + return Array.from(addedNodes).find(addedNode => addedNode instanceof HTMLBodyElement) as + | HTMLBodyElement + | undefined + } + }) + observer.observe(document.documentElement, { + childList: true, + }) +} diff --git a/src/utils/hooks/usePJAX.ts b/src/utils/hooks/usePJAX.ts index ba06191..80e37bc 100644 --- a/src/utils/hooks/usePJAX.ts +++ b/src/utils/hooks/usePJAX.ts @@ -4,6 +4,8 @@ import { platform } from 'platforms' import * as React from 'react' import { useEvent } from 'react-use' +// TODO: rename PJAX + const config: Config = { areas: [ // github @@ -53,7 +55,10 @@ export const loadWithPJAX = (url: string, element: HTMLElement) => { } export function useOnPJAXDone(callback: () => void) { - useEvent('pjax:end', callback, document) + useEvent('pjax:end', callback, document) // legacy support + // 'turbo:render' should be the best timing but GitHub has attached a mutation observer on body to block that + // TODO: fire at turbo:render + useEvent('turbo:load', callback, document) } export function useRedirectedEvents(