astro-accelerator 4.0.9 → 4.0.12

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
@@ -1,6 +1,5 @@
1
1
  # Astro Accelerator
2
2
 
3
-
4
3
  [![Deploy and Test](https://github.com/Steve-Fenton/astro-accelerator/actions/workflows/build-astro.yml/badge.svg)](https://github.com/Steve-Fenton/astro-accelerator/actions/workflows/build-astro.yml)
5
4
 
6
5
  Review the documentation at [astro.stevefenton.co.uk](https://astro.stevefenton.co.uk/)
@@ -8,6 +7,21 @@ Review the documentation at [astro.stevefenton.co.uk](https://astro.stevefenton.
8
7
  [![npm](https://img.shields.io/npm/v/astro-accelerator?color=blue&style=plastic)](https://www.npmjs.com/package/astro-accelerator/)
9
8
  [![npm](https://img.shields.io/npm/dm/astro-accelerator?style=plastic)](https://www.npmjs.com/package/astro-accelerator/)
10
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
+
19
+ ## Publish to NPM
20
+
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".
22
+
23
+ The NPM token expires periodically and must be updated in GitHub settings -> Secrets -> Actions.
24
+
11
25
  ## Upgrades
12
26
 
13
27
  - [Upgrading from v3 to v4 of Astro and Astro Accelerator.](https://www.stevefenton.co.uk/blog/2023/12/upgrade-astro-v4/)
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "4.0.9",
2
+ "version": "4.0.12",
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
  },
@@ -55,14 +56,13 @@
55
56
  "src/config.ts",
56
57
  "src/pages/index.md",
57
58
  "src/pages/search.md",
58
- "src/pages/authors/[[]author[]]/[[]page[]].astro",
59
- "src/pages/articles/[[]page[]].astro",
60
- "src/pages/articles/index.astro",
61
- "src/pages/articles/feed.xml.ts",
62
- "src/pages/category/[[]category[]]/[[]page[]].astro",
63
- "src/pages/category/[[]category[]]/index.astro",
64
- "src/pages/tag/[[]tag[]]/[[]page[]].astro",
65
- "src/pages/tag/[[]tag[]]/index.astro",
59
+ "src/pages/articles/*.astro",
60
+ "src/pages/articles/*.ts",
61
+ "src/pages/authors/**/*.astro",
62
+ "src/pages/category/**/*.astro",
63
+ "src/pages/category/**/*.astro",
64
+ "src/pages/tag/**/*.astro",
65
+ "src/pages/tag/**/*.astro",
66
66
  "src/pages/search.json.ts",
67
67
  "src/pages/sitemap.xml.ts",
68
68
  "src/pages/report/*",
@@ -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,79 @@ 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
+ function activateInput() {
116
+ siteSearchWrapper.classList.add('is-active');
117
+ }
118
+
119
+ function deactivateInput() {
120
+ siteSearchWrapper.classList.remove('is-active');
121
+ }
122
+
123
+ function openDropdown() {
124
+ siteSearchElement.classList.add('is-active');
125
+
126
+ requestAnimationFrame(() => {
127
+ const dropdownHeightPercentage = parseFloat(getComputedStyle(siteSearchWrapper).getPropertyValue('--search-dropdown-height'));
128
+ // Convert vh to pixels
129
+ const dropdownHeight = window.innerHeight * (dropdownHeightPercentage / 100) + 32;
130
+ const siteSearchElementRect = siteSearchElement.getBoundingClientRect();
131
+ const offsetFromBottomToElement = window.innerHeight - siteSearchElementRect.bottom;
132
+
133
+ if (offsetFromBottomToElement < dropdownHeight) {
134
+ // Scroll to the siteSearchElement
135
+ siteSearchElement.scrollIntoView({ behavior: 'smooth', block: 'start' });
136
+
137
+ // Delay the overflow to allow for smooth scrolling
138
+ setTimeout(() => {
139
+ document.body.style.overflow = 'hidden';
140
+ }, 300);
141
+ } else {
142
+ // If dropdown is fully visible, no need to adjust scroll but prevent further scrolling
143
+ document.body.style.overflow = 'hidden';
144
+ }
145
+ });
146
+ }
147
+
148
+ function closeDropdown() {
149
+ siteSearchElement.classList.remove('is-active');
150
+ document.body.style.overflow = '';
151
+ }
152
+
153
+ function clearInput() {
154
+ closeDropdown();
155
+ siteSearchInput.value = '';
156
+ siteSearchInput.focus();
157
+ }
158
+
80
159
  /** @type{Synonyms | null} */
81
160
  var _synonyms = null;
82
161
 
@@ -91,7 +170,7 @@ async function getSynonyms() {
91
170
 
92
171
  try {
93
172
  const synonymsModule = await import('./synonyms.js');
94
- _synonyms =synonymsModule.synonyms;
173
+ _synonyms = synonymsModule.synonyms;
95
174
  } catch {
96
175
  _synonyms = {};
97
176
  }
@@ -105,7 +184,7 @@ async function getSynonyms() {
105
184
  */
106
185
  async function replaceSynonyms(queryTerms) {
107
186
  const synonyms = await getSynonyms();
108
-
187
+
109
188
  for (let i = 0; i < queryTerms.length; i++) {
110
189
  const term = queryTerms[i];
111
190
  if (synonyms[term] != null) {
@@ -125,8 +204,17 @@ async function replaceSynonyms(queryTerms) {
125
204
  async function search(s, r) {
126
205
  const numberOfResults = r ?? 12;
127
206
 
207
+ // Add 'is-active' class when search is performed
208
+ if (s && s.trim().length > 0) {
209
+ activateInput();
210
+ openDropdown();
211
+ } else {
212
+ // Remove 'is-active' class when search is cleared
213
+ closeDropdown();
214
+ }
215
+
128
216
  /** @type {SearchEntry[]} */
129
- const needles = [];
217
+ const needles = [];
130
218
 
131
219
  // Clean the input
132
220
  const cleanQuery = sanitise(s);
@@ -149,7 +237,7 @@ async function search(s, r) {
149
237
 
150
238
  const allTerms = queryTerms.concat(stemmedTerms);
151
239
 
152
- cleanQuery.length > 0 && haystack.forEach( (item) => {
240
+ cleanQuery.length > 0 && haystack.forEach((item) => {
153
241
 
154
242
  item.foundWords = 0;
155
243
  item.score = 0;
@@ -163,7 +251,7 @@ async function search(s, r) {
163
251
  if (item.safeTitle === currentQuery) {
164
252
  item.foundWords += 2;
165
253
  }
166
-
254
+
167
255
  if (contains(item.safeTitle, currentQuery)) {
168
256
  item.score = item.score + scoring.phraseTitle;
169
257
  item.foundWords += 2;
@@ -187,7 +275,7 @@ async function search(s, r) {
187
275
  // Part 2 - Term Matches, i.e. "Kitchen" or "Sink"
188
276
 
189
277
  let foundWords = 0;
190
-
278
+
191
279
  allTerms.forEach(term => {
192
280
  let isTermFound = false;
193
281
 
@@ -255,7 +343,7 @@ async function search(s, r) {
255
343
  }
256
344
  });
257
345
 
258
- needles.sort(function (a, b){
346
+ needles.sort(function (a, b) {
259
347
  if (b.foundWords === a.foundWords) {
260
348
  return b.score - a.score;
261
349
  }
@@ -265,12 +353,12 @@ async function search(s, r) {
265
353
 
266
354
  const total = needles.reduce(function (accumulator, needle) {
267
355
  return accumulator + needle.score;
268
- }, 0);
356
+ }, 0);
269
357
 
270
- const results = qs('#site-search-results');
358
+ const results = siteSearchResults;
271
359
 
272
- const ol = document.createElement('ol');
273
- ol.className = 'site-search-results';
360
+ const ul = document.createElement('ul');
361
+ ul.className = 'site-search-results__list';
274
362
 
275
363
  const limit = Math.min(needles.length, numberOfResults);
276
364
 
@@ -282,28 +370,59 @@ async function search(s, r) {
282
370
 
283
371
  const address = new URL(needle.url);
284
372
  const isSameHost = siteUrl.host == address.host;
285
- const url = isSameHost ? address.pathname : needle.url;
373
+ const url = isSameHost ? address.pathname : needle.url;
374
+
375
+ const listElementWrapper = document.createElement('a');
376
+ listElementWrapper.href = url;
377
+ listElementWrapper.className = 'result-wrapper';
286
378
 
287
- const a = document.createElement('a');
379
+ const listElementTitle = document.createElement('span');
288
380
  // Only highlight user query terms, not stemmed terms
289
- a.innerHTML = highlight(needle.title, queryTerms);
290
- a.href = url;
381
+ listElementTitle.innerHTML = highlight(needle.title, queryTerms);
382
+ listElementTitle.className = 'result-title';
291
383
 
292
384
  const path = document.createElement('div');
293
385
  path.className = 'result-path';
294
- path.innerHTML = address.pathname;
295
386
 
296
- const markers = document.createElement('div');
297
- markers.className = 'result-text';
387
+ // Split the path into segments, filter out empty segments (in case of leading slash)
388
+ const segments = address.pathname.split('/').filter(Boolean);
389
+
390
+ segments.forEach((segment, index) => {
391
+ const words = segment.replace(/-/g, ' ').split(' ');
392
+ const processedSegment = words.map((word, index) =>
393
+ index === 0 ? word.charAt(0).toUpperCase() + word.slice(1).toLowerCase() : word.toLowerCase()
394
+ ).join(' ');
395
+
396
+ const segmentSpan = document.createElement('span');
397
+ segmentSpan.className = 'result-path__segment';
398
+ segmentSpan.textContent = processedSegment;
399
+ path.appendChild(segmentSpan);
400
+
401
+ if (index < segments.length - 1) {
402
+ const svgIcon = document.createElement('span');
403
+ svgIcon.className = 'result-path__icon';
404
+ svgIcon.innerHTML = `
405
+ <svg xmlns="http://www.w3.org/2000/svg" width="6" height="10" viewBox="0 0 6 10" fill="none">
406
+ <path d="M1 9L5 5L1 1" stroke="#7C98B4" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>
407
+ </svg>
408
+ `;
409
+ path.appendChild(svgIcon);
410
+ }
411
+ });
412
+
413
+ const listElementDescription = document.createElement('p');
414
+ listElementDescription.className = 'result-description';
298
415
  // Only highlight user query terms, not stemmed terms
299
- markers.innerHTML = highlight(needle.description, queryTerms);
416
+ listElementDescription.innerHTML = highlight(needle.description, queryTerms);
300
417
 
301
418
  const li = document.createElement('li');
419
+ li.classList.add('site-search-results__item');
302
420
  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);
421
+ li.dataset.score = (Math.round((needle.score / total) * 1000) / 1000).toString();
422
+ listElementWrapper.appendChild(path);
423
+ listElementWrapper.appendChild(listElementTitle);
424
+ listElementWrapper.appendChild(listElementDescription);
425
+ li.appendChild(listElementWrapper);
307
426
 
308
427
  if (enabled(f.search, 'headings') && needle.matchedHeadings.length > 0) {
309
428
  const headings = document.createElement('ul');
@@ -325,28 +444,30 @@ async function search(s, r) {
325
444
  li.appendChild(headings);
326
445
  }
327
446
 
328
- ol.appendChild(li);
447
+ ul.appendChild(li);
329
448
  }
330
449
 
331
- var h2 = document.createElement('h2');
332
- h2.innerHTML = needles.length === 0
333
- ? results.dataset.emptytitle || 'No Results'
334
- : results.dataset.title || 'Results';
450
+ let h2;
451
+ if (needles.length === 0) {
452
+ h2 = document.createElement('h2');
453
+ h2.classList.add('search-results__heading');
454
+ h2.innerHTML = results.dataset.emptytitle || 'No Results';
455
+ }
335
456
 
336
457
  const more = document.createElement('button');
337
458
  more.className = 'show-more';
338
459
  more.type = 'button';
339
460
  more.innerHTML = 'See more';
340
- more.addEventListener('click', function() {
461
+ more.addEventListener('click', function (e) {
462
+ e.stopPropagation(); // Prevent the click from closing the dropdown
341
463
  currentQuery = '';
342
464
  const newTotal = numberOfResults + 12;
343
-
344
465
  search(s, newTotal);
345
- })
466
+ });
346
467
 
347
468
  results.innerHTML = '';
348
- results.appendChild(h2);
349
- results.appendChild(ol);
469
+ results.appendChild(ul);
470
+ h2 && results.appendChild(h2);
350
471
 
351
472
  if (needles.length > numberOfResults) {
352
473
  results.appendChild(more);
@@ -361,10 +482,12 @@ async function search(s, r) {
361
482
  var debounceTimer;
362
483
 
363
484
  function debounceSearch() {
364
- var input = /** @type {HTMLInputElement} */(qs('#site-search-query'));
485
+ var input = siteSearchInput;
486
+
487
+ document.body.style.overflow = 'hidden'; // Prevent scrolling when active
365
488
 
366
489
  if (input == null) {
367
- throw new Error('Cannot find #site-search-query');
490
+ throw new Error('Cannot find data-site-search-query');
368
491
  }
369
492
 
370
493
  // Words chained with . are combined, i.e. System.Text is "systemtext"
@@ -379,10 +502,10 @@ function debounceSearch() {
379
502
  }
380
503
 
381
504
  fetch(dataUrl)
382
- .then(function (response) {
505
+ .then(function (response) {
383
506
  return response.json();
384
507
  })
385
- .then(function (data) {
508
+ .then(function (data) {
386
509
  haystack = data;
387
510
  ready = true;
388
511
 
@@ -397,21 +520,21 @@ fetch(dataUrl)
397
520
  }
398
521
 
399
522
  /** @type {HTMLFormElement} */
400
- const siteSearch = qs('#site-search');
523
+ const siteSearch = siteSearchElement;
401
524
 
402
525
  /** @type {HTMLInputElement} */
403
- const siteSearchQuery = qs('#site-search-query');
526
+ const siteSearchQuery = siteSearchInput;
404
527
 
405
528
  if (siteSearch == null || siteSearchQuery == null) {
406
- throw new Error('Cannot find #site-search or #site-search-query');
529
+ throw new Error('Cannot find #site-search or data-site-search-query');
407
530
  }
408
-
531
+
409
532
  siteSearch.addEventListener('submit', function (e) {
410
533
  e.preventDefault();
411
534
  debounceSearch();
412
535
  return false;
413
536
  });
414
-
537
+
415
538
  siteSearchQuery.addEventListener('keyup', function (e) {
416
539
  e.preventDefault();
417
540
  if (!scrolled) {
@@ -429,7 +552,7 @@ fetch(dataUrl)
429
552
 
430
553
  for (let key of Object.keys(scoring)) {
431
554
  if (params.has(`s_${key}`)) {
432
- scoring[key] = parseInt(params.get(`s_${key}`) ?? scoring[key].toString(), 10) ;
555
+ scoring[key] = parseInt(params.get(`s_${key}`) ?? scoring[key].toString(), 10);
433
556
  }
434
557
  }
435
558
 
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,67 @@
1
+ ---
2
+ // warning: This file is overwritten by Astro Accelerator
3
+
4
+ // For listing all articles in this folder
5
+ import { PostFiltering, PostOrdering, Accelerator } from 'astro-accelerator-utils';
6
+ import type { Frontmatter } from 'astro-accelerator-utils/types/Frontmatter';
7
+ import type { MarkdownInstance } from 'astro-accelerator-utils/types/Astro';
8
+ import type { Page } from 'astro';
9
+ import { SITE } from '@config';
10
+ import { Translations, Lang } from '@util/Languages';
11
+ import Default from 'src/layouts/Default.astro';
12
+ import ArticleList from '@components/ArticleList.astro';
13
+ import PagingLinks from '@components/PagingLinks.astro';
14
+
15
+ const accelerator = new Accelerator(SITE);
16
+ const stats = new accelerator.statistics('pages/articles/[page].astro');
17
+ stats.start();
18
+
19
+ const lang = SITE.default.lang;
20
+
21
+ // Props
22
+ type Props = {
23
+ page: Page<MarkdownInstance>;
24
+ headings: { depth: number; slug: string; text: string; }[];
25
+ pubDate: Date;
26
+ };
27
+ const { page, headings, pubDate } = Astro.props satisfies Props;
28
+
29
+ const frontmatter: Frontmatter = {
30
+ layout: 'src/layouts/Default.astro',
31
+ title: 'Articles',
32
+ keywords: `articles`,
33
+ description: `${SITE.title} articles.`,
34
+ pubDate: pubDate
35
+ };
36
+
37
+ // Language
38
+ const _ = Lang(lang);
39
+
40
+ // Logic
41
+ export async function getData() {
42
+ const sourcePosts = await Astro.glob('./**/*.md', './**/*.mdx') as MarkdownInstance[];
43
+ const posts = sourcePosts
44
+ .filter(PostFiltering.isListable)
45
+ .sort(PostOrdering.sortByPubDateDesc);
46
+
47
+ return posts;
48
+ }
49
+
50
+ export async function getStaticPaths({ paginate }: any) {
51
+ let data = await getData();
52
+ return paginate(data, {
53
+ props: { pubDate: data[0].frontmatter.pubDate },
54
+ pageSize: SITE.pageSize
55
+ });
56
+ }
57
+
58
+ // Page Links
59
+ const pageLinks = accelerator.paging.links(SITE.pageLinks, page.lastPage, page.currentPage, page.url.current);
60
+
61
+ stats.stop();
62
+ ---
63
+ <Default frontmatter={ frontmatter } headings={ headings }>
64
+ <h2>{ _(Translations.articles.page_title).replace('{n}', page.currentPage.toString()) }</h2>
65
+ <ArticleList lang={ lang } posts={ page.data } />
66
+ <PagingLinks lang={ lang } page={ page } pageLinks={ pageLinks } />
67
+ </Default>
@@ -0,0 +1,120 @@
1
+ ---
2
+ // warning: This file is overwritten by Astro Accelerator
3
+
4
+ // For listing all articles in this folder
5
+ import { PostFiltering, PostOrdering, Accelerator } from 'astro-accelerator-utils';
6
+ import type { Frontmatter } from 'astro-accelerator-utils/types/Frontmatter';
7
+ import type { MarkdownInstance } from 'astro-accelerator-utils/types/Astro';
8
+ import type { Page } from 'astro';
9
+ import { Translations, Lang } from '@util/Languages';
10
+ import { SITE } from '@config';
11
+ import Default from 'src/layouts/Default.astro';
12
+ import ArticleList from '@components/ArticleList.astro';
13
+ import PagingLinks from '@components/PagingLinks.astro';
14
+
15
+ const accelerator = new Accelerator(SITE);
16
+ const stats = new accelerator.statistics('pages/authors/[author]/[page].astro');
17
+ stats.start();
18
+
19
+ const lang = SITE.default.lang;
20
+ const currentUrl = new URL(Astro.request.url);
21
+ const slug = accelerator.urlFormatter.getAuthorId(currentUrl);
22
+
23
+ const author = accelerator.posts.all()
24
+ .filter(PostFiltering.isAuthor)
25
+ .filter(x => {
26
+ if (!x.url) {
27
+ return false;
28
+ }
29
+
30
+ const url = new URL(x.url, SITE.url);
31
+ return accelerator.urlFormatter.getAuthorId(url) == slug;
32
+ })[0];
33
+
34
+ // Language
35
+ const _ = Lang(lang);
36
+
37
+ // Props
38
+ type Props = {
39
+ page: Page<MarkdownInstance>;
40
+ headings: { depth: number; slug: string; text: string; }[];
41
+ pubDate: Date;
42
+ };
43
+ const { title, page, headings, pubDate } = Astro.props satisfies Props;
44
+
45
+ const authorFrontmatter = {
46
+ ...author.frontmatter,
47
+ titleAdditional: `(${ _(Translations.articles.author) }) ` + _(Translations.articles.page_title).replace('{n}', page.currentPage.toString()),
48
+ pubDate: pubDate
49
+ } as Frontmatter;
50
+
51
+ const text = (author.frontmatter.summary ?? '').replace('\n', '\n\n');
52
+ const authorText = accelerator.markdown.getHtmlFrom(text);
53
+
54
+ // Logic
55
+ type AuthorData = {
56
+ posts: MarkdownInstance[];
57
+ authors: string[];
58
+ }
59
+
60
+ export async function getData() {
61
+ const sourcePosts = await Astro.glob(['../../**/*.md', '../../**/*.mdx']) satisfies MarkdownInstance[];
62
+ const data: AuthorData = { posts: [], authors: []};
63
+
64
+ data.posts = sourcePosts
65
+ .filter(PostFiltering.isListable)
66
+ .sort(PostOrdering.sortByPubDateDesc);
67
+
68
+ data.posts.forEach(p => {
69
+ const auths: string[] = p.frontmatter.authors ?? [];
70
+ if (auths.length == 0) {
71
+ console.log('No authors found', p.url);
72
+ }
73
+ auths.forEach(a => {
74
+ if (!data.authors.includes(a)) {
75
+ data.authors.push(a);
76
+ }
77
+ });
78
+ });
79
+
80
+ return data;
81
+ }
82
+
83
+ export async function getStaticPaths({ paginate }: any) {
84
+ let data = await getData();
85
+
86
+ return data.authors.map(a => {
87
+ const filtered = data.posts.filter(p => {
88
+ const auths: string[] = p.frontmatter.authors ?? [];
89
+ return auths.includes(a);
90
+ });
91
+ return paginate(filtered, {
92
+ params: { author: a.toLowerCase() },
93
+ props: { pubDate: filtered[0].frontmatter.pubDate },
94
+ pageSize: SITE.pageSize
95
+ });
96
+ }).flat();
97
+ }
98
+
99
+ // Page Links
100
+ const pageLinks = accelerator.paging.links(SITE.pageLinks, page.lastPage, page.currentPage, page.url.current);
101
+
102
+ // Breadcrumbs
103
+ const breadcrumbs: { url: string; title: string; ariaCurrent?: string; }[] = []
104
+
105
+ if (page.url.current != pageLinks[0].url) {
106
+ breadcrumbs.push({
107
+ url: page.url.current,
108
+ title: _(Translations.articles.page_title).replace('{n}', page.currentPage.toString()),
109
+ ariaCurrent: 'page',
110
+ });
111
+ }
112
+
113
+ stats.stop();
114
+ ---
115
+ <Default frontmatter={ authorFrontmatter } headings={ headings } breadcrumbs={ breadcrumbs }>
116
+ <Fragment set:html={ authorText } />
117
+ <h2>{ _(Translations.articles.page_title).replace('{n}', page.currentPage.toString()) }</h2>
118
+ <ArticleList lang={ lang } posts={ page.data } />
119
+ <PagingLinks lang={ lang } page={ page } pageLinks={ pageLinks } />
120
+ </Default>
@@ -0,0 +1,124 @@
1
+ ---
2
+ // warning: This file is overwritten by Astro Accelerator
3
+
4
+ // For listing by frontmatter.categories
5
+ import { PostFiltering, PostOrdering, Accelerator } from 'astro-accelerator-utils';
6
+ import type { Frontmatter } from 'astro-accelerator-utils/types/Frontmatter';
7
+ import type { MarkdownInstance } from 'astro-accelerator-utils/types/Astro';
8
+ import type { Page } from 'astro';
9
+ import { Translations, Lang } from '@util/Languages';
10
+ import { SITE } from '@config';
11
+ import Default from 'src/layouts/Default.astro';
12
+ import ArticleList from '@components/ArticleList.astro';
13
+ import PagingLinks from '@components/PagingLinks.astro';
14
+
15
+ const accelerator = new Accelerator(SITE);
16
+ const stats = new accelerator.statistics('pages/authors/[category]/[page].astro');
17
+ stats.start();
18
+
19
+ const lang = SITE.default.lang;
20
+ const currentUrl = new URL(Astro.request.url);
21
+ const slug = currentUrl.pathname.split('/')[3];
22
+
23
+ // Language
24
+ const _ = Lang(lang);
25
+
26
+ // Props
27
+ type Props = {
28
+ title: string;
29
+ page: Page<MarkdownInstance>;
30
+ headings: { depth: number; slug: string; text: string; }[];
31
+ pubDate: Date;
32
+ };
33
+ const { title, page, headings, pubDate } = Astro.props satisfies Props;
34
+
35
+ const frontmatter: Frontmatter = {
36
+ layout: 'src/layouts/Default.astro',
37
+ title: title,
38
+ titleAdditional: `(${ _(Translations.articles.category) }) ` + _(Translations.articles.page_title).replace('{n}', page.currentPage.toString()),
39
+ keywords: `${slug},articles`,
40
+ description: `${SITE.title} ${slug} articles.`,
41
+ pubDate: pubDate
42
+ };
43
+
44
+ // Logic
45
+ type CategoryData = {
46
+ posts: MarkdownInstance[];
47
+ categories: string[];
48
+ }
49
+
50
+ export async function getData() {
51
+ // TODO: Replace with call to Posts.all()
52
+ const sourcePosts = await Astro.glob(['../../**/*.md', '../../**/*.mdx']) as MarkdownInstance[];
53
+
54
+ const data: CategoryData = { posts: [], categories: []};
55
+
56
+ data.posts = sourcePosts
57
+ .filter(PostFiltering.isListable)
58
+ .sort(PostOrdering.sortByPubDateDesc);
59
+
60
+ data.posts.forEach(p => {
61
+ const auths: string[] = p.frontmatter.categories ?? [];
62
+ if (auths.length == 0) {
63
+ console.log('No categories found', p.url);
64
+ }
65
+ auths.forEach(a => {
66
+ if (!data.categories.includes(a)) {
67
+ data.categories.push(a);
68
+ }
69
+ });
70
+ });
71
+
72
+ return data;
73
+ }
74
+
75
+ export async function getStaticPaths({ paginate }: any) {
76
+ let data = await getData();
77
+
78
+ return data.categories.map(c => {
79
+ const filtered = data.posts.filter(p => {
80
+ const cats: string[] = p.frontmatter.categories ?? [];
81
+ return cats.includes(c);
82
+ });
83
+ return paginate(filtered, {
84
+ params: { category: c.toLowerCase().replace(/ /g, '-') },
85
+ props: { title: c, pubDate: filtered[0].frontmatter.pubDate },
86
+ pageSize: SITE.pageSize
87
+ });
88
+ }).flat();
89
+ }
90
+
91
+ // Page Links
92
+ const pageLinks = accelerator.paging.links(SITE.pageLinks, page.lastPage, page.currentPage, page.url.current);
93
+
94
+ // Breadcrumbs
95
+ const breadcrumbs: { url: string; title: string; ariaCurrent?: string; }[] = []
96
+
97
+ if (page.url.current == pageLinks[0].url) {
98
+ breadcrumbs.push({
99
+ url: pageLinks[0].url as string,
100
+ title: title,
101
+ ariaCurrent: 'page'
102
+ });
103
+ }
104
+
105
+ if (page.url.current != pageLinks[0].url) {
106
+ breadcrumbs.push({
107
+ url: pageLinks[0].url as string,
108
+ title: title,
109
+ });
110
+
111
+ breadcrumbs.push({
112
+ url: page.url.current,
113
+ title: _(Translations.articles.page_title).replace('{n}', page.currentPage.toString()),
114
+ ariaCurrent: 'page',
115
+ });
116
+ }
117
+
118
+ stats.stop();
119
+ ---
120
+ <Default frontmatter={ frontmatter } headings={ headings } breadcrumbs={ breadcrumbs }>
121
+ <h2>{ _(Translations.articles.page_title).replace('{n}', page.currentPage.toString()) }</h2>
122
+ <ArticleList lang={ lang } posts={ page.data } />
123
+ <PagingLinks lang={ lang } page={ page } pageLinks={ pageLinks } />
124
+ </Default>
@@ -0,0 +1,20 @@
1
+ ---
2
+ // warning: This file is overwritten by Astro Accelerator
3
+
4
+ import { getData } from './[page].astro';
5
+ import Redirect from 'src/layouts/Redirect.astro'
6
+
7
+ export async function getStaticPaths() {
8
+ let data = await getData();
9
+
10
+ return data.categories.map(item => {
11
+ return { params: { category: item.toLowerCase().replace(' ', '-') }}
12
+ });
13
+ }
14
+
15
+ const frontmatter = {
16
+ redirect: './1/'
17
+ } as any;
18
+ ---
19
+
20
+ <Redirect frontmatter={ frontmatter } headings={ [] } />
@@ -0,0 +1,123 @@
1
+ ---
2
+ // warning: This file is overwritten by Astro Accelerator
3
+
4
+ // For listing by frontmatter.tags
5
+ import { PostFiltering, PostOrdering, Accelerator } from 'astro-accelerator-utils';
6
+ import type { Frontmatter } from 'astro-accelerator-utils/types/Frontmatter';
7
+ import type { MarkdownInstance } from 'astro-accelerator-utils/types/Astro';
8
+ import type { Page } from 'astro';
9
+ import { Translations, Lang } from '@util/Languages';
10
+ import { SITE } from '@config';
11
+ import Default from 'src/layouts/Default.astro';
12
+ import ArticleList from '@components/ArticleList.astro';
13
+ import PagingLinks from '@components/PagingLinks.astro';
14
+
15
+ const accelerator = new Accelerator(SITE);
16
+ const stats = new accelerator.statistics('pages/authors/[tag]/[page].astro');
17
+ stats.start();
18
+
19
+ const lang = SITE.default.lang;
20
+ const currentUrl = new URL(Astro.request.url);
21
+ const slug = currentUrl.pathname.split('/')[3];
22
+
23
+ // Language
24
+ const _ = Lang(lang);
25
+
26
+ // Props
27
+ type Props = {
28
+ title: string;
29
+ page: Page<MarkdownInstance>;
30
+ headings: { depth: number; slug: string; text: string; }[];
31
+ pubDate: Date;
32
+ };
33
+ const { title, page, headings, pubDate } = Astro.props satisfies Props;
34
+
35
+ const frontmatter: Frontmatter = {
36
+ layout: 'src/layouts/Default.astro',
37
+ title: title,
38
+ titleAdditional: `(${ _(Translations.articles.tag) }) ` + _(Translations.articles.page_title).replace('{n}', page.currentPage.toString()),
39
+ keywords: `${slug},articles`,
40
+ description: `${SITE.title} ${slug} articles.`,
41
+ pubDate: pubDate
42
+ };
43
+
44
+ // Logic
45
+ type CacheData = {
46
+ posts: MarkdownInstance[];
47
+ tags: string[];
48
+ }
49
+
50
+ export async function getData() {
51
+ const sourcePosts = await Astro.glob(['../../**/*.md', '../../**/*.mdx']) satisfies MarkdownInstance[];
52
+
53
+ const data: CacheData = { posts: [], tags: []};
54
+
55
+ data.posts = sourcePosts
56
+ .filter(PostFiltering.isListable)
57
+ .sort(PostOrdering.sortByPubDateDesc);
58
+
59
+ data.posts.forEach(p => {
60
+ const auths: string[] = p.frontmatter.tags ?? [];
61
+ if (auths.length == 0) {
62
+ console.log('No categories found', p.url);
63
+ }
64
+ auths.forEach(a => {
65
+ if (!data.tags.includes(a)) {
66
+ data.tags.push(a);
67
+ }
68
+ });
69
+ });
70
+
71
+ return data;
72
+ }
73
+
74
+ export async function getStaticPaths({ paginate }: any) {
75
+ let data = await getData();
76
+
77
+ return data.tags.map(t => {
78
+ const filtered = data.posts.filter(p => {
79
+ const tags: string[] = p.frontmatter.tags ?? [];
80
+ return tags.includes(t);
81
+ });
82
+ return paginate(filtered, {
83
+ params: { tag: t.toLowerCase().replace(/ /g, '-') },
84
+ props: { title: t, pubDate: filtered[0].frontmatter.pubDate },
85
+ pageSize: SITE.pageSize
86
+ });
87
+ }).flat();
88
+ }
89
+
90
+ // Page Links
91
+ const pageLinks = accelerator.paging.links(SITE.pageLinks, page.lastPage, page.currentPage, page.url.current);
92
+
93
+ // Breadcrumbs
94
+ const breadcrumbs: { url: string; title: string; ariaCurrent?: string; }[] = []
95
+
96
+ if (page.url.current == pageLinks[0].url) {
97
+ breadcrumbs.push({
98
+ url: pageLinks[0].url as string,
99
+ title: title,
100
+ ariaCurrent: 'page'
101
+ });
102
+ }
103
+
104
+ if (page.url.current != pageLinks[0].url) {
105
+ breadcrumbs.push({
106
+ url: pageLinks[0].url as string,
107
+ title: title,
108
+ });
109
+
110
+ breadcrumbs.push({
111
+ url: page.url.current,
112
+ title: _(Translations.articles.page_title).replace('{n}', page.currentPage.toString()),
113
+ ariaCurrent: 'page',
114
+ });
115
+ }
116
+
117
+ stats.stop();
118
+ ---
119
+ <Default frontmatter={ frontmatter } headings={ headings } breadcrumbs={ breadcrumbs }>
120
+ <h2>{ _(Translations.articles.page_title).replace('{n}', page.currentPage.toString()) }</h2>
121
+ <ArticleList lang={ lang } posts={ page.data } />
122
+ <PagingLinks lang={ lang } page={ page } pageLinks={ pageLinks } />
123
+ </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>