astro-accelerator 5.9.10 → 5.9.11

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": "5.9.10",
2
+ "version": "5.9.11",
3
3
  "author": "Steve Fenton",
4
4
  "name": "astro-accelerator",
5
5
  "description": "A super-lightweight, accessible, SEO-friendly starter project for Astro",
@@ -637,7 +637,7 @@ nav.site-nav h2 {
637
637
  z-index: 1;
638
638
  }
639
639
 
640
- .site-search-results {
640
+ header .site-search-results {
641
641
  background-color: var(--white);
642
642
  position: absolute;
643
643
  top: var(--search-height);
@@ -667,7 +667,7 @@ nav.site-nav h2 {
667
667
  margin-inline-start: 0;
668
668
  }
669
669
 
670
- .site-search-results ul:not(.post-list):not(.result-headings) {
670
+ header .site-search-results ul:not(.post-list):not(.result-headings) {
671
671
  margin: 0;
672
672
  max-height: var(--search-dropdown-height);
673
673
  overflow-y: scroll;
@@ -760,7 +760,6 @@ nav.site-nav h2 {
760
760
 
761
761
  .result-wrapper {
762
762
  display: flex;
763
- padding: 1.5rem 3.6rem 1.75rem;
764
763
  text-decoration: none;
765
764
  flex-direction: column;
766
765
  gap: 0.56rem;
@@ -770,6 +769,10 @@ nav.site-nav h2 {
770
769
  }
771
770
  }
772
771
 
772
+ header .result-wrapper {
773
+ padding: 1.5rem 3.6rem 1.75rem;
774
+ }
775
+
773
776
  .result-wrapper:hover {
774
777
  background-color: initial;
775
778
  }
@@ -882,10 +885,13 @@ nav.site-nav h2 {
882
885
 
883
886
  .result-headings li {
884
887
  text-align: left;
885
- padding: 0.1rem 3.6rem;
886
888
  font-size: 1rem;
887
889
  }
888
890
 
891
+ header .result-headings li {
892
+ padding: 0.1rem 3.6rem;
893
+ }
894
+
889
895
  .show-more {
890
896
  padding: 0.5em 1em;
891
897
  background-color: var(--fore-link);
@@ -9,7 +9,6 @@
9
9
 
10
10
  import { qs } from './modules/query.js';
11
11
  import { raiseEvent } from './modules/events.js';
12
- import { removeScroll, resetScroll } from './modules/scrollbar.js';
13
12
  import { contains, sanitise, explode, highlight } from './modules/string.js';
14
13
  import { stemmer } from './modules/stemmer.js';
15
14
 
@@ -158,7 +157,6 @@ function initializeSearch() {
158
157
  return;
159
158
  }
160
159
  siteSearchWrapper.classList.add('is-active');
161
- removeScroll();
162
160
  openDropdown();
163
161
  }
164
162
 
@@ -167,7 +165,6 @@ function initializeSearch() {
167
165
  return;
168
166
  }
169
167
  siteSearchWrapper.classList.remove('is-active');
170
- resetScroll();
171
168
  }
172
169
 
173
170
  function openDropdown() {
@@ -1,676 +0,0 @@
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
- }