Skip to content

Commit b1b1963

Browse files
aaazzamclaude
andauthored
Add docs content engagement tracking (#21426)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 48105c0 commit b1b1963

File tree

1 file changed

+162
-10
lines changed

1 file changed

+162
-10
lines changed

docs/script.js

Lines changed: 162 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -222,18 +222,165 @@ function loadAmplitude() {
222222
})
223223
}
224224

225+
let previousPath = null
226+
let pageEnteredAt = Date.now()
227+
let scrollThresholdsFired = new Set()
228+
let dwellFiredForPath = null
229+
let scrollRafPending = false
230+
let contentEl = null
231+
232+
function elapsedSeconds() {
233+
return Math.round((Date.now() - pageEnteredAt) / 1000)
234+
}
235+
236+
function maxScrollDepth() {
237+
return scrollThresholdsFired.size ? Math.max(...scrollThresholdsFired) : 0
238+
}
239+
225240
function trackPageView() {
226-
amplitude.track(
227-
'Page View: Docs New',
228-
{
229-
'url': window.location.href,
230-
'title': document.title,
231-
'referrer': document.referrer,
232-
'path': window.location.pathname,
233-
'source': 'docs',
234-
'source_detail': '3.x'
241+
trackDwell()
242+
243+
const currentPath = window.location.pathname
244+
const props = {
245+
'url': window.location.href,
246+
'title': document.title,
247+
'referrer': document.referrer,
248+
'path': currentPath,
249+
'source': 'docs',
250+
'source_detail': '3.x'
251+
}
252+
if (previousPath) {
253+
props['from_path'] = previousPath
254+
}
255+
amplitude.track('Page View: Docs New', props)
256+
previousPath = currentPath
257+
pageEnteredAt = Date.now()
258+
scrollThresholdsFired = new Set()
259+
dwellFiredForPath = null
260+
contentEl = null
261+
window.removeEventListener('scroll', onScroll)
262+
window.addEventListener('scroll', onScroll, { passive: true })
263+
}
264+
265+
function getScrollPercent() {
266+
if (!contentEl) contentEl = document.getElementById('content-area')
267+
if (!contentEl) return 0
268+
const rect = contentEl.getBoundingClientRect()
269+
if (rect.height <= 0) return 0
270+
// How much of the content bottom has been scrolled into the viewport
271+
const remaining = rect.bottom - window.innerHeight
272+
if (remaining <= 0) return 100
273+
return Math.round(((rect.height - remaining) / rect.height) * 100)
274+
}
275+
276+
function onScroll() {
277+
if (scrollRafPending || scrollThresholdsFired.size >= 4) return
278+
scrollRafPending = true
279+
requestAnimationFrame(() => {
280+
scrollRafPending = false
281+
const pct = getScrollPercent()
282+
for (const t of [25, 50, 75, 100]) {
283+
if (pct >= t && !scrollThresholdsFired.has(t)) {
284+
scrollThresholdsFired.add(t)
285+
amplitude.track('docs_scroll_depth', {
286+
path: window.location.pathname,
287+
scroll_depth: t,
288+
time_on_page_s: elapsedSeconds()
289+
})
290+
}
235291
}
236-
)
292+
if (scrollThresholdsFired.size >= 4) {
293+
window.removeEventListener('scroll', onScroll)
294+
}
295+
})
296+
}
297+
298+
function trackDwell() {
299+
const path = window.location.pathname
300+
if (dwellFiredForPath === path) return
301+
const seconds = elapsedSeconds()
302+
if (seconds < 2) return
303+
dwellFiredForPath = path
304+
amplitude.track('docs_dwell_time', {
305+
path: path,
306+
duration_s: seconds,
307+
scroll_depth: maxScrollDepth()
308+
})
309+
amplitude.flush()
310+
}
311+
312+
function initClickTracking() {
313+
let searchDebounceTimer = null
314+
315+
document.addEventListener('input', (e) => {
316+
if (!e.target.matches('[cmdk-input]')) return
317+
const path = window.location.pathname
318+
clearTimeout(searchDebounceTimer)
319+
searchDebounceTimer = setTimeout(() => {
320+
const query = e.target.value.trim()
321+
if (query.length >= 2) {
322+
amplitude.track('docs_search', {
323+
query: query.slice(0, 200),
324+
path: path
325+
})
326+
}
327+
}, 1000)
328+
})
329+
330+
document.addEventListener('click', (e) => {
331+
// Code copy button
332+
const copyBtn = e.target.closest('[data-testid="copy-code-button"]')
333+
if (copyBtn) {
334+
const block = copyBtn.closest('[data-component-part="code-block-root"]')
335+
const header = block ? block.querySelector('[data-component-part="code-block-header-filename"]') : null
336+
amplitude.track('docs_code_copied', {
337+
path: window.location.pathname,
338+
code_title: header ? header.textContent.trim() : null,
339+
code_block_index: block
340+
? Array.from(document.querySelectorAll('[data-component-part="code-block-root"]')).indexOf(block)
341+
: null
342+
})
343+
return
344+
}
345+
346+
// Search result click (cmdk)
347+
const item = e.target.closest('[cmdk-item]')
348+
if (item) {
349+
const input = document.querySelector('[cmdk-input]')
350+
amplitude.track('docs_search_result_clicked', {
351+
query: input ? input.value.trim().slice(0, 200) : null,
352+
result_text: item.textContent.trim().slice(0, 200),
353+
path: window.location.pathname
354+
})
355+
return
356+
}
357+
358+
// AI assistant send button
359+
const sendBtn = e.target.closest('.chat-assistant-send-button')
360+
if (sendBtn) {
361+
const textarea = document.getElementById('chat-assistant-textarea')
362+
if (textarea && textarea.value.trim()) {
363+
amplitude.track('docs_ai_search', {
364+
query: textarea.value.trim().slice(0, 200),
365+
path: window.location.pathname
366+
})
367+
}
368+
return
369+
}
370+
371+
// Search bar open
372+
if (e.target.closest('#search-bar-entry, #search-bar-entry-mobile')) {
373+
amplitude.track('docs_search_opened', { path: window.location.pathname })
374+
return
375+
}
376+
377+
// Feedback thumbs
378+
if (e.target.closest('#feedback-thumbs-up')) {
379+
amplitude.track('docs_feedback', { path: window.location.pathname, rating: 'positive' })
380+
} else if (e.target.closest('#feedback-thumbs-down')) {
381+
amplitude.track('docs_feedback', { path: window.location.pathname, rating: 'negative' })
382+
}
383+
})
237384
}
238385

239386
const init = () => {
@@ -256,6 +403,11 @@ function loadAmplitude() {
256403

257404
setTimeout(addDeviceIdToAppLinks)
258405
observeRouteChanges(trackPageView)
406+
document.addEventListener('visibilitychange', () => {
407+
if (document.visibilityState === 'hidden') trackDwell()
408+
})
409+
window.addEventListener('beforeunload', trackDwell)
410+
initClickTracking()
259411
}
260412

261413
const url = 'https://cdn.amplitude.com/libs/analytics-browser-2.8.1-min.js.gz'

0 commit comments

Comments
 (0)