astro-accelerator 4.1.5 → 4.1.7

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,5 +1,5 @@
1
1
  {
2
- "version": "4.1.5",
2
+ "version": "4.1.7",
3
3
  "author": "Steve Fenton",
4
4
  "name": "astro-accelerator",
5
5
  "description": "A super-lightweight, accessible, SEO-friendly starter project for Astro",
@@ -32,9 +32,9 @@
32
32
  },
33
33
  "dependencies": {
34
34
  "@astrojs/mdx": "^2.3.1",
35
- "astro": "^4.16.6",
36
- "astro-accelerator-utils": "^0.3.25",
37
- "cspell": "^8.15.4",
35
+ "astro": "^4.16.8",
36
+ "astro-accelerator-utils": "^0.3.26",
37
+ "cspell": "^8.15.7",
38
38
  "csv": "^6.3.10",
39
39
  "glob": "^11.0.0",
40
40
  "hast-util-from-selector": "^3.0.0",
@@ -45,7 +45,7 @@
45
45
  "sharp": "^0.33.5"
46
46
  },
47
47
  "devDependencies": {
48
- "@playwright/test": "^1.48.1"
48
+ "@playwright/test": "^1.48.2"
49
49
  },
50
50
  "files": [
51
51
  ".npmrc",
@@ -774,7 +774,8 @@ nav.site-nav h2 {
774
774
  background-color: initial;
775
775
  }
776
776
 
777
- .result-wrapper mark {
777
+ .result-wrapper mark,
778
+ .result-headings mark {
778
779
  background-color: var(--aft-mark);
779
780
  }
780
781
 
@@ -874,26 +875,15 @@ nav.site-nav h2 {
874
875
  cursor: pointer;
875
876
  }
876
877
 
877
- .result-headings li {
878
- display: none;
879
- }
880
-
881
- .result-headings li:nth-child(-n + 3) {
882
- display: block;
883
- }
884
-
885
- .result-headings:has(li:nth-child(n + 4))::after {
886
- content: 'See more >';
887
- color: var(--fore-link);
888
- text-decoration: underline;
889
- }
890
-
891
- .result-headings:focus-within li {
892
- display: block;
878
+ .result-headings {
879
+ display: grid;
880
+ grid-template-columns: 1fr 1fr;
893
881
  }
894
882
 
895
- .result-headings:focus-within::after {
896
- display: none;
883
+ .result-headings li {
884
+ text-align: left;
885
+ padding: 0.1rem 3.6rem;
886
+ font-size: 1rem;
897
887
  }
898
888
 
899
889
  .show-more {
@@ -72,6 +72,7 @@ function initializeSearch() {
72
72
  var currentQuery = '';
73
73
  var dataUrl = siteSearchElement.dataset.sourcedata;
74
74
 
75
+ // Legacy scoring
75
76
  var scoring = {
76
77
  depth: 5,
77
78
  phraseTitle: 60,
@@ -84,6 +85,14 @@ function initializeSearch() {
84
85
  termKeywords: 15,
85
86
  };
86
87
 
88
+ // Found word scoring
89
+ const scores = {
90
+ titleExact: 20,
91
+ titleContains: 15,
92
+ headingContains: 10,
93
+ contentContains: 1,
94
+ };
95
+
87
96
  var ready = false;
88
97
  var scrolled = false;
89
98
 
@@ -246,6 +255,7 @@ function initializeSearch() {
246
255
  /**
247
256
  * Replaces synonyms
248
257
  * @param {string[]} queryTerms
258
+ * @returns {Promise<string[]>}
249
259
  */
250
260
  async function replaceSynonyms(queryTerms) {
251
261
  const synonyms = await getSynonyms();
@@ -253,11 +263,17 @@ function initializeSearch() {
253
263
  for (let i = 0; i < queryTerms.length; i++) {
254
264
  const term = queryTerms[i];
255
265
  if (synonyms[term] != null) {
256
- queryTerms.push(synonyms[term]);
266
+ if (synonyms[term].length === 0) {
267
+ // @ts-ignore
268
+ queryTerms[i] = null;
269
+ } else {
270
+ queryTerms.push(synonyms[term]);
271
+ }
257
272
  }
258
273
  }
259
274
 
260
- return queryTerms;
275
+ console.log('Post-synonym', queryTerms);
276
+ return queryTerms.filter((qt) => qt != null);
261
277
  }
262
278
 
263
279
  /**
@@ -314,12 +330,12 @@ function initializeSearch() {
314
330
 
315
331
  // Title
316
332
  if (item.safeTitle === currentQuery) {
317
- item.foundWords += 2;
333
+ item.foundWords += scores.titleExact;
318
334
  }
319
335
 
320
336
  if (contains(item.safeTitle, currentQuery)) {
321
337
  item.score = item.score + scoring.phraseTitle;
322
- item.foundWords += 2;
338
+ item.foundWords += scores.titleContains;
323
339
  }
324
340
 
325
341
  // Headings
@@ -327,14 +343,14 @@ function initializeSearch() {
327
343
  if (contains(c.safeText, currentQuery)) {
328
344
  item.score = item.score + scoring.phraseHeading;
329
345
  item.matchedHeadings.push(c);
330
- item.foundWords++;
346
+ item.foundWords += scores.headingContains;
331
347
  }
332
348
  });
333
349
 
334
350
  // Description
335
351
  if (contains(item.description, currentQuery)) {
336
352
  item.score = item.score + scoring.phraseDescription;
337
- item.foundWords++;
353
+ item.foundWords += scores.contentContains;
338
354
  }
339
355
 
340
356
  // Part 2 - Term Matches, i.e. "Kitchen" or "Sink"
@@ -0,0 +1,676 @@
1
+ /**
2
+ * This javascript file comes from Astro Accelerator
3
+ * Edits will be overwritten if you change the file locally
4
+ * THIS IS THE PREVIOUS SEARCH VERSION
5
+ *
6
+ * @format
7
+ */
8
+
9
+ // @ts-check
10
+
11
+ import { qs } from './modules/query.js';
12
+ import { raiseEvent } from './modules/events.js';
13
+ import { removeScroll, resetScroll } from './modules/scrollbar.js';
14
+ import { contains, sanitise, explode, highlight } from './modules/string.js';
15
+ import { stemmer } from './modules/stemmer.js';
16
+
17
+ // @ts-ignore
18
+ const f = window.site_features ?? {};
19
+
20
+ /**
21
+ *
22
+ * @param {string[]} settings
23
+ * @param {string} option
24
+ * @returns
25
+ */
26
+ function enabled(settings, option) {
27
+ return settings && settings.includes(option);
28
+ }
29
+
30
+ /**
31
+ @typedef {
32
+ {
33
+ text: string;
34
+ safeText: string;
35
+ slug: string;
36
+ }
37
+ } Heading
38
+
39
+ @typedef {
40
+ {
41
+ foundWords: number;
42
+ score: number;
43
+ depth: number;
44
+ title: string;
45
+ keywords: string;
46
+ safeTitle: string;
47
+ description: string;
48
+ safeDescription: string;
49
+ headings: Heading[];
50
+ tags: string[];
51
+ url: string;
52
+ date: string;
53
+ matchedHeadings: Heading[];
54
+ }
55
+ } SearchEntry
56
+
57
+ @typedef {
58
+ {
59
+ [ix: string]: string
60
+ }
61
+ } Synonyms
62
+ */
63
+
64
+ function initializeSearch() {
65
+ const siteSearchInput = qs('[data-site-search-query]');
66
+ const siteSearchWrapper = qs('[data-site-search-wrapper]');
67
+ const siteSearchElement = qs('[data-site-search]');
68
+ const siteSearchResults = qs('[data-site-search-results');
69
+ const removeSearchButton = qs('[data-site-search-remove]');
70
+
71
+ /** @type {SearchEntry[]} */
72
+ var haystack = [];
73
+ var currentQuery = '';
74
+ var dataUrl = siteSearchElement.dataset.sourcedata;
75
+
76
+ var scoring = {
77
+ depth: 5,
78
+ phraseTitle: 60,
79
+ phraseHeading: 20,
80
+ phraseDescription: 20,
81
+ termTitle: 40,
82
+ termHeading: 15,
83
+ termDescription: 15,
84
+ termTags: 15,
85
+ termKeywords: 15,
86
+ };
87
+
88
+ var ready = false;
89
+ var scrolled = false;
90
+
91
+ siteSearchInput.addEventListener('focus', () => activateInput());
92
+
93
+ function deactivate(e) {
94
+ if (
95
+ !siteSearchElement.contains(e.target) &&
96
+ !siteSearchResults.contains(e.target)
97
+ ) {
98
+ closeDropdown();
99
+
100
+ const duration = getComputedStyle(
101
+ siteSearchWrapper
102
+ ).getPropertyValue('--search-dropdown-duration');
103
+
104
+ // Convert duration to milliseconds for setTimeout
105
+ const durationMs =
106
+ parseFloat(duration) * (duration.endsWith('ms') ? 1 : 1000);
107
+
108
+ setTimeout(() => {
109
+ deactivateInput();
110
+ }, durationMs);
111
+ }
112
+ }
113
+
114
+ // Close the dropdown upon activity outside the search
115
+ document.addEventListener('click', deactivate);
116
+ document.addEventListener('keydown', deactivate);
117
+
118
+ function removeSearch() {
119
+ clearInput();
120
+ deactivateInput();
121
+ debounceSearch();
122
+ siteSearchInput.focus();
123
+ }
124
+
125
+ // Clear the search input
126
+ removeSearchButton.addEventListener('click', () => {
127
+ removeSearch();
128
+ });
129
+
130
+ // Dropdown accessibility controls
131
+ document.addEventListener('keydown', handleDropdownKeyboardNavigation);
132
+
133
+ function activateInput() {
134
+ if (
135
+ siteSearchWrapper.classList.contains('is-active') ||
136
+ siteSearchInput.value.trim().length === 0
137
+ ) {
138
+ return;
139
+ }
140
+ siteSearchWrapper.classList.add('is-active');
141
+ removeScroll();
142
+ openDropdown();
143
+ }
144
+
145
+ function deactivateInput() {
146
+ if (!siteSearchWrapper.classList.contains('is-active')) {
147
+ return;
148
+ }
149
+ siteSearchWrapper.classList.remove('is-active');
150
+ resetScroll();
151
+ }
152
+
153
+ function openDropdown() {
154
+ siteSearchElement.classList.add('is-active');
155
+
156
+ requestAnimationFrame(() => {
157
+ const dropdownHeightPercentage = parseFloat(
158
+ getComputedStyle(siteSearchWrapper).getPropertyValue(
159
+ '--search-dropdown-height'
160
+ )
161
+ );
162
+ // Convert vh to pixels
163
+ const dropdownHeight =
164
+ window.innerHeight * (dropdownHeightPercentage / 100) + 32;
165
+ const siteSearchElementRect =
166
+ siteSearchElement.getBoundingClientRect();
167
+ const offsetFromBottomToElement =
168
+ window.innerHeight - siteSearchElementRect.bottom;
169
+
170
+ if (offsetFromBottomToElement < dropdownHeight) {
171
+ document.body.style.overflow = '';
172
+
173
+ // Scroll to the siteSearchElement
174
+ siteSearchElement.scrollIntoView({
175
+ behavior: 'smooth',
176
+ block: 'start',
177
+ });
178
+
179
+ // Delay the overflow to allow for smooth scrolling
180
+ setTimeout(() => {
181
+ document.body.style.overflow = 'hidden';
182
+ }, 300);
183
+ }
184
+ });
185
+ }
186
+
187
+ function closeDropdown() {
188
+ siteSearchElement.classList.remove('is-active');
189
+ }
190
+
191
+ function clearInput() {
192
+ closeDropdown();
193
+ siteSearchInput.value = '';
194
+ }
195
+
196
+ function handleDropdownKeyboardNavigation(e) {
197
+ // Proceed only if search dropdown is active
198
+ if (!siteSearchWrapper.classList.contains('is-active')) return;
199
+
200
+ if (e.key === 'Escape') {
201
+ removeSearch();
202
+ return;
203
+ }
204
+
205
+ if (e.key === 'Tab') {
206
+ const firstElement = siteSearchInput;
207
+ const lastElement =
208
+ siteSearchResults.querySelector('button') ||
209
+ siteSearchResults.querySelector(
210
+ '.site-search-results-item:last-child .result-wrapper'
211
+ );
212
+
213
+ if (e.shiftKey && document.activeElement === firstElement) {
214
+ // Shift + Tab: Move focus to the last element if the first element is currently focused
215
+ e.preventDefault();
216
+ if (lastElement) lastElement.focus();
217
+ } else if (!e.shiftKey && document.activeElement === lastElement) {
218
+ // Tab: Move focus to the first element if the last element is currently focused
219
+ e.preventDefault();
220
+ firstElement.focus();
221
+ }
222
+ }
223
+ }
224
+
225
+ /** @type{Synonyms | null} */
226
+ var _synonyms = null;
227
+
228
+ /**
229
+ * Gets the list of synonyms if they exist
230
+ * @returns { Promise<Synonyms> }
231
+ */
232
+ async function getSynonyms() {
233
+ if (_synonyms != null) {
234
+ return _synonyms;
235
+ }
236
+
237
+ try {
238
+ const synonymsModule = await import('./synonyms.js');
239
+ _synonyms = synonymsModule.synonyms;
240
+ } catch {
241
+ _synonyms = {};
242
+ }
243
+
244
+ return _synonyms ?? {};
245
+ }
246
+
247
+ /**
248
+ * Replaces synonyms
249
+ * @param {string[]} queryTerms
250
+ */
251
+ async function replaceSynonyms(queryTerms) {
252
+ const synonyms = await getSynonyms();
253
+
254
+ for (let i = 0; i < queryTerms.length; i++) {
255
+ const term = queryTerms[i];
256
+ if (synonyms[term] != null) {
257
+ queryTerms.push(synonyms[term]);
258
+ }
259
+ }
260
+
261
+ return queryTerms;
262
+ }
263
+
264
+ /**
265
+ * Search term `s` and number of results `r`
266
+ * @param {string} s
267
+ * @param {number|null} [r=12]
268
+ * @returns
269
+ */
270
+ async function search(s, r) {
271
+ const numberOfResults = r ?? 12;
272
+
273
+ // Add 'is-active' class when search is performed
274
+ if (s && s.trim().length > 0) {
275
+ activateInput();
276
+ openDropdown();
277
+ } else if (siteSearchElement.classList.contains('is-active')) {
278
+ // Remove 'is-active' class when search is cleared
279
+ closeDropdown();
280
+ }
281
+
282
+ /** @type {SearchEntry[]} */
283
+ const needles = [];
284
+
285
+ // Clean the input
286
+ const cleanQuery = sanitise(s);
287
+
288
+ if (currentQuery === cleanQuery) {
289
+ return;
290
+ }
291
+
292
+ currentQuery = cleanQuery;
293
+ /** @type {string[]} */
294
+ const stemmedTerms = [];
295
+ const queryTerms = await replaceSynonyms(explode(currentQuery));
296
+
297
+ for (const term of queryTerms) {
298
+ const stemmed = stemmer(term);
299
+ if (stemmed !== term) {
300
+ stemmedTerms.push(stemmed);
301
+ }
302
+ }
303
+
304
+ const allTerms = queryTerms.concat(stemmedTerms);
305
+
306
+ cleanQuery.length > 0 &&
307
+ haystack.forEach((item) => {
308
+ item.foundWords = 0;
309
+ item.score = 0;
310
+ item.matchedHeadings = [];
311
+
312
+ // The user searches for "Kitchen Sink"
313
+
314
+ // Part 1 - Phrase Matches, i.e. "Kitchen Sink"
315
+
316
+ // Title
317
+ if (item.safeTitle === currentQuery) {
318
+ item.foundWords += 2;
319
+ }
320
+
321
+ if (contains(item.safeTitle, currentQuery)) {
322
+ item.score = item.score + scoring.phraseTitle;
323
+ item.foundWords += 2;
324
+ }
325
+
326
+ // Headings
327
+ item.headings.forEach((c) => {
328
+ if (contains(c.safeText, currentQuery)) {
329
+ item.score = item.score + scoring.phraseHeading;
330
+ item.matchedHeadings.push(c);
331
+ item.foundWords++;
332
+ }
333
+ });
334
+
335
+ // Description
336
+ if (contains(item.description, currentQuery)) {
337
+ item.score = item.score + scoring.phraseDescription;
338
+ item.foundWords++;
339
+ }
340
+
341
+ // Part 2 - Term Matches, i.e. "Kitchen" or "Sink"
342
+
343
+ let foundWords = 0;
344
+
345
+ allTerms.forEach((term) => {
346
+ let isTermFound = false;
347
+
348
+ // Title
349
+ if (contains(item.safeTitle, term)) {
350
+ item.score = item.score + scoring.termTitle;
351
+ isTermFound = true;
352
+ }
353
+
354
+ // Headings
355
+ item.headings.forEach((c) => {
356
+ if (contains(c.safeText, term)) {
357
+ item.score = item.score + scoring.termHeading;
358
+ isTermFound = true;
359
+
360
+ if (
361
+ item.matchedHeadings.filter(
362
+ (h) => h.slug == c.slug
363
+ ).length == 0
364
+ ) {
365
+ item.matchedHeadings.push(c);
366
+ }
367
+ }
368
+ });
369
+
370
+ // Description
371
+ if (contains(item.description, term)) {
372
+ isTermFound = true;
373
+ item.score = item.score + scoring.termDescription;
374
+ }
375
+
376
+ // Tags
377
+ item.tags.forEach((t) => {
378
+ if (contains(t, term)) {
379
+ isTermFound = true;
380
+ item.score = item.score + scoring.termTags;
381
+ }
382
+ });
383
+
384
+ // Keywords
385
+ if (contains(item.keywords, term)) {
386
+ isTermFound = true;
387
+ item.score = item.score + scoring.termKeywords;
388
+ }
389
+
390
+ if (isTermFound) {
391
+ foundWords++;
392
+ }
393
+ });
394
+
395
+ item.foundWords += foundWords;
396
+
397
+ if (item.score > 0) {
398
+ needles.push(item);
399
+ }
400
+ });
401
+
402
+ needles.forEach((n) => {
403
+ // Bonus points for shallow results, i.e. /features over /features/something/something
404
+
405
+ if (n.depth < 5) {
406
+ n.score += scoring.depth;
407
+ n.foundWords++;
408
+ }
409
+
410
+ if (n.depth < 4) {
411
+ n.score += scoring.depth;
412
+ n.foundWords++;
413
+ }
414
+ });
415
+
416
+ needles.sort(function (a, b) {
417
+ if (b.foundWords === a.foundWords) {
418
+ return b.score - a.score;
419
+ }
420
+
421
+ return b.foundWords - a.foundWords;
422
+ });
423
+
424
+ const total = needles.reduce(function (accumulator, needle) {
425
+ return accumulator + needle.score;
426
+ }, 0);
427
+
428
+ const results = siteSearchResults;
429
+
430
+ const ul = document.createElement('ul');
431
+ ul.className = 'site-search-results__list';
432
+
433
+ const limit = Math.min(needles.length, numberOfResults);
434
+
435
+ // @ts-ignore
436
+ const siteUrl = new URL(site_url);
437
+
438
+ for (let i = 0; i < limit; i++) {
439
+ const needle = needles[i];
440
+
441
+ const address = new URL(needle.url);
442
+ const isSameHost = siteUrl.host == address.host;
443
+ const url = isSameHost ? address.pathname : needle.url;
444
+
445
+ const listElementWrapper = document.createElement('a');
446
+ listElementWrapper.href = url;
447
+ listElementWrapper.className = 'result-wrapper';
448
+
449
+ const listElementTitle = document.createElement('span');
450
+ // Only highlight user query terms, not stemmed terms
451
+ listElementTitle.innerHTML = highlight(needle.title, queryTerms);
452
+ listElementTitle.className = 'result-title';
453
+
454
+ const path = document.createElement('div');
455
+ path.className = 'result-path';
456
+
457
+ // Split the path into segments, filter out empty segments (in case of leading slash)
458
+ const segments = address.pathname.split('/').filter(Boolean);
459
+
460
+ segments.forEach((segment, index) => {
461
+ const words = segment.replace(/-/g, ' ').split(' ');
462
+ const processedSegment = words
463
+ .map((word, index) =>
464
+ index === 0
465
+ ? word.charAt(0).toUpperCase() +
466
+ word.slice(1).toLowerCase()
467
+ : word.toLowerCase()
468
+ )
469
+ .join(' ');
470
+
471
+ const segmentSpan = document.createElement('span');
472
+ segmentSpan.className = 'result-path-segment';
473
+ segmentSpan.textContent = processedSegment;
474
+ path.appendChild(segmentSpan);
475
+
476
+ if (index < segments.length - 1) {
477
+ const svgIcon = document.createElement('span');
478
+ svgIcon.className = 'result-path-icon';
479
+ svgIcon.innerHTML = `
480
+ <svg xmlns="http://www.w3.org/2000/svg" width="6" height="10" viewBox="0 0 6 10">
481
+ <path d="M1 9L5 5L1 1" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>
482
+ </svg>
483
+ `;
484
+ path.appendChild(svgIcon);
485
+ }
486
+ });
487
+
488
+ const listElementDescription = document.createElement('p');
489
+ listElementDescription.className = 'result-description';
490
+ // Only highlight user query terms, not stemmed terms
491
+ listElementDescription.innerHTML = highlight(
492
+ needle.description,
493
+ queryTerms
494
+ );
495
+
496
+ const li = document.createElement('li');
497
+ li.classList.add('site-search-results-item');
498
+ li.dataset.words = needle.foundWords.toString();
499
+ li.dataset.score = (
500
+ Math.round((needle.score / total) * 1000) / 1000
501
+ ).toString();
502
+ listElementWrapper.appendChild(path);
503
+ listElementWrapper.appendChild(listElementTitle);
504
+ listElementWrapper.appendChild(listElementDescription);
505
+ li.appendChild(listElementWrapper);
506
+
507
+ if (
508
+ enabled(f.search, 'headings') &&
509
+ needle.matchedHeadings.length > 0
510
+ ) {
511
+ const headings = document.createElement('ul');
512
+ headings.className = 'result-headings';
513
+
514
+ headings.tabIndex = 0;
515
+
516
+ needle.matchedHeadings.forEach((h) => {
517
+ const item = document.createElement('li');
518
+ const link = document.createElement('a');
519
+ link.href = url + '#' + h.slug;
520
+ // Only highlight user query terms, not stemmed terms
521
+ link.innerHTML = highlight(h.text, queryTerms);
522
+ item.appendChild(link);
523
+ headings.append(item);
524
+ });
525
+
526
+ li.appendChild(headings);
527
+ }
528
+
529
+ ul.appendChild(li);
530
+ }
531
+
532
+ let h4;
533
+ if (needles.length === 0) {
534
+ h4 = document.createElement('h4');
535
+ h4.classList.add('search-results-heading');
536
+ h4.innerHTML = results.dataset.emptytitle || 'No Results';
537
+ }
538
+
539
+ const more = document.createElement('button');
540
+ more.className = 'show-more';
541
+ more.type = 'button';
542
+ more.innerHTML = 'See more';
543
+ more.addEventListener('click', async function (e) {
544
+ e.stopPropagation(); // Prevent the click from closing the dropdown
545
+ currentQuery = '';
546
+ const oldTotal = numberOfResults;
547
+ const newTotal = numberOfResults + 12;
548
+ await search(s, newTotal);
549
+ window.setTimeout(function () {
550
+ const previousPosition = qs(
551
+ `.site-search-results__list li:nth-child(${oldTotal}) > a`
552
+ );
553
+ console.log(previousPosition.outerHTML);
554
+ previousPosition.focus();
555
+ }, 10);
556
+ });
557
+
558
+ if (needles.length > numberOfResults) {
559
+ const moreLi = document.createElement('li');
560
+ moreLi.appendChild(more);
561
+ ul.appendChild(moreLi);
562
+ }
563
+
564
+ results.innerHTML = '';
565
+ results.appendChild(ul);
566
+ h4 && results.appendChild(h4);
567
+
568
+ const address = window.location.href.split('?')[0];
569
+ window.history.pushState(
570
+ {},
571
+ '',
572
+ address + '?q=' + encodeURIComponent(cleanQuery)
573
+ );
574
+ raiseEvent('searched', { search: s });
575
+ }
576
+
577
+ /** @type {Number} */
578
+ var debounceTimer;
579
+
580
+ function debounceSearch() {
581
+ var input = siteSearchInput;
582
+
583
+ if (input == null) {
584
+ throw new Error('Cannot find data-site-search-query');
585
+ }
586
+
587
+ // Words chained with . are combined, i.e. System.Text is "systemtext"
588
+ var s = input.value.replace(/\./g, '').trim();
589
+
590
+ if (!s) {
591
+ const address = window.location.href.split('?')[0];
592
+ window.history.pushState({}, '', address);
593
+ }
594
+
595
+ window.clearTimeout(debounceTimer);
596
+ debounceTimer = window.setTimeout(function () {
597
+ if (ready && s) {
598
+ search(s);
599
+ }
600
+ }, 400);
601
+ }
602
+
603
+ fetch(dataUrl)
604
+ .then(function (response) {
605
+ return response.json();
606
+ })
607
+ .then(function (data) {
608
+ haystack = data;
609
+ ready = true;
610
+
611
+ for (let i = 0; i < haystack.length; i++) {
612
+ const item = haystack[i];
613
+ item.safeTitle = sanitise(item.title);
614
+ item.tags = item.tags.map((t) => sanitise(t));
615
+ item.safeDescription = sanitise(item.description);
616
+ item.depth = item.url.match(/\//g)?.length ?? 0;
617
+
618
+ item.headings.forEach((h) => (h.safeText = sanitise(h.text)));
619
+ }
620
+
621
+ /** @type {HTMLFormElement} */
622
+ const siteSearch = siteSearchElement;
623
+
624
+ /** @type {HTMLInputElement} */
625
+ const siteSearchQuery = siteSearchInput;
626
+
627
+ if (siteSearch == null || siteSearchQuery == null) {
628
+ throw new Error(
629
+ 'Cannot find #site-search or data-site-search-query'
630
+ );
631
+ }
632
+
633
+ siteSearch.addEventListener('submit', function (e) {
634
+ e.preventDefault();
635
+ debounceSearch();
636
+ return false;
637
+ });
638
+
639
+ siteSearchQuery.addEventListener('keyup', function (e) {
640
+ e.preventDefault();
641
+ if (!scrolled) {
642
+ scrolled = true;
643
+ this.scrollIntoView(true);
644
+ }
645
+ debounceSearch();
646
+ return false;
647
+ });
648
+
649
+ const params = new URLSearchParams(window.location.search);
650
+ if (params.has('q')) {
651
+ siteSearchQuery.value = params.get('q') ?? '';
652
+ }
653
+
654
+ for (let key of Object.keys(scoring)) {
655
+ if (params.has(`s_${key}`)) {
656
+ scoring[key] = parseInt(
657
+ params.get(`s_${key}`) ?? scoring[key].toString(),
658
+ 10
659
+ );
660
+ }
661
+ }
662
+
663
+ debounceSearch();
664
+ })
665
+ .catch((error) => {
666
+ console.log(error);
667
+ });
668
+ }
669
+
670
+ if (document.readyState === 'loading') {
671
+ // Loading hasn't finished yet
672
+ document.addEventListener('DOMContentLoaded', initializeSearch);
673
+ } else {
674
+ // `DOMContentLoaded` has already fired
675
+ initializeSearch();
676
+ }
@@ -0,0 +1,14 @@
1
+ /** @format */
2
+
3
+ const synonyms = {
4
+ // Keep me alphabetical
5
+ // Removes join words
6
+ and: '',
7
+ for: '',
8
+ if: '',
9
+ the: '',
10
+ to: '',
11
+ with: '',
12
+ };
13
+
14
+ export { synonyms };
package/src/config.ts CHANGED
@@ -40,7 +40,7 @@ const SITE: Site = {
40
40
  youTubeLinks: ['embed'],
41
41
  headers: ['link'],
42
42
  details: ['tabs'],
43
- search: [],
43
+ search: ['headings'],
44
44
  },
45
45
  images: {
46
46
  // Generated using https://ausi.github.io/respimagelint/
@@ -26,6 +26,11 @@ const canonicalURL = accelerator.urlFormatter.formatUrl(new URL(Astro.url.pathn
26
26
  const socialTitle = await accelerator.markdown.getTextFrom(frontmatter?.title);
27
27
  const title = `${ accelerator.markdown.titleCase(socialTitle) } ${ ((frontmatter.titleAdditional) ? ` ${frontmatter.titleAdditional}` : '') } | ${ SITE.title }`;
28
28
 
29
+ const authorList = accelerator.authors.forPost(frontmatter);
30
+ const authorMeta = (authorList.mainAuthor?.frontmatter?.meta && authorList.mainAuthor?.frontmatter?.meta?.length > 0)
31
+ ? authorList.mainAuthor.frontmatter.meta
32
+ : [];
33
+
29
34
  stats.stop();
30
35
  ---
31
36
  <head>
@@ -52,6 +57,9 @@ stats.stop();
52
57
  <meta property="og:description" content={ frontmatter.description } />
53
58
  <meta property="og:image" content={ canonicalImageSrc } />
54
59
  <meta property="og:image:alt" content={ imageAlt } />
60
+ {authorMeta.map(m =>
61
+ <meta name={ m.name } content={ m.content } />
62
+ )}
55
63
  <meta name="twitter:card" content="summary_large_image" />
56
64
  <meta name="twitter:title" content={ socialTitle } />
57
65
  <meta name="twitter:description" content={ frontmatter.description } />