diff --git a/content/300-accelerate/index.mdx b/content/300-accelerate/index.mdx index 10ad80c7bb..8a0c35304a 100644 --- a/content/300-accelerate/index.mdx +++ b/content/300-accelerate/index.mdx @@ -10,7 +10,6 @@ pagination_next: 'accelerate/getting-started' import { Bolt, - BorderBox, BoxTitle, Database, Grid, diff --git a/content/700-optimize/index.mdx b/content/700-optimize/index.mdx index eba5f3aa51..cd3c4a7e7c 100644 --- a/content/700-optimize/index.mdx +++ b/content/700-optimize/index.mdx @@ -10,7 +10,6 @@ pagination_next: 'optimize/getting-started' import { Bolt, - BorderBox, BoxTitle, Database, Grid, diff --git a/functions/_middleware.ts b/functions/_middleware.ts index cfd258bc87..9054f840db 100644 --- a/functions/_middleware.ts +++ b/functions/_middleware.ts @@ -95,7 +95,6 @@ export const onRequest: PagesFunction = async (context) => { if (response.ok) { // Check what content type we actually got const actualContentType = response.headers.get('content-type'); - console.log(`Fetched ${markdownPath}, got content-type: ${actualContentType}`); return new Response(response.body, { status: 200, diff --git a/src/hooks/useUTMParams.ts b/src/hooks/useUTMParams.ts new file mode 100644 index 0000000000..a2a0c12917 --- /dev/null +++ b/src/hooks/useUTMParams.ts @@ -0,0 +1,34 @@ +import { useEffect, useState } from 'react'; + +export const useUTMParams = (): string => { + const [utmParams, setUTMParams] = useState(''); + + useEffect(() => { + if (typeof window !== 'undefined') { + const updateUTMParams = () => { + const storedParams = sessionStorage.getItem('utm_params'); + setUTMParams(storedParams || ''); + }; + + // Initial load + updateUTMParams(); + + const handleStorageChange = (e: StorageEvent) => { + if (e.key === 'utm_params') { + updateUTMParams(); + } + }; + + window.addEventListener('storage', handleStorageChange); + + const interval = setInterval(updateUTMParams, 500); + + return () => { + window.removeEventListener('storage', handleStorageChange); + clearInterval(interval); + }; + } + }, []); + + return utmParams; +}; \ No newline at end of file diff --git a/src/theme/Logo/index.tsx b/src/theme/Logo/index.tsx new file mode 100644 index 0000000000..d441327f26 --- /dev/null +++ b/src/theme/Logo/index.tsx @@ -0,0 +1,80 @@ +import React, {type ReactNode} from 'react'; +import Link from '@docusaurus/Link'; +import useBaseUrl from '@docusaurus/useBaseUrl'; +import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; +import {useThemeConfig, type NavbarLogo} from '@docusaurus/theme-common'; +import ThemedImage from '@theme/ThemedImage'; +import type {Props} from '@theme/Logo'; +import { useUTMParams } from '@site/src/hooks/useUTMParams'; + +function LogoThemedImage({ + logo, + alt, + imageClassName, +}: { + logo: NavbarLogo; + alt: string; + imageClassName?: string; +}) { + const sources = { + light: useBaseUrl(logo.src), + dark: useBaseUrl(logo.srcDark || logo.src), + }; + const themedImage = ( + + ); + + return imageClassName ? ( +
{themedImage}
+ ) : ( + themedImage + ); +} + +export default function Logo(props: Props): ReactNode { + const { + siteConfig: {title}, + } = useDocusaurusContext(); + const { + navbar: {title: navbarTitle, logo}, + } = useThemeConfig(); + + const {imageClassName, titleClassName, ...propsRest} = props; + const logoLink = useBaseUrl(logo?.href || '/'); + const utmParams = useUTMParams(); + + const appendUtmParams = (url: string): string => { + if (!utmParams) { + return url; + } + const separator = url.includes('?') ? '&' : '?'; + const result = `${url}${separator}${utmParams}`; + return result; + }; + const fallbackAlt = navbarTitle ? '' : title; + + const alt = logo?.alt ?? fallbackAlt; + + return ( + + {logo && ( + + )} + {navbarTitle != null && {navbarTitle}} + + ); +} diff --git a/src/theme/Navbar/Content/index.tsx b/src/theme/Navbar/Content/index.tsx index 84dd491c34..6e951e21a0 100644 --- a/src/theme/Navbar/Content/index.tsx +++ b/src/theme/Navbar/Content/index.tsx @@ -11,6 +11,7 @@ import React, { type ReactNode } from 'react'; import styles from './styles.module.css'; import Link from '@docusaurus/Link'; import useBaseUrl from '@docusaurus/useBaseUrl'; +import { useUTMParams } from '@site/src/hooks/useUTMParams'; function useNavbarItems() { // TODO temporary casting until ThemeConfig type is improved @@ -58,12 +59,20 @@ function NavbarContentLayout({ export default function NavbarContent(): ReactNode { const mobileSidebar = useNavbarMobileSidebar(); + const utmParams = useUTMParams(); const items = useNavbarItems(); const [leftItems, rightItems] = splitNavbarItems(items); const searchBarItem = items.find((item) => item.type === 'search'); const baseUrl = useBaseUrl("/"); + + // Helper function to append UTM params to URL + const appendUtmParams = (url: string): string => { + if (!utmParams) return url; + const separator = url.includes('?') ? '&' : '?'; + return `${url}${separator}${utmParams}`; + }; return ( } / - docs + docs } middle={ diff --git a/src/theme/NavbarItem/NavbarNavLink.tsx b/src/theme/NavbarItem/NavbarNavLink.tsx index b8709c173c..32c3d81a5b 100644 --- a/src/theme/NavbarItem/NavbarNavLink.tsx +++ b/src/theme/NavbarItem/NavbarNavLink.tsx @@ -6,6 +6,7 @@ import {isRegexpStringMatch} from '@docusaurus/theme-common'; import IconExternalLink from '@theme/Icon/ExternalLink'; import type {Props} from '@theme/NavbarItem/NavbarNavLink'; import { Icon } from '@site/src/components/Icon'; +import { useUTMParams } from '@site/src/hooks/useUTMParams'; type CustomProps = Props & { @@ -31,7 +32,23 @@ export default function NavbarNavLink({ const normalizedHref = useBaseUrl(href, {forcePrependBaseUrl: true}); const isExternalLink = label && href && !isInternalUrl(href); - // Link content is set through html XOR label + const utmParams = useUTMParams(); + + const appendUtmParams = (url: string): string => { + if (!utmParams) { + return url; + } + + const [baseUrl, existingQuery] = url.split('?'); + if (existingQuery) { + const result = `${baseUrl}?${existingQuery}&${utmParams}`; + return result; + } else { + const result = `${baseUrl}?${utmParams}`; + return result; + } + }; + const linkContentProps = html ? {dangerouslySetInnerHTML: {__html: html}} : { @@ -54,18 +71,33 @@ export default function NavbarNavLink({ }; if (href) { + if (isExternalLink) { + return ( + + ); + } + + const finalHref = prependBaseUrlToHref ? normalizedHref : href; + const urlWithUtms = appendUtmParams(finalHref); + return ( ); } + const urlWithUtms = appendUtmParams(toUrl); + return ( diff --git a/src/utils/useUTMPersistenceDocs.ts b/src/utils/useUTMPersistenceDocs.ts index 3adecc5625..6d7f4f9e8f 100644 --- a/src/utils/useUTMPersistenceDocs.ts +++ b/src/utils/useUTMPersistenceDocs.ts @@ -23,7 +23,6 @@ export const useUTMPersistenceDocs = () => { }; useEffect(() => { - // Skip initial render if (previousSearch.current === '') { previousSearch.current = location.search; if (hasUTMParams(location.search)) { @@ -35,22 +34,18 @@ export const useUTMPersistenceDocs = () => { const hadUTMs = hasUTMParams(previousSearch.current); const hasUTMs = hasUTMParams(location.search); - // Detect manual removal - if (hadUTMs && !hasUTMs && location.pathname === previousSearch.current.split('?')[0]) { + if (hadUTMs && !hasUTMs) { isManualRemoval.current = true; sessionStorage.removeItem('utm_params'); - console.log('Manual removal detected - UTMs cleared'); } - // Save new UTMs if they exist else if (hasUTMs) { isManualRemoval.current = false; sessionStorage.setItem('utm_params', getUTMParams(location.search)); } - // Restore UTMs if they're missing and weren't manually removed else if (!isManualRemoval.current) { const storedParams = sessionStorage.getItem('utm_params'); if (storedParams) { - const newSearch = storedParams ? `?${storedParams}` : ''; + const newSearch = `?${storedParams}`; if (location.search !== newSearch) { history.replace({ pathname: location.pathname, @@ -62,4 +57,47 @@ export const useUTMPersistenceDocs = () => { previousSearch.current = location.search; }, [location, history]); + + useEffect(() => { + const handlePopState = () => { + setTimeout(() => { + const currentHasUTMs = hasUTMParams(window.location.search); + const storedParams = sessionStorage.getItem('utm_params'); + + if (!currentHasUTMs && storedParams) { + sessionStorage.removeItem('utm_params'); + isManualRemoval.current = true; + } + }, 50); + }; + + const handleHashChange = () => { + const currentHasUTMs = hasUTMParams(window.location.search); + const storedParams = sessionStorage.getItem('utm_params'); + + if (!currentHasUTMs && storedParams) { + sessionStorage.removeItem('utm_params'); + isManualRemoval.current = true; + } + }; + + window.addEventListener('popstate', handlePopState); + window.addEventListener('hashchange', handleHashChange); + + const intervalId = setInterval(() => { + const currentHasUTMs = hasUTMParams(window.location.search); + const storedParams = sessionStorage.getItem('utm_params'); + + if (!currentHasUTMs && storedParams && !isManualRemoval.current) { + sessionStorage.removeItem('utm_params'); + isManualRemoval.current = true; + } + }, 1000); + + return () => { + window.removeEventListener('popstate', handlePopState); + window.removeEventListener('hashchange', handleHashChange); + clearInterval(intervalId); + }; + }, []); };