astro-accelerator 0.0.102 → 0.0.104

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": "0.0.102",
2
+ "version": "0.0.104",
3
3
  "author": "Steve Fenton",
4
4
  "name": "astro-accelerator",
5
5
  "description": "A super-lightweight, accessible, SEO-friendly starter project for Astro",
@@ -14,11 +14,11 @@ html {
14
14
  outline: 0;
15
15
  padding: 0;
16
16
  vertical-align: baseline;
17
- }
17
+ }
18
18
 
19
- html {
19
+ html {
20
20
  scroll-padding-top: 5em;
21
- }
21
+ }
22
22
 
23
23
  body {
24
24
  background-color: var(--aft);
@@ -642,6 +642,28 @@ form.site-search button {
642
642
  font-family: var(--code-font);
643
643
  }
644
644
 
645
+ .result-headings li {
646
+ display: none;
647
+ }
648
+
649
+ .result-headings li:nth-child(-n+3) {
650
+ display: block;
651
+ }
652
+
653
+ .result-headings:has(li:nth-child(n+4))::after {
654
+ content: 'See more >';
655
+ color: var(--fore-link);
656
+ text-decoration: underline;
657
+ }
658
+
659
+ .result-headings:focus-within li {
660
+ display: block;
661
+ }
662
+
663
+ .result-headings:focus-within::after {
664
+ display: none;
665
+ }
666
+
645
667
  .show-more {
646
668
  padding: 0.5em 1em;
647
669
  background-color: var(--fore-link);
@@ -43,7 +43,7 @@ function highlight(string, terms) {
43
43
  * Simplifies a string to plain lower case, removing diacritic characters and hyphens
44
44
  * This means a search for "co-op" will be found in "COOP" and "Café" will be found in "cafe"
45
45
  * @param {string} string
46
- * @returns
46
+ * @returns {string}
47
47
  */
48
48
  function sanitise(string) {
49
49
  // @ts-ignore
@@ -31,6 +31,194 @@ import { contains, containsWord, sanitise, explode, highlight } from './modules/
31
31
  } SearchEntry
32
32
  */
33
33
 
34
+ /**
35
+ * Removes "morphological and inflexional endings" from words
36
+ * See: http://www.tartarus.org/~martin/PorterStemmer
37
+ */
38
+ const stemmer = (function () {
39
+ const step2list = {
40
+ "ational": "ate",
41
+ "tional": "tion",
42
+ "enci": "ence",
43
+ "anci": "ance",
44
+ "izer": "ize",
45
+ "bli": "ble",
46
+ "alli": "al",
47
+ "entli": "ent",
48
+ "eli": "e",
49
+ "ousli": "ous",
50
+ "ization": "ize",
51
+ "ation": "ate",
52
+ "ator": "ate",
53
+ "alism": "al",
54
+ "iveness": "ive",
55
+ "fulness": "ful",
56
+ "ousness": "ous",
57
+ "aliti": "al",
58
+ "iviti": "ive",
59
+ "biliti": "ble",
60
+ "logi": "log"
61
+ };
62
+
63
+ const step3list = {
64
+ "icate": "ic",
65
+ "ative": "",
66
+ "alize": "al",
67
+ "iciti": "ic",
68
+ "ical": "ic",
69
+ "ful": "",
70
+ "ness": ""
71
+ };
72
+
73
+ const c = "[^aeiou]", // consonant
74
+ v = "[aeiouy]", // vowel
75
+ C = c + "[^aeiouy]*", // consonant sequence
76
+ V = v + "[aeiou]*", // vowel sequence
77
+
78
+ mgr0 = "^(" + C + ")?" + V + C, // [C]VC... is m>0
79
+ meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$", // [C]VC[V] is m=1
80
+ mgr1 = "^(" + C + ")?" + V + C + V + C, // [C]VCVC... is m>1
81
+ s_v = "^(" + C + ")?" + v; // vowel in stem
82
+
83
+ /**
84
+ * @param {string} w
85
+ * @returns {string}
86
+ */
87
+ return function (w) {
88
+ var stem,
89
+ suffix,
90
+ firstch,
91
+ re,
92
+ re2,
93
+ re3,
94
+ re4,
95
+ origword = w;
96
+
97
+ if (w.length < 3) { return w; }
98
+
99
+ firstch = w.substring(0, 1);
100
+
101
+ if (firstch == "y") {
102
+ w = firstch.toUpperCase() + w.substring(1, w.length);
103
+ }
104
+
105
+ // Step 1a
106
+ re = /^(.+?)(ss|i)es$/;
107
+ re2 = /^(.+?)([^s])s$/;
108
+
109
+ if (re.test(w)) {
110
+ w = w.replace(re, "$1$2");
111
+ } else if (re2.test(w)) {
112
+ w = w.replace(re2, "$1$2");
113
+ }
114
+
115
+ // Step 1b
116
+ re = /^(.+?)eed$/;
117
+ re2 = /^(.+?)(ed|ing)$/;
118
+ if (re.test(w)) {
119
+ var fp = re.exec(w);
120
+ re = new RegExp(mgr0);
121
+ if (re.test(fp[1])) {
122
+ re = /.$/;
123
+ w = w.replace(re, "");
124
+ }
125
+ } else if (re2.test(w)) {
126
+ var fp = re2.exec(w);
127
+ stem = fp[1];
128
+ re2 = new RegExp(s_v);
129
+ if (re2.test(stem)) {
130
+ w = stem;
131
+ re2 = /(at|bl|iz)$/;
132
+ re3 = new RegExp("([^aeiouylsz])\\1$");
133
+ re4 = new RegExp("^" + C + v + "[^aeiouwxy]$");
134
+ if (re2.test(w)) { w = w + "e"; }
135
+ else if (re3.test(w)) { re = /.$/; w = w.replace(re, ""); }
136
+ else if (re4.test(w)) { w = w + "e"; }
137
+ }
138
+ }
139
+
140
+ // Step 1c
141
+ re = /^(.+?)y$/;
142
+ if (re.test(w)) {
143
+ var fp = re.exec(w);
144
+ stem = fp[1];
145
+ re = new RegExp(s_v);
146
+ if (re.test(stem)) { w = stem + "i"; }
147
+ }
148
+
149
+ // Step 2
150
+ re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/;
151
+ if (re.test(w)) {
152
+ var fp = re.exec(w);
153
+ stem = fp[1];
154
+ suffix = fp[2];
155
+ re = new RegExp(mgr0);
156
+ if (re.test(stem)) {
157
+ w = stem + step2list[suffix];
158
+ }
159
+ }
160
+
161
+ // Step 3
162
+ re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/;
163
+ if (re.test(w)) {
164
+ var fp = re.exec(w);
165
+ stem = fp[1];
166
+ suffix = fp[2];
167
+ re = new RegExp(mgr0);
168
+ if (re.test(stem)) {
169
+ w = stem + step3list[suffix];
170
+ }
171
+ }
172
+
173
+ // Step 4
174
+ re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/;
175
+ re2 = /^(.+?)(s|t)(ion)$/;
176
+ if (re.test(w)) {
177
+ var fp = re.exec(w);
178
+ stem = fp[1];
179
+ re = new RegExp(mgr1);
180
+ if (re.test(stem)) {
181
+ w = stem;
182
+ }
183
+ } else if (re2.test(w)) {
184
+ var fp = re2.exec(w);
185
+ stem = fp[1] + fp[2];
186
+ re2 = new RegExp(mgr1);
187
+ if (re2.test(stem)) {
188
+ w = stem;
189
+ }
190
+ }
191
+
192
+ // Step 5
193
+ re = /^(.+?)e$/;
194
+ if (re.test(w)) {
195
+ var fp = re.exec(w);
196
+ stem = fp[1];
197
+ re = new RegExp(mgr1);
198
+ re2 = new RegExp(meq1);
199
+ re3 = new RegExp("^" + C + v + "[^aeiouwxy]$");
200
+ if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) {
201
+ w = stem;
202
+ }
203
+ }
204
+
205
+ re = /ll$/;
206
+ re2 = new RegExp(mgr1);
207
+ if (re.test(w) && re2.test(w)) {
208
+ re = /.$/;
209
+ w = w.replace(re, "");
210
+ }
211
+
212
+ // and turn initial Y back to y
213
+
214
+ if (firstch == "y") {
215
+ w = firstch.toLowerCase() + w.substr(1);
216
+ }
217
+
218
+ return w;
219
+ }
220
+ })();
221
+
34
222
  /** @type {SearchEntry[]} */
35
223
  var haystack = [];
36
224
  var currentQuery = '';
@@ -62,8 +250,21 @@ function search(s, r) {
62
250
  raiseEvent('searched', { search: s });
63
251
 
64
252
  currentQuery = cleanQuery;
253
+ /** @type {string[]} */
254
+ const stemmedTerms = [];
65
255
  const queryTerms = explode(currentQuery);
66
256
 
257
+ for (const term of queryTerms) {
258
+ const stemmed = stemmer(term);
259
+ if (stemmed !== term) {
260
+ stemmedTerms.push(stemmed);
261
+ }
262
+ }
263
+
264
+ const allTerms = queryTerms.concat(stemmedTerms);
265
+
266
+ console.log(allTerms);
267
+
67
268
  cleanQuery.length > 0 && haystack.forEach( (item) => {
68
269
 
69
270
  let foundWords = 0;
@@ -94,8 +295,9 @@ function search(s, r) {
94
295
 
95
296
  // Part 2 - Term Matches, i.e. "Kitchen" or "Sink"
96
297
 
97
- queryTerms.forEach(term => {
298
+ allTerms.forEach(term => {
98
299
  let isTermFound = false;
300
+ const isUserTerm = queryTerms.includes(term);
99
301
 
100
302
  // Title
101
303
  if (contains(item.safeTitle, term)) {
@@ -140,7 +342,7 @@ function search(s, r) {
140
342
  }
141
343
  });
142
344
 
143
- item.foundWords = foundWords / queryTerms.length;
345
+ item.foundWords = foundWords / allTerms.length;
144
346
 
145
347
  if (item.score > 0) {
146
348
  needles.push(item);
@@ -177,6 +379,7 @@ function search(s, r) {
177
379
  const url = isSameHost ? address.pathname : needle.url;
178
380
 
179
381
  const a = document.createElement('a');
382
+ // Only highlight user query terms, not stemmed terms
180
383
  a.innerHTML = highlight(needle.title, queryTerms);
181
384
  a.href = url;
182
385
 
@@ -186,27 +389,34 @@ function search(s, r) {
186
389
 
187
390
  const markers = document.createElement('div');
188
391
  markers.className = 'result-text';
392
+ // Only highlight user query terms, not stemmed terms
189
393
  markers.innerHTML = highlight(needle.description, queryTerms);
190
394
 
191
- const headings = document.createElement('ul');
192
- markers.className = 'result-headings';
193
-
194
- needle.matchedHeadings
195
- .forEach(h => {
196
- const item = document.createElement('li');
197
- const link = document.createElement('a');
198
- link.href = url + '#' + h.slug;
199
- link.innerHTML = highlight(h.text, queryTerms);
200
- item.appendChild(link);
201
- headings.append(item);
202
- });
203
-
204
395
  const li = document.createElement('li');
396
+ li.dataset.score = (Math.round((needle.score/ total) * 100)).toString();
205
397
  li.appendChild(a);
206
398
  li.appendChild(path);
207
399
  li.appendChild(markers);
208
- li.append(headings);
209
- li.dataset.score = (Math.round((needle.score/ total) * 100)).toString();
400
+
401
+ if (needle.matchedHeadings.length > 0) {
402
+ const headings = document.createElement('ul');
403
+ headings.className = 'result-headings';
404
+
405
+ headings.tabIndex = 0;
406
+
407
+ needle.matchedHeadings
408
+ .forEach(h => {
409
+ const item = document.createElement('li');
410
+ const link = document.createElement('a');
411
+ link.href = url + '#' + h.slug;
412
+ // Only highlight user query terms, not stemmed terms
413
+ link.innerHTML = highlight(h.text, queryTerms);
414
+ item.appendChild(link);
415
+ headings.append(item);
416
+ });
417
+
418
+ li.appendChild(headings);
419
+ }
210
420
 
211
421
  ol.appendChild(li);
212
422
  }
@@ -128,12 +128,12 @@ for (const file of filesToProcess) {
128
128
  // Only resize if the image is larger than the target size
129
129
  sharp(source)
130
130
  .resize(size[key], null)
131
- .webp({ quality: 80 })
131
+ .webp({ quality: 90 })
132
132
  .toFile(resizeDestination + '.webp');
133
133
  } else {
134
134
  // Don't resize as it's smaller than target size
135
135
  sharp(source)
136
- .webp({ quality: 80 })
136
+ .webp({ quality: 90 })
137
137
  .toFile(resizeDestination + '.webp');
138
138
  }
139
139
  }