astro-accelerator 4.0.10 → 4.0.13

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/README.md CHANGED
@@ -7,6 +7,15 @@ Review the documentation at [astro.stevefenton.co.uk](https://astro.stevefenton.
7
7
  [![npm](https://img.shields.io/npm/v/astro-accelerator?color=blue&style=plastic)](https://www.npmjs.com/package/astro-accelerator/)
8
8
  [![npm](https://img.shields.io/npm/dm/astro-accelerator?style=plastic)](https://www.npmjs.com/package/astro-accelerator/)
9
9
 
10
+ ## Image optimization on Linux
11
+
12
+ Currently, to run the image optimization on Linux, you need to force a compatible version of Sharp to be installed. Any suggestions for a better approach would be appreciated:
13
+
14
+ ```bash
15
+ pnpm install --include=optional sharp
16
+ pnpm install --force @img/sharp-linux-x64
17
+ ```
18
+
10
19
  ## Publish to NPM
11
20
 
12
21
  Update the `package.json` with the new version number, and commit the change with the message "Release n.n.n", for example, if the new version is `4.0.9` commit with: "Release 4.0.9".
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "4.0.10",
2
+ "version": "4.0.13",
3
3
  "author": "Steve Fenton",
4
4
  "name": "astro-accelerator",
5
5
  "description": "A super-lightweight, accessible, SEO-friendly starter project for Astro",
@@ -39,6 +39,7 @@
39
39
  "hast-util-from-selector": "^3.0.0",
40
40
  "html-to-text": "^9.0.5",
41
41
  "keyword-extractor": "^0.0.28",
42
+ "optional": "^0.1.4",
42
43
  "remark-directive": "^3.0.0",
43
44
  "sharp": "^0.33.2"
44
45
  },
@@ -604,44 +604,259 @@ nav.site-nav h2 {
604
604
 
605
605
  /* Site Search */
606
606
 
607
- form.site-search {
608
- padding: 1em;
607
+ .site-search__wrapper {
608
+ position: relative;
609
+ width: 100%;
610
+ max-width: 34.75rem;
611
+ transition: max-width var(--search-dropdown-duration) ease-in-out;
609
612
  }
610
613
 
611
- form.site-search div {
612
- display: grid;
613
- grid-template-columns: fit-content(400px) auto 30px;
614
- gap: 1em;
614
+ .site-search__wrapper.is-active {
615
+ max-width: 57.8rem;
615
616
  }
616
617
 
617
- form.site-search label > * {
618
- padding: 0.2em;
618
+ .site-search {
619
+ height: var(--search-height);
620
+ border-radius: var(--search-border-radius);
621
+ border: var(--search-border);
622
+ background-color: var(--white);
623
+ position: relative;
624
+ z-index: 1
619
625
  }
620
626
 
621
- form.site-search #site-search-button {
622
- display: none;
627
+ .site-search-results {
628
+ background-color: var(--white);
629
+ position: absolute;
630
+ top: var(--search-height);
631
+ width: 100%;
632
+ padding: 0;
633
+ border-radius: var(--search-border-radius);
634
+ border-radius: 0.9375rem;
635
+ list-style-type: none;
636
+ overflow-y: scroll;
637
+ transition-property: padding, transform, visibility;
638
+ transition-duration: var(--search-dropdown-duration);
639
+ transition-timing-function: ease-in-out;
640
+ visibility: hidden;
641
+ transform: translateY(0) scaleY(0);
642
+ z-index: 1;
643
+ max-height: var(--search-dropdown-height);
644
+ transform-origin: top;
645
+ will-change: transform;
623
646
  }
624
647
 
625
- #site-search-results {
626
- padding-bottom: 3rem;
648
+ .site-search.is-active+.site-search-results {
649
+ padding: 1rem 0;
650
+ transform: translateY(1.37rem) scaleY(1);
651
+ visibility: visible;
627
652
  }
628
653
 
629
- .site-search-results {
630
- font-size: 1.3rem;
654
+ .site-search-results ul {
655
+ list-style-type: none;
656
+ margin-inline-start: 0;
631
657
  }
632
658
 
633
- .site-search-results a {
659
+ .site-search-results ul:not(.post-list) {
660
+ margin: 0;
661
+ }
662
+
663
+ .site-search-results .show-more {
664
+ border-radius: 4.0625rem;
665
+ background: #00FFA3;
666
+ padding: 0.5rem 0.75rem;
634
667
  display: block;
635
- padding: 0.2em;
668
+ margin: 1rem auto 0;
669
+ cursor: pointer;
636
670
  }
637
671
 
638
- .site-search-results .result-text {
639
- font-size: 1rem;
672
+ .site-search__overlay {
673
+ opacity: 0;
674
+ visibility: hidden;
675
+ background: rgba(12, 26, 36, 0.30);
676
+ position: fixed;
677
+ top: 0;
678
+ left: 0;
679
+ right: 0;
680
+ bottom: 0;
681
+ transition: opacity var(--search-dropdown-duration) ease-in-out;
682
+ z-index: 1;
640
683
  }
641
684
 
642
- .site-search-results .result-path {
643
- font-size: 1rem;
644
- font-family: var(--code-font);
685
+ .site-search__wrapper.is-active .site-search__overlay {
686
+ opacity: 1;
687
+ visibility: visible;
688
+ }
689
+
690
+ .site-search-query {
691
+ width: 100%;
692
+ background: transparent;
693
+ height: 100%;
694
+ padding: 0 1rem;
695
+ width: calc(100% - 2rem);
696
+ }
697
+
698
+ .search-results__heading {
699
+ visibility: hidden;
700
+ }
701
+
702
+ .site-search.is-active+.site-search-results>.search-results__heading {
703
+ visibility: visible;
704
+ }
705
+
706
+ .site-search-results__item {
707
+ background-color: transparent;
708
+ transition-property: background-color, border-color;
709
+ transition-duration: var(--search-dropdown-duration);
710
+ transition-timing-function: ease-in-out;
711
+ border-bottom: 0.0625rem solid;
712
+ border-color: var(--search-item-border-color);
713
+ position: relative;
714
+ }
715
+
716
+ .site-search-results__item:hover {
717
+ --hover-color: rgba(13, 128, 216, 0.07);
718
+ background-color: var(--hover-color);
719
+ border-color: var(--hover-color);
720
+ }
721
+
722
+ .site-search-results__item::after {
723
+ content: "";
724
+ display: inline-block;
725
+ width: 0.75rem;
726
+ height: 1.25rem;
727
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='20' viewBox='0 0 12 20' fill='none'%3E%3Cpath d='M2 18L10 10L2 2' stroke='%230D80D8' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
728
+ background-size: 0.75rem 1.25rem;
729
+ background-repeat: no-repeat;
730
+ position: absolute;
731
+ right: 2rem;
732
+ top: 50%;
733
+ transform: translateY(-50%) translateX(-1rem);
734
+ opacity: 0;
735
+ transition-property: opacity, transform;
736
+ transition-duration: var(--search-dropdown-duration);
737
+ transition-timing-function: ease-in-out;
738
+ }
739
+
740
+ .site-search-results__item:hover::after {
741
+ opacity: 1;
742
+ transform: translateY(-50%) translateX(0);
743
+ }
744
+
745
+ @media (max-width: 930px) {
746
+ .site-search-results__item {
747
+ padding: 1rem;
748
+ }
749
+ }
750
+
751
+ .result-wrapper {
752
+ display: flex;
753
+ padding: 1.5rem 3.6rem 1.75rem;
754
+ text-decoration: none;
755
+ flex-direction: column;
756
+ gap: 0.56rem;
757
+ }
758
+
759
+ .result-wrapper:hover {
760
+ background-color: initial;
761
+ }
762
+
763
+ @media (max-width: 930px) {
764
+ .result-wrapper {
765
+ padding: 1rem;
766
+ }
767
+ }
768
+
769
+ .site-search-results__item:hover .result-wrapper {
770
+ color: initial;
771
+ }
772
+
773
+ .result-wrapper mark {
774
+ color: var(--blue-500);
775
+ }
776
+
777
+ .result-path {
778
+ color: var(--blue-grey-lighter);
779
+ font-size: var(--font-size-small);
780
+ font-weight: 400;
781
+ display: flex;
782
+ gap: 0.5rem;
783
+ flex-wrap: wrap;
784
+ }
785
+
786
+ .result-path__segment:last-child {
787
+ color: var(--blue-grey);
788
+ }
789
+
790
+ .result-title {
791
+ color: var(--blue-midnight);
792
+ font-size: var(--font-size-large);
793
+ font-weight: 700;
794
+ }
795
+
796
+ .result-description {
797
+ color: var(--blue-grey-dark);
798
+ font-size: var(--font-size-medium);
799
+ font-weight: 400;
800
+ }
801
+
802
+ .site-search>fieldset {
803
+ height: 100%;
804
+ display: flex;
805
+ align-items: center;
806
+ padding: 0 1rem;
807
+ }
808
+
809
+ .site-search__remove-btn {
810
+ margin-left: auto;
811
+ background-color: transparent;
812
+ padding: 0.5rem;
813
+ cursor: pointer;
814
+ visibility: hidden;
815
+ transition: transform var(--search-dropdown-duration) ease-in-out;
816
+ }
817
+
818
+ .site-search__remove-btn:hover {
819
+ transform: scale(1.2);
820
+ }
821
+
822
+ .site-search.is-active .site-search__remove-btn {
823
+ visibility: visible;
824
+ }
825
+
826
+ .site-search__mobile {
827
+ display: none;
828
+ }
829
+
830
+ @media (max-width: 930px) {
831
+ .site-header .site-search__wrapper {
832
+ max-width: fit-content;
833
+ }
834
+
835
+ .site-header .site-search {
836
+ display: none;
837
+ }
838
+
839
+ .site-header .site-search__mobile {
840
+ --search-mobile-size: 3rem;
841
+ display: flex;
842
+ width: var(--search-mobile-size);
843
+ height: var(--search-mobile-size);
844
+ border-radius: calc(var(--search-mobile-size) / 2);
845
+ justify-content: center;
846
+ align-items: center;
847
+ border: var(--search-border);
848
+ }
849
+ }
850
+
851
+ .site-search .show-more {
852
+ display: inline-block;
853
+ font-size: var(--font-size-small);
854
+ border-radius: 100px;
855
+ text-decoration: none;
856
+ text-align: center;
857
+ padding: 0.2em 0.6em 0.3em 0.6em;
858
+ color: var(--color-hint);
859
+ background-color: var(--bg-color-hint);
645
860
  }
646
861
 
647
862
  .result-headings li {
@@ -24,7 +24,7 @@
24
24
  --fore-block: #333;
25
25
  --aft-block: #F3F5FF;
26
26
  --icon-block: #4D71FF;
27
-
27
+
28
28
  --fore-table-head: #FDFDFE;
29
29
  --aft-table-head: #2F3141;
30
30
  --fore-table-row-odd: #333;
@@ -54,6 +54,16 @@
54
54
 
55
55
  /* Calculate the mobile width by removing l/r columns and grid gap */
56
56
  --content-width-mobile: calc(100vw - (1rem * 2));
57
+
58
+ /* Search variables */
59
+ --white: #fff;
60
+ --search-height: 3rem;
61
+ --search-border-radius: calc(var(--search-height)/2);
62
+ --search-results-padding: 1rem;
63
+ --search-border: 0.0625rem solid #76A1C2;
64
+ --search-dropdown-height: 65vh;
65
+ --search-dropdown-duration: 0.3s;
66
+ --search-item-border-color: #DAE2E9;
57
67
  }
58
68
 
59
69
  @media (prefers-color-scheme: dark) {
@@ -61,11 +71,11 @@
61
71
  --fore: #CCC;
62
72
  --fore-headings: #CCE;
63
73
  --aft: #333;
64
-
74
+
65
75
  --fore-link: #abb9ef;
66
76
  --fore-link-alt: #abb9ef;
67
77
  --aft-link-alt: #232323;
68
-
78
+
69
79
  --fore-head: #CCC;
70
80
  --aft-head: #222;
71
81
 
@@ -80,10 +90,16 @@
80
90
  --icon-block: #abb9ef;
81
91
 
82
92
  --fore-table-head: #CCC;
83
- --aft-table-head: #222;
93
+ --aft-table-head: #222;
84
94
  --fore-table-row-odd: #CCC;
85
95
  --aft-table-row-odd: #333;
86
96
  --fore-table-row-even: #CCC;
87
97
  --aft-table-row-even: #444;
98
+
99
+
100
+ /* Search variables */
101
+ --white: var(--aft);
102
+ --search-border: 0.0625rem solid var(--link-alt-head);
103
+ --search-item-border-color: var(--link-head);
88
104
  }
89
105
  }
@@ -57,10 +57,16 @@ function enabled(settings, option) {
57
57
  } Synonyms
58
58
  */
59
59
 
60
+ const siteSearchInput = qs('[data-site-search-query]');
61
+ const siteSearchWrapper = qs('[data-site-search-wrapper]');
62
+ const siteSearchElement = qs('[data-site-search]');
63
+ const siteSearchResults = qs('[data-site-search-results');
64
+ const removeSearchButton = qs('[data-site-search-remove]');
65
+
60
66
  /** @type {SearchEntry[]} */
61
67
  var haystack = [];
62
68
  var currentQuery = '';
63
- var dataUrl = qs('#site-search').dataset.sourcedata;
69
+ var dataUrl = siteSearchElement.dataset.sourcedata;
64
70
 
65
71
  var scoring = {
66
72
  depth: 5,
@@ -77,6 +83,106 @@ var scoring = {
77
83
  var ready = false;
78
84
  var scrolled = false;
79
85
 
86
+ siteSearchInput.addEventListener('focus', () => activateInput());
87
+
88
+ // Close the dropdown upon clicking outside the search
89
+ document.addEventListener('click', function (e) {
90
+ if (!siteSearchElement.contains(e.target) && !siteSearchResults.contains(e.target)) {
91
+ closeDropdown();
92
+
93
+ const duration = getComputedStyle(siteSearchWrapper).getPropertyValue('--search-dropdown-duration');
94
+
95
+ // Convert duration to milliseconds for setTimeout
96
+ const durationMs = parseFloat(duration) * (duration.endsWith('ms') ? 1 : 1000);
97
+
98
+ setTimeout(() => {
99
+ deactivateInput();
100
+ }, durationMs);
101
+ }
102
+ });
103
+
104
+ // Reopen the dropdown upon clicking the input after it has been closed
105
+ siteSearchInput.addEventListener('click', () => {
106
+ if (siteSearchInput.value.trim() !== '') {
107
+ activateInput();
108
+ openDropdown();
109
+ }
110
+ });
111
+
112
+ // Clear the search input
113
+ removeSearchButton.addEventListener('click', () => clearInput());
114
+
115
+ // Dropdown accessibility controls
116
+ document.addEventListener('keydown', function (e) {
117
+ if (e.key === 'Escape' && siteSearchWrapper.classList.contains('is-active')) {
118
+ closeDropdown();
119
+ deactivateInput();
120
+ }
121
+
122
+ // Only proceed if search is active
123
+ if (!siteSearchWrapper.classList.contains('is-active')) return;
124
+
125
+ const firstElement = siteSearchInput;
126
+ const lastElement = siteSearchResults.querySelector('button');
127
+
128
+ if (e.key === 'Tab') {
129
+ if (e.shiftKey && document.activeElement === firstElement) {
130
+ // If shift+tab is pressed on the first focusable element, move to the last
131
+ e.preventDefault();
132
+ if (lastElement) lastElement.focus();
133
+ } else if (!e.shiftKey && document.activeElement === lastElement) {
134
+ // If tab is pressed on the last focusable element, move to the first
135
+ e.preventDefault();
136
+ firstElement.focus();
137
+ }
138
+ }
139
+ });
140
+
141
+ function activateInput() {
142
+ siteSearchWrapper.classList.add('is-active');
143
+ }
144
+
145
+ function deactivateInput() {
146
+ siteSearchWrapper.classList.remove('is-active');
147
+ }
148
+
149
+ function openDropdown() {
150
+ siteSearchElement.classList.add('is-active');
151
+
152
+ requestAnimationFrame(() => {
153
+ const dropdownHeightPercentage = parseFloat(getComputedStyle(siteSearchWrapper).getPropertyValue('--search-dropdown-height'));
154
+ // Convert vh to pixels
155
+ const dropdownHeight = window.innerHeight * (dropdownHeightPercentage / 100) + 32;
156
+ const siteSearchElementRect = siteSearchElement.getBoundingClientRect();
157
+ const offsetFromBottomToElement = window.innerHeight - siteSearchElementRect.bottom;
158
+
159
+ if (offsetFromBottomToElement < dropdownHeight) {
160
+ // Scroll to the siteSearchElement
161
+ siteSearchElement.scrollIntoView({ behavior: 'smooth', block: 'start' });
162
+
163
+ // Delay the overflow to allow for smooth scrolling
164
+ setTimeout(() => {
165
+ document.body.style.overflow = 'hidden';
166
+ }, 300);
167
+ } else {
168
+ // If dropdown is fully visible, no need to adjust scroll but prevent further scrolling
169
+ document.body.style.overflow = 'hidden';
170
+ }
171
+ });
172
+ }
173
+
174
+ function closeDropdown() {
175
+ siteSearchElement.classList.remove('is-active');
176
+ document.body.style.overflow = '';
177
+ siteSearchInput.blur();
178
+ }
179
+
180
+ function clearInput() {
181
+ closeDropdown();
182
+ siteSearchInput.value = '';
183
+ siteSearchInput.focus();
184
+ }
185
+
80
186
  /** @type{Synonyms | null} */
81
187
  var _synonyms = null;
82
188
 
@@ -91,7 +197,7 @@ async function getSynonyms() {
91
197
 
92
198
  try {
93
199
  const synonymsModule = await import('./synonyms.js');
94
- _synonyms =synonymsModule.synonyms;
200
+ _synonyms = synonymsModule.synonyms;
95
201
  } catch {
96
202
  _synonyms = {};
97
203
  }
@@ -105,7 +211,7 @@ async function getSynonyms() {
105
211
  */
106
212
  async function replaceSynonyms(queryTerms) {
107
213
  const synonyms = await getSynonyms();
108
-
214
+
109
215
  for (let i = 0; i < queryTerms.length; i++) {
110
216
  const term = queryTerms[i];
111
217
  if (synonyms[term] != null) {
@@ -125,8 +231,17 @@ async function replaceSynonyms(queryTerms) {
125
231
  async function search(s, r) {
126
232
  const numberOfResults = r ?? 12;
127
233
 
234
+ // Add 'is-active' class when search is performed
235
+ if (s && s.trim().length > 0) {
236
+ activateInput();
237
+ openDropdown();
238
+ } else {
239
+ // Remove 'is-active' class when search is cleared
240
+ closeDropdown();
241
+ }
242
+
128
243
  /** @type {SearchEntry[]} */
129
- const needles = [];
244
+ const needles = [];
130
245
 
131
246
  // Clean the input
132
247
  const cleanQuery = sanitise(s);
@@ -149,7 +264,7 @@ async function search(s, r) {
149
264
 
150
265
  const allTerms = queryTerms.concat(stemmedTerms);
151
266
 
152
- cleanQuery.length > 0 && haystack.forEach( (item) => {
267
+ cleanQuery.length > 0 && haystack.forEach((item) => {
153
268
 
154
269
  item.foundWords = 0;
155
270
  item.score = 0;
@@ -163,7 +278,7 @@ async function search(s, r) {
163
278
  if (item.safeTitle === currentQuery) {
164
279
  item.foundWords += 2;
165
280
  }
166
-
281
+
167
282
  if (contains(item.safeTitle, currentQuery)) {
168
283
  item.score = item.score + scoring.phraseTitle;
169
284
  item.foundWords += 2;
@@ -187,7 +302,7 @@ async function search(s, r) {
187
302
  // Part 2 - Term Matches, i.e. "Kitchen" or "Sink"
188
303
 
189
304
  let foundWords = 0;
190
-
305
+
191
306
  allTerms.forEach(term => {
192
307
  let isTermFound = false;
193
308
 
@@ -255,7 +370,7 @@ async function search(s, r) {
255
370
  }
256
371
  });
257
372
 
258
- needles.sort(function (a, b){
373
+ needles.sort(function (a, b) {
259
374
  if (b.foundWords === a.foundWords) {
260
375
  return b.score - a.score;
261
376
  }
@@ -265,12 +380,12 @@ async function search(s, r) {
265
380
 
266
381
  const total = needles.reduce(function (accumulator, needle) {
267
382
  return accumulator + needle.score;
268
- }, 0);
383
+ }, 0);
269
384
 
270
- const results = qs('#site-search-results');
385
+ const results = siteSearchResults;
271
386
 
272
- const ol = document.createElement('ol');
273
- ol.className = 'site-search-results';
387
+ const ul = document.createElement('ul');
388
+ ul.className = 'site-search-results__list';
274
389
 
275
390
  const limit = Math.min(needles.length, numberOfResults);
276
391
 
@@ -282,28 +397,59 @@ async function search(s, r) {
282
397
 
283
398
  const address = new URL(needle.url);
284
399
  const isSameHost = siteUrl.host == address.host;
285
- const url = isSameHost ? address.pathname : needle.url;
400
+ const url = isSameHost ? address.pathname : needle.url;
401
+
402
+ const listElementWrapper = document.createElement('a');
403
+ listElementWrapper.href = url;
404
+ listElementWrapper.className = 'result-wrapper';
286
405
 
287
- const a = document.createElement('a');
406
+ const listElementTitle = document.createElement('span');
288
407
  // Only highlight user query terms, not stemmed terms
289
- a.innerHTML = highlight(needle.title, queryTerms);
290
- a.href = url;
408
+ listElementTitle.innerHTML = highlight(needle.title, queryTerms);
409
+ listElementTitle.className = 'result-title';
291
410
 
292
411
  const path = document.createElement('div');
293
412
  path.className = 'result-path';
294
- path.innerHTML = address.pathname;
295
413
 
296
- const markers = document.createElement('div');
297
- markers.className = 'result-text';
414
+ // Split the path into segments, filter out empty segments (in case of leading slash)
415
+ const segments = address.pathname.split('/').filter(Boolean);
416
+
417
+ segments.forEach((segment, index) => {
418
+ const words = segment.replace(/-/g, ' ').split(' ');
419
+ const processedSegment = words.map((word, index) =>
420
+ index === 0 ? word.charAt(0).toUpperCase() + word.slice(1).toLowerCase() : word.toLowerCase()
421
+ ).join(' ');
422
+
423
+ const segmentSpan = document.createElement('span');
424
+ segmentSpan.className = 'result-path__segment';
425
+ segmentSpan.textContent = processedSegment;
426
+ path.appendChild(segmentSpan);
427
+
428
+ if (index < segments.length - 1) {
429
+ const svgIcon = document.createElement('span');
430
+ svgIcon.className = 'result-path__icon';
431
+ svgIcon.innerHTML = `
432
+ <svg xmlns="http://www.w3.org/2000/svg" width="6" height="10" viewBox="0 0 6 10" fill="none">
433
+ <path d="M1 9L5 5L1 1" stroke="#7C98B4" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>
434
+ </svg>
435
+ `;
436
+ path.appendChild(svgIcon);
437
+ }
438
+ });
439
+
440
+ const listElementDescription = document.createElement('p');
441
+ listElementDescription.className = 'result-description';
298
442
  // Only highlight user query terms, not stemmed terms
299
- markers.innerHTML = highlight(needle.description, queryTerms);
443
+ listElementDescription.innerHTML = highlight(needle.description, queryTerms);
300
444
 
301
445
  const li = document.createElement('li');
446
+ li.classList.add('site-search-results__item');
302
447
  li.dataset.words = needle.foundWords.toString();
303
- li.dataset.score = (Math.round((needle.score/ total) * 1000) / 1000).toString();
304
- li.appendChild(a);
305
- li.appendChild(path);
306
- li.appendChild(markers);
448
+ li.dataset.score = (Math.round((needle.score / total) * 1000) / 1000).toString();
449
+ listElementWrapper.appendChild(path);
450
+ listElementWrapper.appendChild(listElementTitle);
451
+ listElementWrapper.appendChild(listElementDescription);
452
+ li.appendChild(listElementWrapper);
307
453
 
308
454
  if (enabled(f.search, 'headings') && needle.matchedHeadings.length > 0) {
309
455
  const headings = document.createElement('ul');
@@ -325,28 +471,30 @@ async function search(s, r) {
325
471
  li.appendChild(headings);
326
472
  }
327
473
 
328
- ol.appendChild(li);
474
+ ul.appendChild(li);
329
475
  }
330
476
 
331
- var h2 = document.createElement('h2');
332
- h2.innerHTML = needles.length === 0
333
- ? results.dataset.emptytitle || 'No Results'
334
- : results.dataset.title || 'Results';
477
+ let h2;
478
+ if (needles.length === 0) {
479
+ h2 = document.createElement('h2');
480
+ h2.classList.add('search-results__heading');
481
+ h2.innerHTML = results.dataset.emptytitle || 'No Results';
482
+ }
335
483
 
336
484
  const more = document.createElement('button');
337
485
  more.className = 'show-more';
338
486
  more.type = 'button';
339
487
  more.innerHTML = 'See more';
340
- more.addEventListener('click', function() {
488
+ more.addEventListener('click', function (e) {
489
+ e.stopPropagation(); // Prevent the click from closing the dropdown
341
490
  currentQuery = '';
342
491
  const newTotal = numberOfResults + 12;
343
-
344
492
  search(s, newTotal);
345
- })
493
+ });
346
494
 
347
495
  results.innerHTML = '';
348
- results.appendChild(h2);
349
- results.appendChild(ol);
496
+ results.appendChild(ul);
497
+ h2 && results.appendChild(h2);
350
498
 
351
499
  if (needles.length > numberOfResults) {
352
500
  results.appendChild(more);
@@ -361,10 +509,12 @@ async function search(s, r) {
361
509
  var debounceTimer;
362
510
 
363
511
  function debounceSearch() {
364
- var input = /** @type {HTMLInputElement} */(qs('#site-search-query'));
512
+ var input = siteSearchInput;
513
+
514
+ document.body.style.overflow = 'hidden'; // Prevent scrolling when active
365
515
 
366
516
  if (input == null) {
367
- throw new Error('Cannot find #site-search-query');
517
+ throw new Error('Cannot find data-site-search-query');
368
518
  }
369
519
 
370
520
  // Words chained with . are combined, i.e. System.Text is "systemtext"
@@ -379,10 +529,10 @@ function debounceSearch() {
379
529
  }
380
530
 
381
531
  fetch(dataUrl)
382
- .then(function (response) {
532
+ .then(function (response) {
383
533
  return response.json();
384
534
  })
385
- .then(function (data) {
535
+ .then(function (data) {
386
536
  haystack = data;
387
537
  ready = true;
388
538
 
@@ -397,21 +547,21 @@ fetch(dataUrl)
397
547
  }
398
548
 
399
549
  /** @type {HTMLFormElement} */
400
- const siteSearch = qs('#site-search');
550
+ const siteSearch = siteSearchElement;
401
551
 
402
552
  /** @type {HTMLInputElement} */
403
- const siteSearchQuery = qs('#site-search-query');
553
+ const siteSearchQuery = siteSearchInput;
404
554
 
405
555
  if (siteSearch == null || siteSearchQuery == null) {
406
- throw new Error('Cannot find #site-search or #site-search-query');
556
+ throw new Error('Cannot find #site-search or data-site-search-query');
407
557
  }
408
-
558
+
409
559
  siteSearch.addEventListener('submit', function (e) {
410
560
  e.preventDefault();
411
561
  debounceSearch();
412
562
  return false;
413
563
  });
414
-
564
+
415
565
  siteSearchQuery.addEventListener('keyup', function (e) {
416
566
  e.preventDefault();
417
567
  if (!scrolled) {
@@ -429,7 +579,7 @@ fetch(dataUrl)
429
579
 
430
580
  for (let key of Object.keys(scoring)) {
431
581
  if (params.has(`s_${key}`)) {
432
- scoring[key] = parseInt(params.get(`s_${key}`) ?? scoring[key].toString(), 10) ;
582
+ scoring[key] = parseInt(params.get(`s_${key}`) ?? scoring[key].toString(), 10);
433
583
  }
434
584
  }
435
585
 
package/src/config.ts CHANGED
@@ -38,7 +38,7 @@ const SITE: Site = {
38
38
  youTubeLinks: ['embed'],
39
39
  headers: ['link'],
40
40
  details: ['tabs'],
41
- search: ['dialog','headings'],
41
+ search: [],
42
42
  },
43
43
  images: {
44
44
  // Generated using https://ausi.github.io/respimagelint/
@@ -1,8 +1,9 @@
1
1
  ---
2
- // warning: This file is overwritten by Astro Accelerator
3
-
4
2
  import type { Frontmatter } from 'astro-accelerator-utils/types/Frontmatter';
5
- import Search from '@layouts/Search.astro';
3
+ import { SITE } from '@config';
4
+ import { Lang } from '@util/Languages';
5
+ import Default from './Default.astro';
6
+ import Search from '../themes/accelerator/components/Search.astro';
6
7
 
7
8
  // Properties
8
9
  type Props = {
@@ -10,7 +11,12 @@ type Props = {
10
11
  headings: { depth: number; slug: string; text: string; }[];
11
12
  }
12
13
  const { frontmatter, headings } = Astro.props satisfies Props;
14
+ const lang = frontmatter.lang ?? SITE.default.lang;
15
+
16
+ // Language
17
+ const _ = Lang(lang);
13
18
  ---
14
- <Search frontmatter={ frontmatter } headings={ headings }>
19
+ <Default frontmatter={ frontmatter } headings={ headings }>
15
20
  <slot />
16
- </Search>
21
+ <Search lang={ lang }/>
22
+ </Default>
@@ -0,0 +1,99 @@
1
+ ---
2
+ import { Accelerator, PostFiltering } from "astro-accelerator-utils";
3
+ import { Translations, Lang } from "@util/Languages";
4
+ import { SITE } from "@config";
5
+
6
+ // Properties
7
+ type Props = {
8
+ lang: string;
9
+ };
10
+ const { lang } = Astro.props satisfies Props;
11
+
12
+ // Language
13
+ const _ = Lang(lang);
14
+
15
+ // Logic
16
+ const siteUrl = Astro.site ? Astro.site.href : "";
17
+ const accelerator = new Accelerator(SITE);
18
+ const search =
19
+ accelerator.posts.all().filter(PostFiltering.isSearch).shift() ?? null;
20
+ const searchUrl =
21
+ (search && accelerator.urlFormatter.formatAddress(search.url)) ||
22
+ SITE.search.fallbackUrl;
23
+ ---
24
+
25
+ <div class="site-search__wrapper" data-site-search-wrapper>
26
+ <div class="site-search__overlay"></div>
27
+ <form
28
+ method="GET"
29
+ action={SITE.search.fallbackUrl ?? "https://www.google.com/search"}
30
+ role="search"
31
+ class="site-search"
32
+ autocomplete="off"
33
+ data-sourcedata={SITE.subfolder + "/search.json"}
34
+ data-site-search
35
+ >
36
+ <fieldset>
37
+ <input
38
+ type="hidden"
39
+ name={SITE.search.fallbackSite ?? "q"}
40
+ value={"site:" + siteUrl}
41
+ />
42
+ <svg
43
+ xmlns="http://www.w3.org/2000/svg"
44
+ width="20"
45
+ height="19"
46
+ viewBox="0 0 20 19"
47
+ fill="none"
48
+ >
49
+ <path
50
+ d="M19.0524 16.4267L15.3727 12.7273C15.2067 12.5603 14.9815 12.4675 14.7453 12.4675H14.1437C15.1624 11.1577 15.7676 9.5102 15.7676 7.718C15.7676 3.45455 12.3316 0 8.09097 0C3.85035 0 0.414307 3.45455 0.414307 7.718C0.414307 11.9814 3.85035 15.436 8.09097 15.436C9.87358 15.436 11.5123 14.8275 12.8151 13.8033V14.4082C12.8151 14.6456 12.9073 14.872 13.0734 15.039L16.7531 18.7384C17.1 19.0872 17.661 19.0872 18.0042 18.7384L19.0487 17.6883C19.3956 17.3395 19.3956 16.7755 19.0524 16.4267ZM8.09097 12.4675C5.48164 12.4675 3.36687 10.3451 3.36687 7.718C3.36687 5.09462 5.47795 2.96846 8.09097 2.96846C10.7003 2.96846 12.8151 5.09091 12.8151 7.718C12.8151 10.3414 10.704 12.4675 8.09097 12.4675Z"
51
+ fill="#274B66"></path>
52
+ </svg>
53
+ <input
54
+ type="text"
55
+ name={SITE.search.fallbackSite ?? "q"}
56
+ class="site-search-query"
57
+ placeholder={_(Translations.search.search_for)}
58
+ spellcheck="true"
59
+ autocomplete="off"
60
+ data-site-search-query
61
+ />
62
+ <button class="site-search__remove-btn" data-site-search-remove>
63
+ <svg
64
+ xmlns="http://www.w3.org/2000/svg"
65
+ width="11"
66
+ height="11"
67
+ viewBox="0 0 11 11"
68
+ fill="none"
69
+ >
70
+ <path
71
+ d="M7.585 5.5L10.7122 2.37281C11.0959 1.98906 11.0959 1.36687 10.7122 0.982812L10.0172 0.287813C9.63344 -0.0959375 9.01125 -0.0959375 8.62719 0.287813L5.5 3.415L2.37281 0.287813C1.98906 -0.0959375 1.36688 -0.0959375 0.982813 0.287813L0.287813 0.982812C-0.0959375 1.36656 -0.0959375 1.98875 0.287813 2.37281L3.415 5.5L0.287813 8.62719C-0.0959375 9.01094 -0.0959375 9.63312 0.287813 10.0172L0.982813 10.7122C1.36656 11.0959 1.98906 11.0959 2.37281 10.7122L5.5 7.585L8.62719 10.7122C9.01094 11.0959 9.63344 11.0959 10.0172 10.7122L10.7122 10.0172C11.0959 9.63344 11.0959 9.01125 10.7122 8.62719L7.585 5.5Z"
72
+ fill="#355670"></path>
73
+ </svg>
74
+ </button>
75
+ </fieldset>
76
+ </form>
77
+ <div
78
+ class="site-search-results"
79
+ data-title={_(Translations.search.results_title)}
80
+ data-emptytitle={_(Translations.search.no_results_title)}
81
+ data-site-search-results
82
+ >
83
+ </div>
84
+ <a href={searchUrl} class="site-search__mobile">
85
+ <svg
86
+ xmlns="http://www.w3.org/2000/svg"
87
+ width="20"
88
+ height="19"
89
+ viewBox="0 0 20 19"
90
+ fill="none"
91
+ >
92
+ <path
93
+ d="M19.0524 16.4267L15.3727 12.7273C15.2067 12.5603 14.9815 12.4675 14.7453 12.4675H14.1437C15.1624 11.1577 15.7676 9.5102 15.7676 7.718C15.7676 3.45455 12.3316 0 8.09097 0C3.85035 0 0.414307 3.45455 0.414307 7.718C0.414307 11.9814 3.85035 15.436 8.09097 15.436C9.87358 15.436 11.5123 14.8275 12.8151 13.8033V14.4082C12.8151 14.6456 12.9073 14.872 13.0734 15.039L16.7531 18.7384C17.1 19.0872 17.661 19.0872 18.0042 18.7384L19.0487 17.6883C19.3956 17.3395 19.3956 16.7755 19.0524 16.4267ZM8.09097 12.4675C5.48164 12.4675 3.36687 10.3451 3.36687 7.718C3.36687 5.09462 5.47795 2.96846 8.09097 2.96846C10.7003 2.96846 12.8151 5.09091 12.8151 7.718C12.8151 10.3414 10.704 12.4675 8.09097 12.4675Z"
94
+ fill="#274B66"></path>
95
+ </svg>
96
+ </a>
97
+ </div>
98
+
99
+ <script src={SITE.subfolder + "/js/search.js"} type="module" async></script>