@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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opendocsdev/cli",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -30,28 +30,23 @@ const shouldTrack = backend && siteId && analyticsEnabled;
30
30
  is:inline
31
31
  >
32
32
  (function() {
33
- // Get configuration from script tag
34
- const script = document.currentScript;
35
- const backend = script.getAttribute('data-backend');
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
- // Use provided path or current pathname
40
- const path = pathFromProp || window.location.pathname;
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
- // Send pageview to backend (short path to avoid ad blockers)
43
- fetch(`${backend}/api/analytics/pv`, {
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
- // Track search query for analytics - only tracks once per search session
104
- const trackSearch = useCallback(() => {
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
- // Handle close with analytics tracking
122
- const handleClose = useCallback(() => {
123
- trackSearch();
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
- // Handle result selection with analytics tracking
128
- const handleResultSelect = useCallback((url: string) => {
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
- lastSearchRef.current = { query: searchQuery, resultCount: searchResults.results.length };
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
- // Reset state and focus input when modal opens
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
- // Small delay to ensure modal is rendered before focusing
172
+ lastResultCountRef.current = 0;
179
173
  const timerId = setTimeout(() => inputRef.current?.focus(), 50);
180
- return () => clearTimeout(timerId);
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
- handleClose();
188
+ onClose();
191
189
  }
192
190
  };
193
191
  document.addEventListener("keydown", handleEscape);
194
192
  return () => document.removeEventListener("keydown", handleEscape);
195
- }, [isOpen, handleClose]);
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={handleClose}
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",