astro-accelerator 0.0.1

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.
Files changed (71) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +6 -0
  3. package/package.json +52 -0
  4. package/public/js/main.js +45 -0
  5. package/public/js/modules/animation.js +69 -0
  6. package/public/js/modules/click-blocks.js +41 -0
  7. package/public/js/modules/code-blocks.js +59 -0
  8. package/public/js/modules/events.js +19 -0
  9. package/public/js/modules/external-links.js +20 -0
  10. package/public/js/modules/figures.js +37 -0
  11. package/public/js/modules/focus.js +76 -0
  12. package/public/js/modules/nav-mobile.js +122 -0
  13. package/public/js/modules/nav-sticky.js +54 -0
  14. package/public/js/modules/query.js +41 -0
  15. package/public/js/modules/resizing.js +43 -0
  16. package/public/js/modules/string.js +66 -0
  17. package/public/js/modules/youtube.js +39 -0
  18. package/public/js/search.js +249 -0
  19. package/src/data/footer.ts +43 -0
  20. package/src/data/image-size.mjs +5 -0
  21. package/src/data/images.mjs +4 -0
  22. package/src/data/navigation.ts +64 -0
  23. package/src/layouts/Author.astro +15 -0
  24. package/src/layouts/Default.astro +15 -0
  25. package/src/layouts/Redirect.astro +15 -0
  26. package/src/layouts/Search.astro +15 -0
  27. package/src/pages/articles/feed.xml.ts +52 -0
  28. package/src/pages/report/missing-banner.astro +42 -0
  29. package/src/pages/report/missing-meta.astro +42 -0
  30. package/src/pages/report/oldest-content.astro +41 -0
  31. package/src/pages/report/taxonomy.astro +47 -0
  32. package/src/pages/search.json.ts +43 -0
  33. package/src/pages/sitemap.xml.ts +36 -0
  34. package/src/themes/accelerator/components/ArticleList.astro +67 -0
  35. package/src/themes/accelerator/components/Authors.astro +57 -0
  36. package/src/themes/accelerator/components/AuthorsMini.astro +37 -0
  37. package/src/themes/accelerator/components/Breadcrumbs.astro +33 -0
  38. package/src/themes/accelerator/components/Footer.astro +37 -0
  39. package/src/themes/accelerator/components/FooterItem.astro +29 -0
  40. package/src/themes/accelerator/components/Header.astro +46 -0
  41. package/src/themes/accelerator/components/HtmlHead.astro +49 -0
  42. package/src/themes/accelerator/components/Navigation.astro +31 -0
  43. package/src/themes/accelerator/components/NavigationBar.astro +31 -0
  44. package/src/themes/accelerator/components/NavigationItem.astro +37 -0
  45. package/src/themes/accelerator/components/Paging.astro +33 -0
  46. package/src/themes/accelerator/components/Related.astro +88 -0
  47. package/src/themes/accelerator/components/SkipLinks.astro +25 -0
  48. package/src/themes/accelerator/components/TableOfContents.astro +32 -0
  49. package/src/themes/accelerator/components/Taxonomy.astro +49 -0
  50. package/src/themes/accelerator/layouts/Author.astro +30 -0
  51. package/src/themes/accelerator/layouts/Default.astro +62 -0
  52. package/src/themes/accelerator/layouts/Redirect.astro +19 -0
  53. package/src/themes/accelerator/layouts/Search.astro +43 -0
  54. package/src/themes/accelerator/utilities/Authors.astro +48 -0
  55. package/src/themes/accelerator/utilities/Breadcrumbs.astro +27 -0
  56. package/src/themes/accelerator/utilities/Cache.astro +42 -0
  57. package/src/themes/accelerator/utilities/DateFormat.astro +25 -0
  58. package/src/themes/accelerator/utilities/Footer.astro +176 -0
  59. package/src/themes/accelerator/utilities/Languages.astro +14 -0
  60. package/src/themes/accelerator/utilities/Markdown.astro +27 -0
  61. package/src/themes/accelerator/utilities/NavPage.astro +65 -0
  62. package/src/themes/accelerator/utilities/Navigation.astro +81 -0
  63. package/src/themes/accelerator/utilities/NavigationTypes.astro +20 -0
  64. package/src/themes/accelerator/utilities/PageLinks.astro +72 -0
  65. package/src/themes/accelerator/utilities/PageQueries.astro +71 -0
  66. package/src/themes/accelerator/utilities/PageTypeFilters.astro +69 -0
  67. package/src/themes/accelerator/utilities/Taxonomy.astro +112 -0
  68. package/src/themes/accelerator/utilities/Url.astro +19 -0
  69. package/src/themes/accelerator/utilities/custom-markdown.mjs +104 -0
  70. package/src/themes/accelerator/utilities/img.mjs +152 -0
  71. package/src/themes/accelerator/utilities/language.json +111 -0
@@ -0,0 +1,122 @@
1
+ // @ts-check
2
+
3
+ import { qs, qsa } from './query.js';
4
+ import { getFocusableElement, trapFocusForward, trapReverseFocus } from './focus.js';
5
+
6
+ /**
7
+ * Provides an overlay with the navigation for mobile users.
8
+ *
9
+ * Example: You have site navigation on the page, but demote it (closer to the footer) on mobile to avoid
10
+ * the content being pushed below the fold. You provide an icon that bookmarks to the
11
+ * navigation.
12
+ *
13
+ * The mobile navigation intercepts the bookmark link and opens the navigation in a modal
14
+ * overlay, trapping keyboard focus until the overlay is closed.
15
+ *
16
+ * @param {string} iconSelector
17
+ * @param {string} navigationSelector
18
+ * @param {string} resizedEventName
19
+ */
20
+ function addMobileNavigation(iconSelector, navigationSelector, resizedEventName) {
21
+ const icon = qs(iconSelector);
22
+ const originalIcon = icon.innerHTML;
23
+ const overlay = document.createElement('div');
24
+ const dataOpen = 'data-open';
25
+
26
+ // Focus trap (forwards the tab / shift-tab back to the menu)
27
+ icon.addEventListener('keydown', function(e) {
28
+ if (icon.getAttribute(dataOpen) === dataOpen) {
29
+ var focusElements = getFocusableElement(overlay);
30
+ trapFocusForward(e, focusElements.first);
31
+ trapReverseFocus(e, focusElements.last);
32
+ }
33
+ });
34
+
35
+ // Close menu on escape-key press
36
+ document.addEventListener('keydown', function(e) {
37
+ if (icon.getAttribute(dataOpen) === dataOpen) {
38
+ if (e.key === 'Escape') {
39
+ closeMobileMenu();
40
+ }
41
+ }
42
+ });
43
+
44
+ // Opens and closes menu
45
+ function handleIconInteraction() {
46
+ if (icon.dataset.open == dataOpen) {
47
+ closeMobileMenu();
48
+ } else {
49
+ openMobileMenu();
50
+ }
51
+ }
52
+
53
+ function openMobileMenu(){
54
+ document.body.style.overflow = 'hidden';
55
+ const menuElement = qs(navigationSelector);
56
+
57
+ overlay.innerHTML = menuElement.outerHTML;
58
+ overlay.className = 'overlay overlay-menu';
59
+ overlay.style.display = 'block';
60
+
61
+ qsa('[id]', overlay).forEach((elem) => {
62
+ elem.id = 'overlay__' + elem.id
63
+ });
64
+
65
+ // Modal Accessibility
66
+ const title = qs('.site-nav-title', overlay);
67
+ title.setAttribute('id', 'modal-title');
68
+ title.setAttribute('tabindex', '-1');
69
+ overlay.setAttribute('role', 'dialog');
70
+ overlay.setAttribute('aria-modal', 'true');
71
+ overlay.setAttribute('aria-labelledby', 'modal-title');
72
+
73
+ // Trap Focus to Visible Overlay
74
+ const focusElements = getFocusableElement(overlay);
75
+
76
+ focusElements.first.addEventListener('keydown', function(e) {
77
+ trapReverseFocus(e, icon);
78
+ })
79
+ focusElements.last.addEventListener('keydown', function(e) {
80
+ trapFocusForward(e, icon);
81
+ });
82
+
83
+ icon.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg"
84
+ width="40" height="40" viewBox="0 0 24 24" stroke-width="1.5"
85
+ fill="none" stroke-linecap="round" stroke-linejoin="round">
86
+ <path stroke="none" d="M0 0h24v24H0z" fill="none"/>
87
+ <line x1="18" y1="6" x2="6" y2="18" />
88
+ <line x1="6" y1="6" x2="18" y2="18" />
89
+ </svg>`;
90
+
91
+ document.body.appendChild(overlay);
92
+ icon.setAttribute(dataOpen, dataOpen);
93
+ focusElements.first.focus();
94
+ }
95
+
96
+ function closeMobileMenu() {
97
+ document.body.style.overflow = 'auto';
98
+
99
+ if (icon.getAttribute(dataOpen) === dataOpen) {
100
+ overlay.innerHTML = '';
101
+ overlay.style.display = 'none';
102
+ document.body.removeChild(overlay);
103
+ }
104
+
105
+ icon.innerHTML = originalIcon;
106
+ icon.removeAttribute(dataOpen);
107
+ }
108
+
109
+ icon.addEventListener('click', function (e) {
110
+ e.preventDefault();
111
+ handleIconInteraction();
112
+ return false;
113
+ });
114
+
115
+ document.addEventListener(resizedEventName, function (/** @type {any} */e) {
116
+ if (e.detail.change.width > 0) {
117
+ closeMobileMenu();
118
+ }
119
+ })
120
+ }
121
+
122
+ export { addMobileNavigation };
@@ -0,0 +1,54 @@
1
+ // @ts-check
2
+
3
+ import { qs } from './query.js';
4
+
5
+ /**
6
+ * Makes an existing navigation element sticky
7
+ *
8
+ * Example: If the existing navigation is not as tall as the content, the
9
+ * navigation will stick to the top, allowing the user to see it as
10
+ * they scroll through the article
11
+ *
12
+ * @param {string} headerSelector
13
+ * @param {string} navigationSelector
14
+ * @param {string} navigationListSelector
15
+ */
16
+ function addStickyNavigation(headerSelector, navigationSelector, navigationListSelector, resizedEventName) {
17
+ function setNavigationMode() {
18
+ const header = qs(headerSelector);
19
+ const navigation = qs(navigationSelector);
20
+ const navigationList = qs(navigationListSelector);
21
+
22
+ const buffer = 50;
23
+ const className = 'sticky';
24
+
25
+ const dimensions = {
26
+ browserHeight: window.innerHeight,
27
+ browserWidth: window.innerWidth,
28
+ headerHeight: header.clientHeight,
29
+ navigationHeight: navigationList.clientHeight
30
+ };
31
+
32
+ // Only enable sticky mode if the menu will fit vertically
33
+ // && where the browser is more than 860px wide
34
+ if (dimensions.navigationHeight < ((dimensions.browserHeight - dimensions.headerHeight) - buffer)
35
+ && dimensions.browserWidth > 860) {
36
+ console.log('Navigation: Sticky Mode');
37
+ navigation.classList.add(className)
38
+ navigation.style.top = dimensions.headerHeight.toString() + 'px';
39
+ } else {
40
+ console.log('Navigation: Fixed Mode');
41
+ navigation.classList.remove(className);
42
+ }
43
+ }
44
+
45
+ setNavigationMode();
46
+
47
+ document.addEventListener(resizedEventName, function(e) {
48
+ if (e.detail && e.detail.change && e.detail.change.height != 0) {
49
+ setNavigationMode();
50
+ }
51
+ });
52
+ }
53
+
54
+ export { addStickyNavigation };
@@ -0,0 +1,41 @@
1
+ // @ts-check
2
+
3
+ /**
4
+ *
5
+ * @param {string} query
6
+ * @param {HTMLElement} [container]
7
+ * @returns {HTMLElement}
8
+ */
9
+ function qs(query, container) {
10
+ const target = (container)
11
+ ? container
12
+ : document;
13
+
14
+ /** @type {any} */
15
+ const result = target.querySelector(query);
16
+
17
+ if (result) {
18
+ return result;
19
+ }
20
+
21
+ throw new Error(`No element ${query}`);
22
+ }
23
+
24
+ /**
25
+ * Utility for query selector all
26
+ *
27
+ * @param {string} query
28
+ * @param {HTMLElement | null} [container]
29
+ * @returns {NodeListOf<any>}
30
+ */
31
+ function qsa(query, container) {
32
+ const target = (container)
33
+ ? container
34
+ : document;
35
+
36
+ /** @type {NodeListOf<HTMLElement>} */
37
+ const result = target.querySelectorAll(query);
38
+ return result;
39
+ }
40
+
41
+ export { qs, qsa };
@@ -0,0 +1,43 @@
1
+ // @ts-check
2
+
3
+ import { raiseEvent } from './events.js';
4
+
5
+ var resizeEventName = 'resize';
6
+ var resizedEventName = 'resized';
7
+
8
+ var width = window.innerWidth;
9
+ var height = window.innerHeight;
10
+
11
+ /**
12
+ * Adds a de-bounced "resized" event, so you can listen to:
13
+ * document.addEventListener('resized', <handler>);
14
+ *
15
+ * @returns {string}
16
+ */
17
+ function addResizedEvent() {
18
+ let debounce = null;
19
+
20
+ function resizeEnd(e) {
21
+ window.clearTimeout(debounce);
22
+ debounce = window.setTimeout(raiseResizeEvent, 500);
23
+ }
24
+
25
+ function raiseResizeEvent() {
26
+ const change = {
27
+ width: window.innerWidth - width,
28
+ height: window.innerHeight - height
29
+ };
30
+
31
+ width = window.innerWidth;
32
+ height = window.innerHeight;
33
+
34
+
35
+ raiseEvent(resizedEventName, { change: change });
36
+ }
37
+
38
+ window.addEventListener(resizeEventName, resizeEnd);
39
+
40
+ return resizedEventName;
41
+ }
42
+
43
+ export { addResizedEvent };
@@ -0,0 +1,66 @@
1
+ // @ts-check
2
+
3
+ /**
4
+ * Looks for a search within a string
5
+ *
6
+ * @param {string} string
7
+ * @param {string} search
8
+ * @returns
9
+ */
10
+ function contains(string, search) {
11
+ return string.indexOf(search) > -1;
12
+ }
13
+
14
+ /**
15
+ *
16
+ * @param {string} string
17
+ * @param {string[]} terms
18
+ * @returns
19
+ */
20
+ function highlight(string, terms) {
21
+ terms.forEach(term => {
22
+ const regEx = new RegExp(term, "ig");
23
+ const matches = string.match(regEx);
24
+ if (matches) {
25
+ string = string.replace(regEx, `<mark>${matches[0]}</mark>`)
26
+ }
27
+ });
28
+ return string;
29
+ }
30
+
31
+ /**
32
+ * Simplifies a string to plain lower case, removing diacritic characters and hyphens
33
+ * This means a search for "co-op" will be found in "COOP" and "Café" will be found in "cafe"
34
+ * @param {string} string
35
+ * @returns
36
+ */
37
+ function sanitise(string) {
38
+ // @ts-ignore
39
+ if (String.prototype.normalize) {
40
+ // Reduces diacritic characters to plain characters
41
+ string.trim().normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase().replace(/-/g, '');
42
+ }
43
+
44
+ // Some browsers can't normalise strings
45
+ return string.trim().toLowerCase().replace(/-/g, '');
46
+ }
47
+
48
+ /**
49
+ * Sets a minimum length for a search
50
+ * @param {string} string
51
+ * @returns
52
+ */
53
+ function isLongEnough(string) {
54
+ return string.length > 1;
55
+ }
56
+
57
+ /**
58
+ *
59
+ * @param {string} string
60
+ * @returns {string[]}
61
+ */
62
+ function explode(string) {
63
+ return string.split(' ').filter(isLongEnough).map(sanitise);
64
+ }
65
+
66
+ export { contains, sanitise, explode, highlight };
@@ -0,0 +1,39 @@
1
+ // @ts-check
2
+
3
+ import { qsa } from './query.js';
4
+
5
+ function enhanceYoutubeLinks() {
6
+ const videos = qsa('a[href^="https://www.youtube.com/watch?v="]');
7
+
8
+ for (var video of videos) {
9
+ const id = new URL(video.href).searchParams.get('v');
10
+ video.setAttribute('data-youtube', id);
11
+ video.classList.add('init');
12
+ video.setAttribute('role', 'button');
13
+
14
+ video.innerHTML = `<div class="yt-video">
15
+ <div class="play-icon" style="background-image: url(https://img.youtube.com/vi/${id}/0.jpg)">▶</div>
16
+ <div class="title">${video.textContent}</div>
17
+ </div>`;
18
+ }
19
+
20
+ function clickHandler (event) {
21
+ var link = event.target.closest('[data-youtube]');
22
+
23
+ if (!link) {
24
+ return;
25
+ }
26
+
27
+ event.preventDefault();
28
+ var id = link.getAttribute('data-youtube');
29
+
30
+ var player = document.createElement('div');
31
+ player.innerHTML = `<iframe class="yt-iframe" width="560" height="315" src="https://www.youtube-nocookie.com/embed/${id}?autoplay=1" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>`;
32
+
33
+ link.replaceWith(player);
34
+ }
35
+
36
+ document.addEventListener('click', clickHandler);
37
+ }
38
+
39
+ export { enhanceYoutubeLinks }
@@ -0,0 +1,249 @@
1
+ // @ts-check
2
+
3
+ import { qs } from './modules/query.js';
4
+ import { raiseEvent } from './modules/events.js';
5
+ import { contains, sanitise, explode, highlight } from './modules/string.js';
6
+
7
+ /**
8
+ type Heading = {
9
+ text: string;
10
+ safeText: string;
11
+ slug: string;
12
+ }
13
+
14
+ type SearchEntry = {
15
+ score: number;
16
+ title: string;
17
+ safeTitle: string;
18
+ description: string;
19
+ safeDescription: string;
20
+ headings: Heading[];
21
+ tags: string;
22
+ url: string;
23
+ date: string;
24
+ matchedHeadings: Heading[];
25
+ }
26
+ */
27
+
28
+ var dataUrl = qs('#site-search').dataset.sourcedata;
29
+ var haystack = /** @type {SearchEntry} */ [];
30
+ var currentQuery = '';
31
+
32
+ var ready = false;
33
+ var scrolled = false;
34
+
35
+ /**
36
+ *
37
+ * @param {string} s
38
+ * @returns
39
+ */
40
+ function search(s) {
41
+ const needles = /** @type {SearchEntry} */ [];
42
+
43
+ // Clean the input
44
+ const cleanQuery = sanitise(s);
45
+
46
+ if (currentQuery === cleanQuery) {
47
+ return;
48
+ }
49
+
50
+ raiseEvent('searched', { search: s });
51
+
52
+ currentQuery = cleanQuery;
53
+ const queryTerms = explode(currentQuery);
54
+
55
+ s.length > 0 && haystack.forEach( (item) => {
56
+
57
+ item.score = 0;
58
+ item.matchedHeadings = [];
59
+
60
+ // Imagine the user searched for "Kitchen Sink"
61
+ // The scores are arranged below from highest to lowest relevance
62
+
63
+ // If the title contains "Kitchen Sink"
64
+ if (contains(item.safeTitle, currentQuery)) {
65
+ item.score = item.score + 60;
66
+ }
67
+
68
+ // If a heading contains "Kitchen Sink"
69
+ item.headings.forEach(c => {
70
+ if (contains(c.safeText, currentQuery)) {
71
+ item.score = item.score + 25;
72
+ item.matchedHeadings.push(c);
73
+ }
74
+ });
75
+
76
+ // If the title contains "Kitchen Sink"
77
+ if (contains(item.description, currentQuery)) {
78
+ item.score = item.score + 20;
79
+ }
80
+
81
+ queryTerms.forEach(term => {
82
+ // If the title contains "Kitchen" or "Sink"
83
+ if (contains(item.safeTitle, term)) {
84
+ item.score = item.score + 40;
85
+ }
86
+
87
+ // If a heading contains "Kitchen" or "Sink"
88
+ item.headings.forEach(c => {
89
+ if (contains(c.safeText, term)) {
90
+ item.score = item.score + 15;
91
+ if (item.matchedHeadings.filter(h => h.slug == c.slug).length == 0) {
92
+ item.matchedHeadings.push(c);
93
+ }
94
+ }
95
+ });
96
+
97
+ // If the description contains "Kitchen" or "Sink"
98
+ if (contains(item.description, term)) {
99
+ item.score = item.score + 10;
100
+ }
101
+
102
+ // If a tag contains "Kitchen" or "Sink"
103
+ item.tags.forEach(t => {
104
+ if (contains(t, term)) {
105
+ item.score = item.score + 5;
106
+ }
107
+ });
108
+ })
109
+
110
+ if (item.score > 0) {
111
+ needles.push(item);
112
+ }
113
+ });
114
+
115
+ needles.sort(function (a, b){
116
+ return b.score - a.score;
117
+ });
118
+
119
+ const total = needles.reduce(function (accumulator, needle) {
120
+ return accumulator + needle.score;
121
+ }, 0);
122
+
123
+ const results = qs('#site-search-results');
124
+
125
+ const ol = document.createElement('ol');
126
+ ol.className = 'site-search-results';
127
+
128
+ const limit = Math.min(needles.length, 12);
129
+
130
+ // @ts-ignore
131
+ const siteUrl = new URL(site_url);
132
+
133
+ for (let i = 0; i < limit; i++) {
134
+ const needle = needles[i];
135
+
136
+ const address = new URL(needle.url);
137
+ const isSameHost = siteUrl.host == address.host;
138
+ const url = isSameHost ? address.pathname : needle.url;
139
+
140
+ const a = document.createElement('a');
141
+ a.innerHTML = highlight(needle.title, queryTerms);
142
+ a.href = url;
143
+
144
+ const path = document.createElement('div');
145
+ path.className = 'result-path';
146
+ path.innerHTML = address.pathname;
147
+
148
+ const markers = document.createElement('div');
149
+ markers.className = 'result-text';
150
+ markers.innerHTML = highlight(needle.description, queryTerms);
151
+
152
+ const headings = document.createElement('ul');
153
+ markers.className = 'result-headings';
154
+ console.log(needle.matchedHeadings);
155
+ needle.matchedHeadings
156
+ .forEach(h => {
157
+ const item = document.createElement('li');
158
+ const link = document.createElement('a');
159
+ link.href = url + '#' + h.slug;
160
+ link.innerHTML = highlight(h.text, queryTerms);
161
+ item.appendChild(link);
162
+ headings.append(item);
163
+ });
164
+
165
+ const li = document.createElement('li');
166
+ li.appendChild(a);
167
+ li.appendChild(path);
168
+ li.appendChild(markers);
169
+ li.append(headings);
170
+ li.dataset.score = (Math.round((needle.score/ total) * 100)).toString();
171
+
172
+ ol.appendChild(li);
173
+ }
174
+
175
+ var h2 = document.createElement('h2');
176
+ h2.innerHTML = needles.length === 0
177
+ ? results.dataset.emptytitle || 'No Results'
178
+ : results.dataset.title || 'Results';
179
+
180
+ results.innerHTML = '';
181
+ results.appendChild(h2);
182
+ results.appendChild(ol);
183
+ }
184
+
185
+ var debounceTimer;
186
+
187
+ function debounceSearch() {
188
+ var input = /** @type {HTMLInputElement} */(qs('#site-search-query'));
189
+
190
+ if (input == null) {
191
+ throw new Error('Cannot find #site-search-query');
192
+ }
193
+
194
+ var s = input.value;
195
+
196
+ window.clearTimeout(debounceTimer);
197
+ debounceTimer = window.setTimeout(function () {
198
+ if (ready) {
199
+ search(s);
200
+ }
201
+ }, 400);
202
+ }
203
+
204
+ fetch(dataUrl)
205
+ .then(function (response) {
206
+ return response.json();
207
+ })
208
+ .then(function (data) {
209
+ haystack = data;
210
+ ready = true;
211
+
212
+ for (let i = 0; i < haystack.length; i++) {
213
+ const item = haystack[i];
214
+ item.safeTitle = sanitise(item.title);
215
+ item.tags = item.tags.map(t => sanitise(t));
216
+ item.safeDescription = sanitise(item.description);
217
+
218
+ item.headings.forEach(h => h.safeText = sanitise(h.text));
219
+ }
220
+
221
+ var siteSearch = qs('#site-search');
222
+ var siteSearchQuery = qs('#site-search-query');
223
+
224
+ if (siteSearch == null || siteSearchQuery == null) {
225
+ throw new Error('Cannot find #site-search or #site-search-query');
226
+ }
227
+
228
+ siteSearch.addEventListener('submit', function (e) {
229
+ e.preventDefault();
230
+ debounceSearch();
231
+ return false;
232
+ });
233
+
234
+ siteSearchQuery.addEventListener('keyup', function (e) {
235
+ e.preventDefault();
236
+ if (!scrolled) {
237
+ scrolled = true;
238
+ this.scrollIntoView(true);
239
+ }
240
+ debounceSearch();
241
+ return false;
242
+ });
243
+
244
+ console.log('Search ready');
245
+ debounceSearch();
246
+ })
247
+ .catch((error) => {
248
+ console.log(error)
249
+ });
@@ -0,0 +1,43 @@
1
+ import type { NavPage } from '@util/NavigationTypes.astro';
2
+
3
+ export const menu: (NavPage | 'categories' | 'tags' | 'toptags')[] = [
4
+ 'categories',
5
+ 'tags',
6
+ {
7
+ title: 'Quick Links',
8
+ url: '',
9
+ ariaCurrent: false,
10
+ isOpen: false,
11
+ order: 1,
12
+ children: [{
13
+ title: 'Steve Fenton',
14
+ url: 'https://www.stevefenton.co.uk/',
15
+ ariaCurrent: false,
16
+ isOpen: false,
17
+ order: 1,
18
+ children: []
19
+ },{
20
+ title: 'Astro',
21
+ url: 'https://astro.build',
22
+ ariaCurrent: false,
23
+ isOpen: false,
24
+ order: 1,
25
+ children: []
26
+ },{
27
+ title: 'GitHub',
28
+ url: 'https://github.com/Steve-Fenton/astro-accelerator',
29
+ ariaCurrent: false,
30
+ isOpen: false,
31
+ order: 1,
32
+ children: []
33
+ }]
34
+ }];
35
+
36
+ /*
37
+ See navigation.ts
38
+ Allows customisation of the footer navigation
39
+
40
+ 'categories' -> Auto columns of links for categories
41
+ 'tags' -> Auto columns of links for tags
42
+
43
+ */
@@ -0,0 +1,5 @@
1
+ export const size = {
2
+ small: 400,
3
+ medium: 700,
4
+ large: 1000
5
+ };
@@ -0,0 +1,4 @@
1
+ export const imagePaths = {
2
+ src: 'img',
3
+ dest: 'i'
4
+ }