Skip to content

Commit 825bcb9

Browse files
obx-viviengreenrobot
authored andcommitted
Add search-analytics.js
1 parent 25e5aa8 commit 825bcb9

File tree

2 files changed

+133
-62
lines changed

2 files changed

+133
-62
lines changed

docusaurus.config.ts

Lines changed: 48 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -8,81 +8,68 @@ const config: Config = {
88
favicon: 'img/favicon.ico',
99

1010
url: 'https://cpp.objectbox.io',
11-
/*baseUrl: '/',*/
1211
baseUrl: '/',
1312

14-
organizationName: 'objectbox',
13+
organizationName: 'objectbox',
1514
projectName: 'objectbox-c-cpp-docs',
1615

1716
onBrokenLinks: 'throw',
1817
onBrokenMarkdownLinks: 'warn',
1918

20-
i18n: {
21-
defaultLocale: 'en',
22-
locales: ['en'],
23-
},
19+
i18n: { defaultLocale: 'en', locales: ['en'] },
2420

2521
themes: [
2622
[
27-
'@easyops-cn/docusaurus-search-local',
28-
{
29-
hashed: true,
30-
language: ['en'],
31-
highlightSearchTermsOnTargetPage: true,
32-
explicitSearchResultPath: false, // Changed from true - this can cause 404s
33-
indexDocs: true,
34-
indexBlog: false,
35-
indexPages: true,
36-
docsRouteBasePath: '/',
37-
searchResultLimits: 8,
38-
searchResultContextMaxLength: 50,
39-
ignoreFiles: [],
40-
},
23+
'@easyops-cn/docusaurus-search-local',
24+
{
25+
hashed: true,
26+
language: ['en'],
27+
highlightSearchTermsOnTargetPage: true,
28+
explicitSearchResultPath: false,
29+
indexDocs: true,
30+
indexBlog: false,
31+
indexPages: true,
32+
docsRouteBasePath: '/',
33+
searchResultLimits: 8,
34+
searchResultContextMaxLength: 50,
35+
ignoreFiles: [],
36+
},
4137
],
4238
],
4339

44-
45-
presets: [
46-
[
47-
'classic',
48-
{
49-
docs: {
50-
routeBasePath: '/', // serve docs at /
51-
sidebarPath: require.resolve('./sidebars.ts'),
52-
editUrl:
53-
'https://github.com/objectbox/objectbox-c-cpp-docs/blob/main/',
54-
},
55-
// If you don't need a blog, you can disable it:
56-
blog: false,
57-
theme: {
58-
customCss: [
59-
require.resolve('./src/css/custom.css'),
60-
],
61-
},
62-
sitemap: {
63-
lastmod: 'date',
64-
changefreq: 'weekly',
65-
priority: 0.5,
66-
filename: 'sitemap.xml',
67-
},
68-
gtag: {
69-
trackingID: 'G-2LXKBNQ3TW',
70-
anonymizeIP: true,
71-
},
72-
} satisfies Preset.Options,
40+
presets: [
41+
[
42+
'classic',
43+
{
44+
docs: {
45+
routeBasePath: '/',
46+
sidebarPath: require.resolve('./sidebars.ts'),
47+
editUrl: 'https://github.com/objectbox/objectbox-c-cpp-docs/blob/main/',
48+
},
49+
blog: false,
50+
theme: { customCss: [require.resolve('./src/css/custom.css')] },
51+
sitemap: {
52+
lastmod: 'date',
53+
changefreq: 'weekly',
54+
priority: 0.5,
55+
filename: 'sitemap.xml',
56+
},
57+
gtag: {
58+
trackingID: 'G-2LXKBNQ3TW',
59+
anonymizeIP: true,
60+
},
61+
} satisfies Preset.Options,
62+
],
7363
],
74-
],
75-
76-
7764

7865
themeConfig: {
7966
image: 'img/objectbox-social-card.jpg',
8067
navbar: {
8168
title: 'C / C++ Docs',
8269
logo: {
8370
alt: 'ObjectBox Logo',
84-
src: 'img/objectbox-logo.jpg', // Logo for light mode
85-
srcDark: 'img/objectbox-logo-dm.png', // Logo for dark mode
71+
src: 'img/objectbox-logo.jpg',
72+
srcDark: 'img/objectbox-logo-dm.png',
8673
},
8774
items: [
8875
// Right side items in the order you want them to appear:
@@ -94,7 +81,7 @@ const config: Config = {
9481
},
9582
{
9683
href: 'https://sync.objectbox.io',
97-
label: 'Data Sync Docs',
84+
label: 'Data Sync Docs',
9885
position: 'right',
9986
// target: '_self', // ← This prevents external link behavior
10087
},
@@ -123,15 +110,14 @@ const config: Config = {
123110
prism: {
124111
theme: prismThemes.github,
125112
darkTheme: prismThemes.dracula,
126-
additionalLanguages: [
127-
'cmake', 'bash', 'c', 'cpp',
128-
'swift', 'kotlin', 'java', 'python',
129-
'dart', 'go', 'protobuf'
130-
],
113+
additionalLanguages: ['cmake','bash','c','cpp','swift','kotlin','java','python','dart','go','protobuf'],
131114
},
132-
133-
134115
} satisfies Preset.ThemeConfig,
116+
117+
// Put scripts here, inside the same config object
118+
scripts: [{ src: '/js/search-analytics.js', async: true }],
119+
// Optional: keep this if you want it rendered in the footer
120+
// customFields: {},
135121
};
136122

137123
export default config;
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
(function () {
2+
// Utility: send GA4 events if gtag exists
3+
function sendEvent(name, params) {
4+
if (typeof window.gtag === 'function') {
5+
window.gtag('event', name, params || {});
6+
}
7+
}
8+
9+
// Detect the search input rendered by docusaurus-search-local
10+
function getSearchInput() {
11+
// Works with the default local-search theme (input[type="search"])
12+
return document.querySelector('input[type="search"]');
13+
}
14+
15+
// Hook into search typing + submit/enter
16+
function attachSearchListeners() {
17+
const input = getSearchInput();
18+
if (!input) return;
19+
20+
let lastValue = '';
21+
let lastResultsCount = null;
22+
23+
// Fire when user presses Enter (treated as "view_search_results")
24+
input.addEventListener('keydown', (e) => {
25+
if (e.key === 'Enter') {
26+
const query = input.value.trim();
27+
if (!query) return;
28+
// send GA4 "view_search_results"
29+
sendEvent('view_search_results', {
30+
search_term: query,
31+
});
32+
// Also record if there were zero results (if we can read result container)
33+
// Result container is rendered after typing; we can query it shortly after.
34+
setTimeout(() => {
35+
const results = document.querySelectorAll('.searchResultItem');
36+
lastResultsCount = results ? results.length : null;
37+
if (lastResultsCount === 0) {
38+
sendEvent('search_no_results', { search_term: query });
39+
}
40+
}, 50);
41+
}
42+
});
43+
44+
// Debounced input capture to log "search" intent (optional)
45+
let t;
46+
input.addEventListener('input', () => {
47+
clearTimeout(t);
48+
t = setTimeout(() => {
49+
const val = input.value.trim();
50+
if (val && val !== lastValue) {
51+
lastValue = val;
52+
sendEvent('search', { search_term: val });
53+
}
54+
}, 400);
55+
});
56+
57+
// Click tracking on results (delegated)
58+
document.addEventListener('click', (e) => {
59+
const a = e.target.closest('a');
60+
if (!a) return;
61+
62+
// Local search results typically have a container; adjust selector if needed
63+
const resultItem = e.target.closest('.searchResultItem, .DocSearch-Hit'); // support both local & DocSearch
64+
if (resultItem) {
65+
const href = a.getAttribute('href') || '';
66+
const title =
67+
resultItem.querySelector('mark')?.textContent ||
68+
resultItem.textContent?.trim().slice(0, 120) ||
69+
'';
70+
sendEvent('select_content', {
71+
content_type: 'search_result',
72+
item_id: href,
73+
item_name: title,
74+
});
75+
}
76+
});
77+
}
78+
79+
// Wait for hydration
80+
if (document.readyState === 'loading') {
81+
document.addEventListener('DOMContentLoaded', attachSearchListeners);
82+
} else {
83+
attachSearchListeners();
84+
}
85+
})();

0 commit comments

Comments
 (0)