Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion content/300-accelerate/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ pagination_next: 'accelerate/getting-started'

import {
Bolt,
BorderBox,
BoxTitle,
Database,
Grid,
Expand Down
1 change: 0 additions & 1 deletion content/700-optimize/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ pagination_next: 'optimize/getting-started'

import {
Bolt,
BorderBox,
BoxTitle,
Database,
Grid,
Expand Down
1 change: 0 additions & 1 deletion functions/_middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,6 @@ export const onRequest: PagesFunction<Env> = 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,
Expand Down
34 changes: 34 additions & 0 deletions src/hooks/useUTMParams.ts
Original file line number Diff line number Diff line change
@@ -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;
};
4 changes: 0 additions & 4 deletions src/theme/Layout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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) => {
Expand Down
80 changes: 80 additions & 0 deletions src/theme/Logo/index.tsx
Original file line number Diff line number Diff line change
@@ -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 = (
<ThemedImage
className={logo.className}
sources={sources}
height={logo.height}
width={logo.width}
alt={alt}
style={logo.style}
/>
);

return imageClassName ? (
<div className={imageClassName}>{themedImage}</div>
) : (
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 (
<Link
to={appendUtmParams(logoLink)}
{...propsRest}
{...(logo?.target && {target: logo.target})}>
{logo && (
<LogoThemedImage
logo={logo}
alt={alt}
imageClassName={imageClassName}
/>
)}
{navbarTitle != null && <b className={titleClassName}>{navbarTitle}</b>}
</Link>
);
}
11 changes: 10 additions & 1 deletion src/theme/Navbar/Content/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 (
<NavbarContentLayout
Expand All @@ -73,7 +82,7 @@ export default function NavbarContent(): ReactNode {
{!mobileSidebar.disabled && <NavbarMobileSidebarToggle />}
<NavbarLogo />
<span className={styles.separator}>/</span>
<Link to={baseUrl} className="logo-link">docs</Link>
<Link to={appendUtmParams(baseUrl)} className="logo-link">docs</Link>
</>
}
middle={
Expand Down
38 changes: 35 additions & 3 deletions src/theme/NavbarItem/NavbarNavLink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 & {
Expand All @@ -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}}
: {
Expand All @@ -54,18 +71,33 @@ export default function NavbarNavLink({
};

if (href) {
if (isExternalLink) {
return (
<Link
href={prependBaseUrlToHref ? normalizedHref : href}
{...props}
{...linkContentProps}
/>
);
}

const finalHref = prependBaseUrlToHref ? normalizedHref : href;
const urlWithUtms = appendUtmParams(finalHref);

return (
<Link
href={prependBaseUrlToHref ? normalizedHref : href}
href={urlWithUtms}
{...props}
{...linkContentProps}
/>
);
}

const urlWithUtms = appendUtmParams(toUrl);

return (
<Link
to={toUrl}
to={urlWithUtms}
isNavLink
{...((activeBasePath || activeBaseRegex) && {
isActive: (_match, location) =>
Expand Down
76 changes: 26 additions & 50 deletions src/utils/useUTMPersistenceDocs.ts
Original file line number Diff line number Diff line change
@@ -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]);
};
Loading