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/Layout/index.tsx b/src/theme/Layout/index.tsx index 8f25b99533..c719cf8ce6 100644 --- a/src/theme/Layout/index.tsx +++ b/src/theme/Layout/index.tsx @@ -22,7 +22,6 @@ export default function Layout(props: Props): ReactNode { children, noFooter, wrapperClassName, - // Not really layout-related, but kept for convenience/retro-compatibility title, description, } = props; @@ -43,15 +42,12 @@ export default function Layout(props: Props): ReactNode { new URLSearchParams(utms).forEach((v, k) => url.searchParams.set(k, v)); a.setAttribute('href', url.toString()); } catch (e) { - // Handle invalid URLs } }); }; - // Initial update updateLinks(document.querySelectorAll('a[href*="console.prisma.io"]')); - // Watch for new links const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { mutation.addedNodes.forEach((node) => { 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..1330cf442a 100644 --- a/src/utils/useUTMPersistenceDocs.ts +++ b/src/utils/useUTMPersistenceDocs.ts @@ -1,65 +1,41 @@ import { useHistory, useLocation } from '@docusaurus/router'; import { useEffect, useRef } from 'react'; -const hasUTMParams = (search: string) => { +const UTM_KEYS = ['utm_source', 'utm_medium', 'utm_campaign'] as const; +const STORAGE_KEY = 'utm_params'; + +const extractUTMParams = (search: string): string => { const params = new URLSearchParams(search); - return ['utm_source', 'utm_medium', 'utm_campaign'].some(p => params.has(p)); + const utmParams = new URLSearchParams(); + UTM_KEYS.forEach(key => { + const value = params.get(key); + if (value) utmParams.set(key, value); + }); + return utmParams.toString(); }; export const useUTMPersistenceDocs = () => { const location = useLocation(); const history = useHistory(); - const isManualRemoval = useRef(false); - const previousSearch = useRef(''); - - const getUTMParams = (search: string) => { - const params = new URLSearchParams(search); - const utmParams = new URLSearchParams(); - ['utm_source', 'utm_medium', 'utm_campaign'].forEach(param => { - const value = params.get(param); - if (value) utmParams.set(param, value); - }); - return utmParams.toString(); - }; + const prevPathname = useRef(location.pathname); useEffect(() => { - // Skip initial render - if (previousSearch.current === '') { - previousSearch.current = location.search; - if (hasUTMParams(location.search)) { - sessionStorage.setItem('utm_params', getUTMParams(location.search)); - } - return; - } + const currentUTMs = extractUTMParams(location.search); + const storedUTMs = sessionStorage.getItem(STORAGE_KEY); + const isNavigation = prevPathname.current !== location.pathname; - const hadUTMs = hasUTMParams(previousSearch.current); - const hasUTMs = hasUTMParams(location.search); - - // Detect manual removal - if (hadUTMs && !hasUTMs && location.pathname === previousSearch.current.split('?')[0]) { - 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}` : ''; - if (location.search !== newSearch) { - history.replace({ - pathname: location.pathname, - search: newSearch, - }); - } - } + if (currentUTMs) { + sessionStorage.setItem(STORAGE_KEY, currentUTMs); + } else if (storedUTMs && isNavigation) { + history.replace({ + pathname: location.pathname, + search: `?${storedUTMs}`, + hash: location.hash, + }); + } else if (!currentUTMs && storedUTMs && !isNavigation) { + sessionStorage.removeItem(STORAGE_KEY); } - previousSearch.current = location.search; - }, [location, history]); + prevPathname.current = location.pathname; + }, [location.pathname, location.search, location.hash]); };