@opendocsdev/cli 0.2.3 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -30,28 +30,23 @@ const shouldTrack = backend && siteId && analyticsEnabled;
|
|
|
30
30
|
is:inline
|
|
31
31
|
>
|
|
32
32
|
(function() {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const siteId = script.getAttribute('data-site-id');
|
|
37
|
-
const pathFromProp = script.getAttribute('data-path');
|
|
33
|
+
var script = document.currentScript;
|
|
34
|
+
var backend = script.getAttribute('data-backend');
|
|
35
|
+
var siteId = script.getAttribute('data-site-id');
|
|
38
36
|
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
function trackPageview() {
|
|
38
|
+
fetch(backend + '/api/analytics/pv', {
|
|
39
|
+
method: 'POST',
|
|
40
|
+
headers: { 'Content-Type': 'application/json' },
|
|
41
|
+
body: JSON.stringify({
|
|
42
|
+
siteId: siteId,
|
|
43
|
+
path: window.location.pathname,
|
|
44
|
+
}),
|
|
45
|
+
}).catch(function() {});
|
|
46
|
+
}
|
|
41
47
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
method: 'POST',
|
|
45
|
-
headers: {
|
|
46
|
-
'Content-Type': 'application/json',
|
|
47
|
-
},
|
|
48
|
-
body: JSON.stringify({
|
|
49
|
-
siteId: siteId,
|
|
50
|
-
path: path,
|
|
51
|
-
}),
|
|
52
|
-
}).catch(function(err) {
|
|
53
|
-
console.debug('[opendocs] Analytics error:', err);
|
|
54
|
-
});
|
|
48
|
+
trackPageview();
|
|
49
|
+
document.addEventListener('astro:after-swap', trackPageview);
|
|
55
50
|
})();
|
|
56
51
|
</script>
|
|
57
52
|
)}
|
|
@@ -80,13 +80,13 @@ export function SearchModal({ isOpen, onClose, backend, siteId }: SearchModalPro
|
|
|
80
80
|
const resultsRef = useRef<HTMLDivElement>(null);
|
|
81
81
|
const pagefindRef = useRef<any>(null);
|
|
82
82
|
|
|
83
|
-
// Track the last search for analytics (only sent on meaningful actions)
|
|
84
|
-
const lastSearchRef = useRef<{ query: string; resultCount: number } | null>(null);
|
|
85
83
|
const hasTrackedRef = useRef(false);
|
|
84
|
+
const lastResultCountRef = useRef(0);
|
|
85
|
+
const queryRef = useRef(query);
|
|
86
|
+
queryRef.current = query;
|
|
86
87
|
|
|
87
88
|
const loadPagefind = async () => {
|
|
88
89
|
try {
|
|
89
|
-
// Use Function constructor to create a truly dynamic import that Vite won't analyze
|
|
90
90
|
const importPagefind = new Function(
|
|
91
91
|
'return import("/pagefind/pagefind.js")'
|
|
92
92
|
) as () => Promise<any>;
|
|
@@ -100,17 +100,15 @@ export function SearchModal({ isOpen, onClose, backend, siteId }: SearchModalPro
|
|
|
100
100
|
}
|
|
101
101
|
};
|
|
102
102
|
|
|
103
|
-
//
|
|
104
|
-
|
|
103
|
+
// Fire-and-forget analytics using current query state
|
|
104
|
+
// Tracks on close or result click — captures the query text at that moment,
|
|
105
|
+
// including searches with 0 results (failed searches)
|
|
106
|
+
const trackSearch = useCallback((searchQuery: string, resultCount: number) => {
|
|
105
107
|
if (!backend || !siteId) return;
|
|
106
108
|
if (hasTrackedRef.current) return;
|
|
107
|
-
if (!lastSearchRef.current) return;
|
|
108
|
-
|
|
109
|
-
const { query: searchQuery, resultCount } = lastSearchRef.current;
|
|
110
109
|
if (!searchQuery.trim()) return;
|
|
111
110
|
|
|
112
111
|
hasTrackedRef.current = true;
|
|
113
|
-
|
|
114
112
|
const blob = new Blob(
|
|
115
113
|
[JSON.stringify({ siteId, query: searchQuery, resultCount })],
|
|
116
114
|
{ type: "application/json" }
|
|
@@ -118,15 +116,12 @@ export function SearchModal({ isOpen, onClose, backend, siteId }: SearchModalPro
|
|
|
118
116
|
navigator.sendBeacon(`${backend}/api/analytics/sq`, blob);
|
|
119
117
|
}, [backend, siteId]);
|
|
120
118
|
|
|
121
|
-
//
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
onClose();
|
|
125
|
-
}, [onClose, trackSearch]);
|
|
119
|
+
// Ref so cleanup effects always call the latest trackSearch without re-registering
|
|
120
|
+
const trackSearchRef = useRef(trackSearch);
|
|
121
|
+
trackSearchRef.current = trackSearch;
|
|
126
122
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
trackSearch();
|
|
123
|
+
const handleResultSelect = useCallback((url: string, searchQuery: string, resultCount: number) => {
|
|
124
|
+
trackSearch(searchQuery, resultCount);
|
|
130
125
|
window.location.href = url;
|
|
131
126
|
}, [trackSearch]);
|
|
132
127
|
|
|
@@ -155,7 +150,7 @@ export function SearchModal({ isOpen, onClose, backend, siteId }: SearchModalPro
|
|
|
155
150
|
})
|
|
156
151
|
);
|
|
157
152
|
dispatch({ type: "SEARCH_SUCCESS", payload: formattedResults });
|
|
158
|
-
|
|
153
|
+
lastResultCountRef.current = searchResults.results.length;
|
|
159
154
|
} catch (e) {
|
|
160
155
|
console.error("Search error:", e);
|
|
161
156
|
dispatch({ type: "SEARCH_ERROR", payload: "Search failed" });
|
|
@@ -169,15 +164,18 @@ export function SearchModal({ isOpen, onClose, backend, siteId }: SearchModalPro
|
|
|
169
164
|
}
|
|
170
165
|
}, [isOpen, pagefindLoaded]);
|
|
171
166
|
|
|
172
|
-
//
|
|
167
|
+
// Track analytics when modal closes, reset state when it opens
|
|
173
168
|
useEffect(() => {
|
|
174
169
|
if (isOpen) {
|
|
175
170
|
dispatch({ type: "RESET" });
|
|
176
|
-
lastSearchRef.current = null;
|
|
177
171
|
hasTrackedRef.current = false;
|
|
178
|
-
|
|
172
|
+
lastResultCountRef.current = 0;
|
|
179
173
|
const timerId = setTimeout(() => inputRef.current?.focus(), 50);
|
|
180
|
-
return () =>
|
|
174
|
+
return () => {
|
|
175
|
+
clearTimeout(timerId);
|
|
176
|
+
// Fires when isOpen flips to false — track the search session
|
|
177
|
+
trackSearchRef.current(queryRef.current, lastResultCountRef.current);
|
|
178
|
+
};
|
|
181
179
|
}
|
|
182
180
|
}, [isOpen]);
|
|
183
181
|
|
|
@@ -187,12 +185,12 @@ export function SearchModal({ isOpen, onClose, backend, siteId }: SearchModalPro
|
|
|
187
185
|
const handleEscape = (e: KeyboardEvent) => {
|
|
188
186
|
if (e.key === "Escape") {
|
|
189
187
|
e.preventDefault();
|
|
190
|
-
|
|
188
|
+
onClose();
|
|
191
189
|
}
|
|
192
190
|
};
|
|
193
191
|
document.addEventListener("keydown", handleEscape);
|
|
194
192
|
return () => document.removeEventListener("keydown", handleEscape);
|
|
195
|
-
}, [isOpen,
|
|
193
|
+
}, [isOpen, onClose]);
|
|
196
194
|
|
|
197
195
|
// Scroll selected item into view
|
|
198
196
|
useEffect(() => {
|
|
@@ -224,7 +222,7 @@ export function SearchModal({ isOpen, onClose, backend, siteId }: SearchModalPro
|
|
|
224
222
|
case "Enter":
|
|
225
223
|
e.preventDefault();
|
|
226
224
|
if (results[selectedIndex]) {
|
|
227
|
-
handleResultSelect(results[selectedIndex].url);
|
|
225
|
+
handleResultSelect(results[selectedIndex].url, query, lastResultCountRef.current);
|
|
228
226
|
}
|
|
229
227
|
break;
|
|
230
228
|
}
|
|
@@ -243,7 +241,7 @@ export function SearchModal({ isOpen, onClose, backend, siteId }: SearchModalPro
|
|
|
243
241
|
{/* Backdrop */}
|
|
244
242
|
<div
|
|
245
243
|
className="absolute inset-0 bg-black/50 backdrop-blur-sm"
|
|
246
|
-
onClick={
|
|
244
|
+
onClick={onClose}
|
|
247
245
|
aria-hidden="true"
|
|
248
246
|
/>
|
|
249
247
|
|
|
@@ -288,7 +286,7 @@ export function SearchModal({ isOpen, onClose, backend, siteId }: SearchModalPro
|
|
|
288
286
|
href={result.url}
|
|
289
287
|
onClick={(e) => {
|
|
290
288
|
e.preventDefault();
|
|
291
|
-
handleResultSelect(result.url);
|
|
289
|
+
handleResultSelect(result.url, query, lastResultCountRef.current);
|
|
292
290
|
}}
|
|
293
291
|
className={cn(
|
|
294
292
|
"block px-4 py-3 transition-colors",
|