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
3 changes: 3 additions & 0 deletions src/components/CodeEmbed/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ export const CodeEmbed = (props) => {
setPreviewCodeString(codeString);
}
announce("Sketch is running");

// Analytics for per-user (anonymized, no-cookie) conversion: did they run a sketch?
window.fathom.trackEvent(`Run Sketch`);
};

const [previewCodeString, setPreviewCodeString] = useState(codeString);
Expand Down
74 changes: 74 additions & 0 deletions src/components/ReferenceTracker/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { useEffect, useRef } from "preact/hooks";

const TIME_LABELS: Record<number, string> = {
10000: "Focus 10s",
30000: "Focus 30s",
90000: "Focus 90s",
300000: "Focus 5min",
600000: "Focus 10min",
};

const SCROLL_LABELS: Record<number, string> = {
25: "Scroll 25%",
50: "Scroll 50%",
75: "Scroll 75%",
100: "Scroll 100%",
};

const track = (label: string) => {
// Fathom analytics does not use cookies. The data collected
// is aggregate and minimal. The scope is currently only on
// the Reference page, but it can be generalized in the future.
if (window.fathom && window.location.pathname.startsWith("/reference/")) {

@lirenjie95 lirenjie95 Jul 29, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pathname check misses all localized reference pages. Non-default locales are served under /[locale]/reference/...(e.g. /es/reference/p5/line), so pathname.startsWith("/reference/") is false there and none of these events will ever fire on non-English reference pages — which likely skews the data toward English-speaking users. Consider stripping the locale prefix before testing, e.g. with the existing removeLocalePrefix util from @i18n/utils.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the catch!!

window.fathom.trackEvent(label);
}
};

const ReferenceTracker = ({
timeThresholds,
scrollThresholds,
}: {
timeThresholds: number[];
scrollThresholds: number[];
}) => {
const hasFiredTime = useRef<Set<number>>(new Set());
const hasFiredScroll = useRef<Set<number>>(new Set());
const elapsed = useRef(0);

useEffect(() => {
const tick = () => {
if (document.hidden) return;
elapsed.current += 1000;
for (const ms of timeThresholds) {
if (elapsed.current >= ms && !hasFiredTime.current.has(ms)) {
hasFiredTime.current.add(ms);
track(TIME_LABELS[ms]);
}
}
};

const onScroll = () => {
const max = document.documentElement.scrollHeight - document.documentElement.clientHeight;
if (max <= 0) return;
const pct = Math.round((window.scrollY / max) * 100);
for (const t of scrollThresholds) {
if (pct >= t && !hasFiredScroll.current.has(t)) {
hasFiredScroll.current.add(t);
track(SCROLL_LABELS[t]);
}
}
};

const interval = setInterval(tick, 1000);
window.addEventListener("scroll", onScroll, { passive: true });

return () => {
clearInterval(interval);
window.removeEventListener("scroll", onScroll);
};
}, [timeThresholds, scrollThresholds]);

return null;
};

export default ReferenceTracker;
19 changes: 14 additions & 5 deletions src/components/SearchProvider/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,20 @@ const SearchProvider = ({
};

const fuse = new Fuse(flatData, fuseOptions);

const searchResults = fuse
.search(searchTerm)
.map((result) => result.item);

const fuseResults = fuse.search(searchTerm);

const hasExactMatch = fuseResults.some(r => (r.score ?? 0) < 0.1);
if (fuseResults.length > 0 && window.fathom) {
if (hasExactMatch) {
// Only track search term if there is an exact match
window.fathom.trackEvent(`Search ${fuseResults.length} ${searchTerm}`);
} else {
// Otherwise, that a search occurred but without a match
window.fathom.trackEvent(`Search ${fuseResults.length} [FUZZY]`);
}
}

const searchResults = fuseResults.map((result) => result.item);
setResults(searchResults);
})
.catch((error) =>
Expand Down
6 changes: 6 additions & 0 deletions src/layouts/BaseLayout.astro
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import type { CollectionEntry } from "astro:content";
import { render } from "astro:content";
import { getCollectionInLocaleWithFallbacks } from "@pages/_utils";
import { removeLocalePrefix } from "@i18n/utils";
import ReferenceTracker from "@components/ReferenceTracker";

interface Props {
title: string;
Expand Down Expand Up @@ -132,5 +133,10 @@ const headerTopic = topic
</main>
<Footer />
</div>
<ReferenceTracker
client:load
timeThresholds={[10000, 30000, 90000, 300000, 600000]}
scrollThresholds={[25, 50, 75, 100]}
/>
</body>
</html>
5 changes: 5 additions & 0 deletions types/fathom.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
interface Window {
fathom?: {
trackEvent(name: string, attrs?: Record<string, unknown>): void;
};
}