Skip to content

Commit 1417279

Browse files
authored
Track click on links for site insights (#2659)
1 parent e4e2f52 commit 1417279

File tree

20 files changed

+322
-111
lines changed

20 files changed

+322
-111
lines changed

.changeset/nine-gorillas-turn.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'gitbook': minor
3+
---
4+
5+
Track clicks on links (header, footer, content) for site insights.

packages/gitbook/src/components/DocumentView/BlockContentRef.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ export async function BlockContentRef(props: BlockProps<DocumentBlockContentRef>
3434
href={resolved.href}
3535
title={resolved.text}
3636
style={style}
37+
insights={{
38+
target: block.data.ref,
39+
position: 'content',
40+
}}
3741
/>
3842
);
3943
}

packages/gitbook/src/components/DocumentView/File.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { tcls } from '@/lib/tailwind';
66
import { BlockProps } from './Block';
77
import { Caption } from './Caption';
88
import { FileIcon } from './FileIcon';
9+
import { Link } from '../primitives';
910

1011
export async function File(props: BlockProps<DocumentBlockFile>) {
1112
const { block, context } = props;
@@ -21,9 +22,13 @@ export async function File(props: BlockProps<DocumentBlockFile>) {
2122

2223
return (
2324
<Caption {...props} wrapperStyle={[]}>
24-
<a
25+
<Link
2526
href={file.downloadURL}
2627
download={file.name}
28+
insights={{
29+
target: block.data.ref,
30+
position: 'content',
31+
}}
2732
className={tcls(
2833
'group/file',
2934
'flex',
@@ -77,7 +82,7 @@ export async function File(props: BlockProps<DocumentBlockFile>) {
7782
{contentType}
7883
</div>
7984
</div>
80-
</a>
85+
</Link>
8186
</Caption>
8287
);
8388
}

packages/gitbook/src/components/DocumentView/InlineLink.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,11 @@ export async function InlineLink(props: InlineProps<DocumentInlineLink>) {
2525
return (
2626
<Link
2727
href={resolved.href}
28-
className="underline underline-offset-2 text-primary hover:text-primary-700 transition-colors "
28+
className="underline underline-offset-2 text-primary hover:text-primary-700 transition-colors"
29+
insights={{
30+
target: inline.data.ref,
31+
position: 'content',
32+
}}
2933
>
3034
<Inlines
3135
context={context}

packages/gitbook/src/components/DocumentView/Mention.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,15 @@ export async function Mention(props: InlineProps<DocumentInlineMention>) {
1515
return null;
1616
}
1717

18-
return <StyledLink href={resolved.href}>{resolved.text}</StyledLink>;
18+
return (
19+
<StyledLink
20+
href={resolved.href}
21+
insights={{
22+
target: inline.data.ref,
23+
position: 'content',
24+
}}
25+
>
26+
{resolved.text}
27+
</StyledLink>
28+
);
1929
}

packages/gitbook/src/components/DocumentView/Table/RecordCard.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { ContentRef, DocumentTableViewCards } from '@gitbook/api';
22
import React from 'react';
33

4+
import { Link } from '@/components/primitives';
45
import { Image } from '@/components/utils';
56
import { ClassValue, tcls } from '@/lib/tailwind';
67

@@ -153,17 +154,21 @@ export async function RecordCard(
153154
'before:dark:ring-light/2',
154155
] as ClassValue;
155156

156-
if (target) {
157+
if (target && targetRef) {
157158
return (
158-
<a
159+
<Link
159160
href={target.href}
160161
className={tcls(style, [
161162
'hover:before:ring-dark/4',
162163
'dark:hover:before:ring-light/4',
163164
])}
165+
insights={{
166+
target: targetRef,
167+
position: 'content',
168+
}}
164169
>
165170
{body}
166-
</a>
171+
</Link>
167172
);
168173
}
169174

packages/gitbook/src/components/DocumentView/Table/RecordColumnValue.tsx

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ContentRef, DocumentBlockTable } from '@gitbook/api';
1+
import { ContentRef, ContentRefUser, DocumentBlockTable } from '@gitbook/api';
22
import { Icon } from '@gitbook/icons';
33
import assertNever from 'assert-never';
44

@@ -148,6 +148,17 @@ export async function RecordColumnValue<Tag extends React.ElementType = 'div'>(
148148
href={ref.href}
149149
target="_blank"
150150
style={['flex', 'flex-row', 'items-center', 'gap-2']}
151+
insights={
152+
ref.file
153+
? {
154+
target: {
155+
kind: 'file',
156+
file: ref.file.id,
157+
},
158+
position: 'content',
159+
}
160+
: undefined
161+
}
151162
>
152163
{contentType === 'image' ? (
153164
<Image
@@ -178,8 +189,9 @@ export async function RecordColumnValue<Tag extends React.ElementType = 'div'>(
178189
</Tag>
179190
);
180191
case 'content-ref': {
181-
const resolved = value
182-
? await context.resolveContentRef(value as ContentRef, {
192+
const contentRef = value ? (value as ContentRef) : null;
193+
const resolved = contentRef
194+
? await context.resolveContentRef(contentRef, {
183195
resolveAnchorText: true,
184196
iconStyle: ['mr-2', 'text-dark/6', 'dark:text-light/6'],
185197
})
@@ -191,26 +203,51 @@ export async function RecordColumnValue<Tag extends React.ElementType = 'div'>(
191203
>
192204
{resolved?.icon ?? null}
193205
{resolved ? (
194-
<StyledLink href={resolved.href}>{resolved.text}</StyledLink>
206+
<StyledLink
207+
href={resolved.href}
208+
insights={
209+
contentRef
210+
? {
211+
target: contentRef,
212+
position: 'content',
213+
}
214+
: undefined
215+
}
216+
>
217+
{resolved.text}
218+
</StyledLink>
195219
) : null}
196220
</Tag>
197221
);
198222
}
199223
case 'users': {
200224
const resolved = await Promise.all(
201-
(value as string[]).map((userId) =>
202-
context.resolveContentRef({
225+
(value as string[]).map(async (userId) => {
226+
const contentRef: ContentRefUser = {
203227
kind: 'user',
204228
user: userId,
205-
}),
206-
),
229+
};
230+
const resolved = await context.resolveContentRef(contentRef);
231+
if (!resolved) {
232+
return null;
233+
}
234+
235+
return [contentRef, resolved] as const;
236+
}),
207237
);
208238

209239
return (
210240
<Tag className={tcls('text-base')} aria-labelledby={ariaLabelledBy}>
211-
{resolved.filter(filterOutNullable).map((file, index) => (
212-
<StyledLink key={index} href={file.href}>
213-
{file.text}
241+
{resolved.filter(filterOutNullable).map(([contentRef, resolved], index) => (
242+
<StyledLink
243+
key={index}
244+
href={resolved.href}
245+
insights={{
246+
target: contentRef,
247+
position: 'content',
248+
}}
249+
>
250+
{resolved.text}
214251
</StyledLink>
215252
))}
216253
</Tag>

packages/gitbook/src/components/Footer/FooterLinksGroup.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ async function FooterLink(props: { link: CustomizationContentLink; context: Cont
4040
'dark:text-light/8',
4141
'dark:hover:text-light/9',
4242
)}
43+
insights={{
44+
target: link.to,
45+
position: 'footer',
46+
}}
4347
>
4448
{link.title}
4549
</Link>

packages/gitbook/src/components/Header/Dropdown.tsx

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { DetailedHTMLProps, HTMLAttributes, useId } from 'react';
33

44
import { ClassValue, tcls } from '@/lib/tailwind';
55

6-
import { Link } from '../primitives';
6+
import { Link, LinkInsightsProps } from '../primitives';
77

88
export type DropdownButtonProps<E extends HTMLElement = HTMLElement> = Omit<
99
Partial<DetailedHTMLProps<HTMLAttributes<E>, E>>,
@@ -110,19 +110,22 @@ export function DropdownMenu(props: { children: React.ReactNode }) {
110110
/**
111111
* Menu item in a dropdown.
112112
*/
113-
export function DropdownMenuItem(props: {
114-
href: string | null;
115-
active?: boolean;
116-
className?: ClassValue;
117-
children: React.ReactNode;
118-
}) {
119-
const { children, active = false, href, className } = props;
113+
export function DropdownMenuItem(
114+
props: {
115+
href: string | null;
116+
active?: boolean;
117+
className?: ClassValue;
118+
children: React.ReactNode;
119+
} & LinkInsightsProps,
120+
) {
121+
const { children, active = false, href, className, insights } = props;
120122

121123
if (href) {
122124
return (
123125
<Link
124126
href={href}
125127
prefetch={false}
128+
insights={insights}
126129
className={tcls(
127130
'px-3 py-1 text-sm rounded straight-corners:rounded-sm',
128131
active ? 'bg-primary/3 dark:bg-light/2 text-primary-600' : null,

packages/gitbook/src/components/Header/HeaderLink.tsx

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
CustomizationHeaderPreset,
55
SiteCustomizationSettings,
66
CustomizationHeaderItem,
7+
ContentRef,
78
} from '@gitbook/api';
89
import assertNever from 'assert-never';
910

@@ -35,7 +36,7 @@ export async function HeaderLink(props: {
3536
<Dropdown
3637
className="shrink"
3738
button={(buttonProps) => {
38-
if (!target) {
39+
if (!target || !link.to) {
3940
return (
4041
<HeaderItemDropdown
4142
{...buttonProps}
@@ -47,6 +48,7 @@ export async function HeaderLink(props: {
4748
return (
4849
<HeaderLinkNavItem
4950
{...buttonProps}
51+
linkTarget={link.to}
5052
linkStyle={linkStyle}
5153
headerPreset={headerPreset}
5254
title={link.title}
@@ -65,12 +67,13 @@ export async function HeaderLink(props: {
6567
);
6668
}
6769

68-
if (!target) {
70+
if (!target || !link.to) {
6971
return null;
7072
}
7173

7274
return (
7375
<HeaderLinkNavItem
76+
linkTarget={link.to}
7477
linkStyle={linkStyle}
7578
headerPreset={headerPreset}
7679
title={link.title}
@@ -81,6 +84,7 @@ export async function HeaderLink(props: {
8184
}
8285

8386
export type HeaderLinkNavItemProps = {
87+
linkTarget: ContentRef;
8488
linkStyle: NonNullable<CustomizationHeaderItem['style']>;
8589
headerPreset: CustomizationHeaderPreset;
8690
title: string;
@@ -89,14 +93,15 @@ export type HeaderLinkNavItemProps = {
8993
} & DropdownButtonProps<HTMLElement>;
9094

9195
function HeaderLinkNavItem(props: HeaderLinkNavItemProps) {
92-
switch (props.linkStyle) {
96+
const { linkStyle, ...rest } = props;
97+
switch (linkStyle) {
9398
case 'button-secondary':
9499
case 'button-primary':
95-
return <HeaderItemButton {...props} linkStyle={props.linkStyle} />;
100+
return <HeaderItemButton {...rest} linkStyle={linkStyle} />;
96101
case 'link':
97-
return <HeaderItemLink {...props} />;
102+
return <HeaderItemLink {...rest} />;
98103
default:
99-
assertNever(props.linkStyle);
104+
assertNever(linkStyle);
100105
}
101106
}
102107

@@ -105,7 +110,7 @@ function HeaderItemButton(
105110
linkStyle: 'button-secondary' | 'button-primary';
106111
},
107112
) {
108-
const { linkStyle, headerPreset, title, href, isDropdown, ...rest } = props;
113+
const { linkTarget, linkStyle, headerPreset, title, href, isDropdown, ...rest } = props;
109114
const variant = (() => {
110115
switch (linkStyle) {
111116
case 'button-secondary':
@@ -139,6 +144,10 @@ function HeaderItemButton(
139144
),
140145
}[linkStyle],
141146
)}
147+
insights={{
148+
target: linkTarget,
149+
position: 'header',
150+
}}
142151
{...rest}
143152
>
144153
{title}
@@ -158,10 +167,18 @@ function getHeaderLinkClassName(props: { headerPreset: CustomizationHeaderPreset
158167
);
159168
}
160169

161-
function HeaderItemLink(props: HeaderLinkNavItemProps) {
162-
const { headerPreset, title, isDropdown, href, ...rest } = props;
170+
function HeaderItemLink(props: Omit<HeaderLinkNavItemProps, 'linkStyle'>) {
171+
const { linkTarget, headerPreset, title, isDropdown, href, ...rest } = props;
163172
return (
164-
<Link href={href} className={getHeaderLinkClassName({ headerPreset })} {...rest}>
173+
<Link
174+
href={href}
175+
className={getHeaderLinkClassName({ headerPreset })}
176+
insights={{
177+
target: linkTarget,
178+
position: 'header',
179+
}}
180+
{...rest}
181+
>
165182
<span className="truncate min-w-0">{title}</span>
166183
{isDropdown ? <DropdownChevron /> : null}
167184
</Link>
@@ -198,5 +215,9 @@ async function SubHeaderLink(props: {
198215
return null;
199216
}
200217

201-
return <DropdownMenuItem href={target.href}>{link.title}</DropdownMenuItem>;
218+
return (
219+
<DropdownMenuItem href={target.href} insights={{ target: link.to, position: 'header' }}>
220+
{link.title}
221+
</DropdownMenuItem>
222+
);
202223
}

0 commit comments

Comments
 (0)