@abreen/tada 1.15.9 → 1.15.10

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": "@abreen/tada",
3
- "version": "1.15.9",
3
+ "version": "1.15.10",
4
4
  "type": "module",
5
5
  "description": "A static site generator",
6
6
  "license": "MIT",
@@ -45,7 +45,14 @@ export function isEligibleLink(
45
45
  return false;
46
46
  }
47
47
 
48
- if (!url.pathname.startsWith(basePath)) {
48
+ const normalizedBasePath = basePath.endsWith('/')
49
+ ? basePath.slice(0, -1) || '/'
50
+ : basePath;
51
+ if (
52
+ normalizedBasePath !== '/' &&
53
+ url.pathname !== normalizedBasePath &&
54
+ !url.pathname.startsWith(`${normalizedBasePath}/`)
55
+ ) {
49
56
  return false;
50
57
  }
51
58
 
@@ -10,6 +10,7 @@ import { NAVIGATION_EVENT, refreshCurrentPage } from '../navigate/runtime';
10
10
  import { globals } from '../globals';
11
11
 
12
12
  const POLL_INTERVAL_MS = 600_000;
13
+ export const PAGE_UPDATE_REFRESH_EVENT = 'tada:page-update-refresh';
13
14
 
14
15
  interface PageUpdateOptions {
15
16
  pollIntervalMs?: number;
@@ -247,7 +248,10 @@ export function mountPageUpdate(
247
248
  }
248
249
 
249
250
  function handleReloadClick() {
250
- void refreshPage(window);
251
+ void refreshPage(window).then(() => {
252
+ const EventCtor = (window as unknown as { Event: typeof Event }).Event;
253
+ window.dispatchEvent(new EventCtor(PAGE_UPDATE_REFRESH_EVENT));
254
+ });
251
255
  }
252
256
 
253
257
  reloadButton.addEventListener('click', handleReloadClick);
@@ -1,15 +1,9 @@
1
1
  import { getElement, applyBasePath } from '../util';
2
2
  import { getPdfPageNumber, groupPdfResults } from './pdf-utils';
3
3
  import type { Result } from './pdf-utils';
4
- import {
5
- getResponseValidators,
6
- hasResponseValidatorsChanged,
7
- type ResponseValidators,
8
- } from '../validators';
4
+ import { PAGE_UPDATE_REFRESH_EVENT } from '../page-update';
9
5
  import { globals } from '../globals';
10
6
 
11
- const MAX_RESULTS = 24;
12
-
13
7
  interface PagefindSubResult {
14
8
  title?: string;
15
9
  url: string;
@@ -34,7 +28,9 @@ interface PagefindSearchResult {
34
28
  }
35
29
 
36
30
  interface Pagefind {
31
+ destroy(): Promise<void>;
37
32
  init(): Promise<void>;
33
+ options(options: { basePath: string }): Promise<void>;
38
34
  search(query: string): Promise<{ results: PagefindSearchResult[] }>;
39
35
  }
40
36
 
@@ -61,8 +57,7 @@ async function doSearch(query: string, window: Window): Promise<SearchState> {
61
57
  }
62
58
 
63
59
  const search = await pagefind.search(query);
64
- const slice = search.results.slice(0, MAX_RESULTS);
65
- const data = await Promise.all(slice.map(r => r.data()));
60
+ const data = await Promise.all(search.results.map(r => r.data()));
66
61
 
67
62
  const titlePostfix = __SITE_TITLE_POSTFIX__;
68
63
  const results: Result[] = data.map((d: PagefindResult) => {
@@ -139,9 +134,9 @@ function render(
139
134
  ol.tabIndex = -1;
140
135
  ol.setAttribute('aria-label', 'Search results');
141
136
 
142
- const totalVisible = Math.min(state.results.length, MAX_RESULTS);
137
+ const totalVisible = state.results.length;
143
138
 
144
- state.results.slice(0, MAX_RESULTS).forEach((result, i) => {
139
+ state.results.forEach((result, i) => {
145
140
  const a = doc.createElement('a');
146
141
  a.id = `result-${i}`;
147
142
  a.className = 'result';
@@ -175,7 +170,7 @@ function render(
175
170
  li.setAttribute('aria-labelledby', `title-${i}`);
176
171
  li.appendChild(a);
177
172
 
178
- const subsToShow = result.subResults.slice(0, 5);
173
+ const subsToShow = result.subResults;
179
174
  if (subsToShow.length > 0) {
180
175
  const subList = doc.createElement('ul');
181
176
  subList.className = 'sub-results';
@@ -250,10 +245,8 @@ function render(
250
245
  countSpan.textContent = 'No results';
251
246
  } else if (n === 1) {
252
247
  countSpan.textContent = 'One result';
253
- } else if (n <= MAX_RESULTS) {
254
- countSpan.textContent = `${n} results`;
255
248
  } else {
256
- countSpan.textContent = `Showing first ${MAX_RESULTS} results`;
249
+ countSpan.textContent = `${n} results`;
257
250
  }
258
251
  }
259
252
 
@@ -286,64 +279,56 @@ export default (window: Window) => {
286
279
  // Unhide (hidden via inline style in template to prevent FOUC)
287
280
  resultsDiv.style.display = '';
288
281
 
289
- let entryValidators: ResponseValidators = { etag: null, lastModified: null };
290
- let lastIndexCheck = 0;
291
- let indexCheckInFlight = false;
292
-
293
- async function loadPagefind() {
294
- if (pagefind) {
295
- return;
296
- }
297
-
298
- pagefind = (await globals.importModule(
299
- applyBasePath('/pagefind/pagefind.js'),
300
- )) as Pagefind;
301
-
302
- await pagefind.init();
282
+ let pagefindLoadPromise: Promise<boolean> | null = null;
283
+ let pagefindLoadGeneration = 0;
303
284
 
304
- const res = await globals.fetch(
305
- applyBasePath('/pagefind/pagefind-entry.json'),
306
- { cache: 'no-cache' },
307
- );
308
-
309
- if (res.ok) {
310
- entryValidators = getResponseValidators(res);
285
+ function pagefindModulePath(generation: number) {
286
+ const path = applyBasePath('/pagefind/pagefind.js');
287
+ if (generation === 0) {
288
+ return path;
311
289
  }
290
+ return `${path}?tada-pagefind-refresh=${generation}`;
312
291
  }
313
292
 
314
- async function checkForIndexUpdate() {
315
- if (indexCheckInFlight || globals.now() - lastIndexCheck < 3000) {
316
- return;
293
+ async function loadPagefind(): Promise<boolean> {
294
+ if (pagefind) {
295
+ return true;
317
296
  }
318
- lastIndexCheck = globals.now();
319
- indexCheckInFlight = true;
320
- try {
321
- const res = await globals.fetch(
322
- applyBasePath('/pagefind/pagefind-entry.json'),
323
- { method: 'HEAD', cache: 'no-cache' },
324
- );
325
- if (!res.ok) {
326
- return;
297
+
298
+ const generation = pagefindLoadGeneration;
299
+ pagefindLoadPromise ??= (async () => {
300
+ const loadedPagefind = (await globals.importModule(
301
+ pagefindModulePath(generation),
302
+ )) as Pagefind;
303
+
304
+ if (generation !== pagefindLoadGeneration) {
305
+ return false;
306
+ }
307
+ if (generation > 0) {
308
+ await loadedPagefind.options({ basePath: applyBasePath('/pagefind/') });
327
309
  }
328
- const newValidators = getResponseValidators(res);
329
- if (hasResponseValidatorsChanged(entryValidators, newValidators)) {
330
- invalidateUpdates();
331
- pagefind = null;
332
- await loadPagefind();
333
- const updateId = ++latestUpdateId;
334
- await update(updateId);
310
+ if (generation !== pagefindLoadGeneration) {
311
+ return false;
335
312
  }
336
- } catch {
337
- // best-effort
313
+ await loadedPagefind.init();
314
+ if (generation !== pagefindLoadGeneration) {
315
+ await loadedPagefind.destroy();
316
+ return false;
317
+ }
318
+ pagefind = loadedPagefind;
319
+ return true;
320
+ })();
321
+
322
+ const promise = pagefindLoadPromise;
323
+ try {
324
+ return await promise;
338
325
  } finally {
339
- indexCheckInFlight = false;
326
+ if (pagefindLoadPromise === promise) {
327
+ pagefindLoadPromise = null;
328
+ }
340
329
  }
341
330
  }
342
331
 
343
- loadPagefind().catch(err => {
344
- console.log(`failed to load Pagefind: ${err}`);
345
- });
346
-
347
332
  const state: State = {
348
333
  value: '',
349
334
  showResults: false,
@@ -362,10 +347,11 @@ export default (window: Window) => {
362
347
  if (updateId !== latestUpdateId) {
363
348
  return;
364
349
  }
365
- if (nextState) {
366
- state.results = nextState.results;
367
- state.totalResults = nextState.totalResults;
350
+ if (!nextState) {
351
+ return;
368
352
  }
353
+ state.results = nextState.results;
354
+ state.totalResults = nextState.totalResults;
369
355
  if (!state.showResults) {
370
356
  return;
371
357
  }
@@ -381,6 +367,55 @@ export default (window: Window) => {
381
367
  latestUpdateId += 1;
382
368
  }
383
369
 
370
+ function loadPagefindAndUpdate() {
371
+ loadPagefind()
372
+ .then(loadedCurrentPagefind => {
373
+ if (loadedCurrentPagefind && state.showResults) {
374
+ queueUpdate();
375
+ }
376
+ })
377
+ .catch(err => {
378
+ console.log(`failed to load Pagefind: ${err}`);
379
+ if (state.showResults) {
380
+ render(input!, resultsContainer, state, false);
381
+ }
382
+ });
383
+ }
384
+
385
+ function invalidatePagefind() {
386
+ pagefindLoadGeneration += 1;
387
+ pagefind = null;
388
+ pagefindLoadPromise = null;
389
+ invalidateUpdates();
390
+ }
391
+
392
+ async function reloadPagefindAfterPageUpdateRefresh() {
393
+ const stalePagefind = pagefind;
394
+ invalidatePagefind();
395
+ if (state.showResults) {
396
+ render(input!, resultsContainer, state, true);
397
+ }
398
+ if (stalePagefind) {
399
+ try {
400
+ await stalePagefind.destroy();
401
+ } catch (err) {
402
+ console.log(`failed to destroy Pagefind: ${err}`);
403
+ }
404
+ }
405
+ loadPagefindAndUpdate();
406
+ }
407
+
408
+ function handlePageUpdateRefresh() {
409
+ reloadPagefindAfterPageUpdateRefresh().catch(err => {
410
+ console.log(`failed to reload Pagefind: ${err}`);
411
+ if (state.showResults) {
412
+ render(input!, resultsContainer, state, false);
413
+ }
414
+ });
415
+ }
416
+
417
+ loadPagefindAndUpdate();
418
+
384
419
  function hide() {
385
420
  if (!state.showResults) {
386
421
  return;
@@ -417,7 +452,6 @@ export default (window: Window) => {
417
452
  if (previous && !resultsContainer.contains(previous)) {
418
453
  previousFocus = previous;
419
454
  }
420
- checkForIndexUpdate().catch(() => {});
421
455
  if (!state.showResults) {
422
456
  state.showResults = true;
423
457
  queueUpdate();
@@ -563,8 +597,13 @@ export default (window: Window) => {
563
597
  window.addEventListener('pointerdown', handleWindowPointerDown);
564
598
  window.addEventListener('pointermove', handleWindowPointerMove);
565
599
  window.addEventListener('keydown', handleWindowKeyDown);
600
+ window.addEventListener(PAGE_UPDATE_REFRESH_EVENT, handlePageUpdateRefresh);
566
601
 
567
602
  return () => {
603
+ window.removeEventListener(
604
+ PAGE_UPDATE_REFRESH_EVENT,
605
+ handlePageUpdateRefresh,
606
+ );
568
607
  window.removeEventListener('keydown', handleWindowKeyDown);
569
608
  window.removeEventListener('pointermove', handleWindowPointerMove);
570
609
  window.removeEventListener('pointerdown', handleWindowPointerDown);
package/src/top/index.ts CHANGED
@@ -30,11 +30,12 @@ export default (window: Window) => {
30
30
  link.onclick = e => {
31
31
  e.preventDefault();
32
32
  const cleanUrl = window.location.pathname + window.location.search;
33
+ const historyState = window.history.state;
33
34
  if (window.location.hash) {
34
35
  globals.setLocationHash(window, '');
35
- window.history.replaceState(null, '', cleanUrl);
36
+ window.history.replaceState(historyState, '', cleanUrl);
36
37
  } else {
37
- window.history.replaceState(null, '', cleanUrl);
38
+ window.history.replaceState(historyState, '', cleanUrl);
38
39
  }
39
40
  window.scrollTo({ top: 0 });
40
41
  const toc = window.document.querySelector('nav.toc') as HTMLElement | null;