astro-accelerator 4.0.0 → 4.0.2
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 +4 -0
- package/package.json +6 -6
- package/src/pages/search.json.ts +6 -9
- package/.npmrc +0 -2
- package/public/css/main.css +0 -1189
- package/public/css/vars.css +0 -89
- package/public/icons/android-chrome-192x192.png +0 -0
- package/public/icons/android-chrome-512x512.png +0 -0
- package/public/icons/apple-touch-icon.png +0 -0
- package/public/icons/favicon-16x16.png +0 -0
- package/public/icons/favicon-32x32.png +0 -0
- package/public/icons/favicon.ico +0 -0
- package/public/js/main.js +0 -66
- package/public/js/modules/animation.js +0 -69
- package/public/js/modules/click-blocks.js +0 -42
- package/public/js/modules/code-blocks.js +0 -59
- package/public/js/modules/detail-tabs.js +0 -194
- package/public/js/modules/events.js +0 -19
- package/public/js/modules/external-links.js +0 -20
- package/public/js/modules/figures.js +0 -28
- package/public/js/modules/focus.js +0 -76
- package/public/js/modules/headers.js +0 -21
- package/public/js/modules/input-type.js +0 -53
- package/public/js/modules/nav-mobile.js +0 -159
- package/public/js/modules/nav-sticky.js +0 -56
- package/public/js/modules/query.js +0 -41
- package/public/js/modules/resizing.js +0 -43
- package/public/js/modules/search-dialog.js +0 -69
- package/public/js/modules/share.js +0 -31
- package/public/js/modules/string.js +0 -77
- package/public/js/modules/toc.js +0 -82
- package/public/js/modules/youtube.js +0 -44
- package/public/js/search.js +0 -615
- package/src/themes/accelerator/components/ArticleList.astro +0 -90
- package/src/themes/accelerator/components/Authors.astro +0 -65
- package/src/themes/accelerator/components/AuthorsMini.astro +0 -41
- package/src/themes/accelerator/components/Breadcrumbs.astro +0 -53
- package/src/themes/accelerator/components/Copyright.astro +0 -28
- package/src/themes/accelerator/components/Footer.astro +0 -37
- package/src/themes/accelerator/components/FooterItem.astro +0 -31
- package/src/themes/accelerator/components/Header.astro +0 -46
- package/src/themes/accelerator/components/HtmlHead.astro +0 -60
- package/src/themes/accelerator/components/Navigation.astro +0 -34
- package/src/themes/accelerator/components/NavigationBar.astro +0 -33
- package/src/themes/accelerator/components/NavigationItem.astro +0 -40
- package/src/themes/accelerator/components/PagingLinks.astro +0 -36
- package/src/themes/accelerator/components/RecentlyUpdated.astro +0 -38
- package/src/themes/accelerator/components/Related.astro +0 -87
- package/src/themes/accelerator/components/SkipLinks.astro +0 -29
- package/src/themes/accelerator/components/TableOfContents.astro +0 -46
- package/src/themes/accelerator/components/Taxonomy.astro +0 -53
- package/src/themes/accelerator/layouts/Author.astro +0 -27
- package/src/themes/accelerator/layouts/Default.astro +0 -83
- package/src/themes/accelerator/layouts/Redirect.astro +0 -29
- package/src/themes/accelerator/layouts/Search.astro +0 -48
- package/src/themes/accelerator/utilities/Languages.ts +0 -21
- package/src/themes/accelerator/utilities/custom-markdown.mjs +0 -142
- package/src/themes/accelerator/utilities/default-layout.mjs +0 -7
- package/src/themes/accelerator/utilities/img.mjs +0 -142
- package/src/themes/accelerator/utilities/language.json +0 -117
- package/src/themes/accelerator/utilities/stats.mjs +0 -44
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { qsa } from './query.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Enables copy on code blocks (<pre><code>...)
|
|
5
|
-
*/
|
|
6
|
-
function enhanceHeaders() {
|
|
7
|
-
|
|
8
|
-
// Make code blocks focusable, so they can be keyboard scrolled
|
|
9
|
-
qsa('h2[id], h3[id], h4[id], h5[id], h6[id]').forEach((elem) => {
|
|
10
|
-
const linkContainer = document.createElement('a');
|
|
11
|
-
linkContainer.href = `#${elem.id}`;
|
|
12
|
-
linkContainer.className = 'bookmark-link';
|
|
13
|
-
linkContainer.innerHTML = 'Bookmark'
|
|
14
|
-
console.log(elem, typeof elem);
|
|
15
|
-
elem.appendChild(linkContainer);
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export { enhanceHeaders }
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
// @ts-check
|
|
2
|
-
|
|
3
|
-
let inputType = 'unknown';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Sets the user input mode as a class
|
|
7
|
-
*/
|
|
8
|
-
function monitorInputType() {
|
|
9
|
-
window.addEventListener('keydown', event => {
|
|
10
|
-
const eventType = 'input-keyboard';
|
|
11
|
-
processInput(eventType);
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
window.addEventListener('mousemove', event => {
|
|
15
|
-
const eventType = 'input-mouse';
|
|
16
|
-
processInput(eventType);
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
window.addEventListener('touchstart', event => {
|
|
20
|
-
const eventType = 'input-touch';
|
|
21
|
-
processInput(eventType);
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Processes the keyboard, mouse, or touch event
|
|
27
|
-
* @param {string} eventType
|
|
28
|
-
*/
|
|
29
|
-
function processInput(eventType) {
|
|
30
|
-
if (inputType !== eventType) {
|
|
31
|
-
removeClass(inputType);
|
|
32
|
-
inputType = eventType;
|
|
33
|
-
addClass(inputType);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Removes an input type class from the body
|
|
39
|
-
* @param {string} inputType
|
|
40
|
-
*/
|
|
41
|
-
function removeClass(inputType) {
|
|
42
|
-
document.body.classList.remove(inputType);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Adds an input type class to the body
|
|
47
|
-
* @param {string} inputType
|
|
48
|
-
*/
|
|
49
|
-
function addClass(inputType) {
|
|
50
|
-
document.body.classList.add(inputType);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export { monitorInputType };
|
|
@@ -1,159 +0,0 @@
|
|
|
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} resizedEventName
|
|
17
|
-
*/
|
|
18
|
-
function addMobileNav(resizedEventName) {
|
|
19
|
-
const icons = qsa('[data-navigationid]');
|
|
20
|
-
for (let icon of icons) {
|
|
21
|
-
addMobileNavigation(icon, resizedEventName);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const details = qsa('[data-openon]');
|
|
25
|
-
for (let detail of details) {
|
|
26
|
-
const minWidth = parseInt(detail.dataset.openon, 10);
|
|
27
|
-
const width = window.innerWidth;
|
|
28
|
-
if (width > minWidth) {
|
|
29
|
-
detail.setAttribute('open', 'open');
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* @param {HTMLElement} icon
|
|
36
|
-
* @param {string} resizedEventName
|
|
37
|
-
*/
|
|
38
|
-
function addMobileNavigation(icon, resizedEventName) {
|
|
39
|
-
const navigationSelector = icon.dataset.navigationid || '';
|
|
40
|
-
const iconType = icon.firstElementChild && icon.firstElementChild.tagName == 'svg'
|
|
41
|
-
? 'svg'
|
|
42
|
-
: 'element';
|
|
43
|
-
|
|
44
|
-
const originalIcon = icon.innerHTML;
|
|
45
|
-
const overlay = document.createElement('div');
|
|
46
|
-
const dataOpen = 'data-open';
|
|
47
|
-
|
|
48
|
-
icon.setAttribute('aria-expanded', 'false');
|
|
49
|
-
icon.setAttribute('aria-controls', navigationSelector);
|
|
50
|
-
|
|
51
|
-
// Focus trap (forwards the tab / shift-tab back to the menu)
|
|
52
|
-
icon.addEventListener('keydown', function(e) {
|
|
53
|
-
if (icon.getAttribute(dataOpen) === dataOpen) {
|
|
54
|
-
var focusElements = getFocusableElement(overlay);
|
|
55
|
-
trapFocusForward(e, focusElements.first);
|
|
56
|
-
trapReverseFocus(e, focusElements.last);
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
// Close menu on escape-key press
|
|
61
|
-
document.addEventListener('keydown', function(e) {
|
|
62
|
-
if (icon.getAttribute(dataOpen) === dataOpen) {
|
|
63
|
-
if (e.key === 'Escape') {
|
|
64
|
-
closeMobileMenu();
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
// Opens and closes menu
|
|
70
|
-
function handleIconInteraction() {
|
|
71
|
-
if (icon.dataset.open == dataOpen) {
|
|
72
|
-
closeMobileMenu();
|
|
73
|
-
} else {
|
|
74
|
-
openMobileMenu();
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function openMobileMenu(){
|
|
79
|
-
const w1 = document.body.getBoundingClientRect().width;
|
|
80
|
-
document.body.style.overflow = 'hidden';
|
|
81
|
-
const w2 = document.body.getBoundingClientRect().width;
|
|
82
|
-
document.documentElement.style.color = 'red';
|
|
83
|
-
document.documentElement.style.paddingInlineEnd = (w2 - w1) + 'px';
|
|
84
|
-
|
|
85
|
-
console.log(w1, w2, w1 - w2);
|
|
86
|
-
const menuElement = qs('#' + navigationSelector);
|
|
87
|
-
|
|
88
|
-
overlay.innerHTML = menuElement.outerHTML;
|
|
89
|
-
overlay.className = 'overlay overlay-menu';
|
|
90
|
-
overlay.style.display = 'block';
|
|
91
|
-
menuElement.style.display = 'none';
|
|
92
|
-
|
|
93
|
-
qsa('[id]', overlay).forEach((elem) => {
|
|
94
|
-
elem.id = 'overlay__' + elem.id
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
// Modal Accessibility
|
|
98
|
-
const title = menuElement.getAttribute('aria-label') ?? '';
|
|
99
|
-
overlay.setAttribute('role', 'dialog');
|
|
100
|
-
overlay.setAttribute('aria-modal', 'true');
|
|
101
|
-
overlay.setAttribute('aria-label', title);
|
|
102
|
-
|
|
103
|
-
// Trap Focus to Visible Overlay
|
|
104
|
-
const focusElements = getFocusableElement(overlay);
|
|
105
|
-
|
|
106
|
-
focusElements.first.addEventListener('keydown', function(e) {
|
|
107
|
-
trapReverseFocus(e, icon);
|
|
108
|
-
})
|
|
109
|
-
focusElements.last.addEventListener('keydown', function(e) {
|
|
110
|
-
trapFocusForward(e, icon);
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
if (iconType === 'svg') {
|
|
114
|
-
icon.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg"
|
|
115
|
-
width="40" height="40" viewBox="0 0 24 24" stroke-width="1.5"
|
|
116
|
-
fill="none" stroke-linecap="round" stroke-linejoin="round">
|
|
117
|
-
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
|
118
|
-
<line x1="18" y1="6" x2="6" y2="18" />
|
|
119
|
-
<line x1="6" y1="6" x2="18" y2="18" />
|
|
120
|
-
</svg>`;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
document.body.appendChild(overlay);
|
|
124
|
-
icon.setAttribute(dataOpen, dataOpen);
|
|
125
|
-
icon.setAttribute('aria-expanded', 'true');
|
|
126
|
-
focusElements.first.focus();
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function closeMobileMenu() {
|
|
130
|
-
const menuElement = qs('#' + navigationSelector);
|
|
131
|
-
menuElement.style.display = '';
|
|
132
|
-
document.body.style.overflow = 'auto';
|
|
133
|
-
document.documentElement.style.paddingInlineEnd = '0';
|
|
134
|
-
|
|
135
|
-
if (icon.getAttribute(dataOpen) === dataOpen) {
|
|
136
|
-
overlay.innerHTML = '';
|
|
137
|
-
overlay.style.display = 'none';
|
|
138
|
-
document.body.removeChild(overlay);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
icon.innerHTML = originalIcon;
|
|
142
|
-
icon.removeAttribute(dataOpen);
|
|
143
|
-
icon.setAttribute('aria-expanded', 'false');
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
icon.addEventListener('click', function (e) {
|
|
147
|
-
e.preventDefault();
|
|
148
|
-
handleIconInteraction();
|
|
149
|
-
return false;
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
document.addEventListener(resizedEventName, function (/** @type {any} */e) {
|
|
153
|
-
if (e.detail.change.width > 0) {
|
|
154
|
-
closeMobileMenu();
|
|
155
|
-
}
|
|
156
|
-
})
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
export { addMobileNav };
|
|
@@ -1,56 +0,0 @@
|
|
|
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
|
-
* @param {string} resizedEventName
|
|
16
|
-
*/
|
|
17
|
-
function addStickyNavigation(headerSelector, navigationSelector, navigationListSelector, resizedEventName) {
|
|
18
|
-
function setNavigationMode() {
|
|
19
|
-
const header = qs(headerSelector);
|
|
20
|
-
const navigation = qs(navigationSelector);
|
|
21
|
-
const navigationList = qs(navigationListSelector);
|
|
22
|
-
|
|
23
|
-
const buffer = 50;
|
|
24
|
-
const className = 'sticky';
|
|
25
|
-
|
|
26
|
-
const dimensions = {
|
|
27
|
-
browserHeight: window.innerHeight,
|
|
28
|
-
browserWidth: window.innerWidth,
|
|
29
|
-
headerHeight: header.clientHeight,
|
|
30
|
-
navigationHeight: navigationList.clientHeight
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
// Only enable sticky mode if the menu will fit vertically
|
|
34
|
-
// && where the browser is more than 860px wide
|
|
35
|
-
if (dimensions.navigationHeight < ((dimensions.browserHeight - Math.max(dimensions.headerHeight, site_features.stickyNav.top)) - buffer)
|
|
36
|
-
&& dimensions.browserWidth > 860) {
|
|
37
|
-
console.log('Navigation: Sticky Mode');
|
|
38
|
-
navigation.classList.add(className);
|
|
39
|
-
const top = site_features.stickyNav.top ?? 220;
|
|
40
|
-
navigation.style.top = top + 'px';
|
|
41
|
-
} else {
|
|
42
|
-
console.log('Navigation: Fixed Mode');
|
|
43
|
-
navigation.classList.remove(className);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
setNavigationMode();
|
|
48
|
-
|
|
49
|
-
document.addEventListener(resizedEventName, function(e) {
|
|
50
|
-
if (e.detail && e.detail.change && e.detail.change.height != 0) {
|
|
51
|
-
setNavigationMode();
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export { addStickyNavigation };
|
|
@@ -1,41 +0,0 @@
|
|
|
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 };
|
|
@@ -1,43 +0,0 @@
|
|
|
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 };
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
// @ts-check
|
|
2
|
-
|
|
3
|
-
function enhanceSearchIcon() {
|
|
4
|
-
if (document.querySelector('#site-search-query') == null) {
|
|
5
|
-
const icon = document.querySelector('a.search-icon');
|
|
6
|
-
|
|
7
|
-
if (icon != null) {
|
|
8
|
-
fetch(icon.href)
|
|
9
|
-
.then(response => response.text())
|
|
10
|
-
.then(html => {
|
|
11
|
-
const parser = new DOMParser();
|
|
12
|
-
const doc = parser.parseFromString(html, 'text/html');
|
|
13
|
-
const form = doc.querySelector('#site-search');
|
|
14
|
-
const input = form.querySelector('#site-search-query');
|
|
15
|
-
const results = doc.querySelector('#site-search-results');
|
|
16
|
-
icon.setAttribute('aria-expanded', 'false');
|
|
17
|
-
|
|
18
|
-
input?.setAttribute('autofocus', 'autofocus');
|
|
19
|
-
|
|
20
|
-
const close = document.createElement('button');
|
|
21
|
-
close.id = 'site-search-close';
|
|
22
|
-
close.value = 'cancel';
|
|
23
|
-
close.formMethod = 'dialog';
|
|
24
|
-
close.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 24 24" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
|
25
|
-
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
|
26
|
-
<line x1="18" y1="6" x2="6" y2="18"></line>
|
|
27
|
-
<line x1="6" y1="6" x2="18" y2="18"></line>
|
|
28
|
-
</svg>`;
|
|
29
|
-
|
|
30
|
-
const script = doc.querySelector('script[src*="/search.js"]');
|
|
31
|
-
const scr = document.createElement('script');
|
|
32
|
-
for (let att, i = 0, atts = script.attributes, n = atts.length; i < n; i++){
|
|
33
|
-
att = atts[i];
|
|
34
|
-
scr.setAttribute(att.nodeName, att.nodeValue ?? '');
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const dialog = document.createElement('dialog');
|
|
38
|
-
dialog.className = 'search-dialog';
|
|
39
|
-
dialog.append(close, form, results);
|
|
40
|
-
|
|
41
|
-
document.body.appendChild(dialog);
|
|
42
|
-
document.body.appendChild(scr);
|
|
43
|
-
|
|
44
|
-
function openSearch(e) {
|
|
45
|
-
e.preventDefault();
|
|
46
|
-
dialog.showModal();
|
|
47
|
-
icon.setAttribute('aria-expanded', 'true');
|
|
48
|
-
return false;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Open search on CTRL + SPACE
|
|
52
|
-
window.onkeydown = function(e) {
|
|
53
|
-
if (e.ctrlKey && e.key == ' ') {
|
|
54
|
-
return openSearch(e);
|
|
55
|
-
}
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
// Open search on click
|
|
59
|
-
icon.addEventListener('click', openSearch);
|
|
60
|
-
|
|
61
|
-
// Close dialog
|
|
62
|
-
close.addEventListener('click', () => dialog.close());
|
|
63
|
-
dialog.addEventListener('close', () => icon.setAttribute('aria-expanded', 'false'));
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export { enhanceSearchIcon };
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { qs, qsa } from './query.js';
|
|
2
|
-
|
|
3
|
-
function handleClick() {
|
|
4
|
-
|
|
5
|
-
const title = this.dataset.sharetitle ?? qs('meta[property="og:title"]').content;
|
|
6
|
-
const text = this.dataset.sharetext ?? qs('meta[property="og:description"]').content;
|
|
7
|
-
const url = this.dataset.shareurl ?? document.location;
|
|
8
|
-
|
|
9
|
-
const share = {
|
|
10
|
-
title: title,
|
|
11
|
-
text: text,
|
|
12
|
-
url: url
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
if (navigator.share) {
|
|
16
|
-
navigator.share(share);
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function enableSharing() {
|
|
21
|
-
const canShare = !!navigator.share;
|
|
22
|
-
qsa('[data-share]').forEach((elem) => {
|
|
23
|
-
if (canShare) {
|
|
24
|
-
elem.addEventListener('click', handleClick);
|
|
25
|
-
} else {
|
|
26
|
-
elem.style.display = 'none';
|
|
27
|
-
}
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export { enableSharing }
|
|
@@ -1,77 +0,0 @@
|
|
|
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
|
-
* Looks for a search within a string
|
|
16
|
-
*
|
|
17
|
-
* @param {string} string
|
|
18
|
-
* @param {string} search
|
|
19
|
-
* @returns
|
|
20
|
-
*/
|
|
21
|
-
function containsWord(string, search) {
|
|
22
|
-
return string.split(' ').indexOf(search) > -1;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
*
|
|
27
|
-
* @param {string} string
|
|
28
|
-
* @param {string[]} terms
|
|
29
|
-
* @returns
|
|
30
|
-
*/
|
|
31
|
-
function highlight(string, terms) {
|
|
32
|
-
terms.forEach(term => {
|
|
33
|
-
const regEx = new RegExp(term, "ig");
|
|
34
|
-
const matches = string.match(regEx);
|
|
35
|
-
if (matches) {
|
|
36
|
-
string = string.replace(regEx, `<mark>${matches[0]}</mark>`)
|
|
37
|
-
}
|
|
38
|
-
});
|
|
39
|
-
return string;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Simplifies a string to plain lower case, removing diacritic characters and hyphens
|
|
44
|
-
* This means a search for "co-op" will be found in "COOP" and "Café" will be found in "cafe"
|
|
45
|
-
* @param {string} string
|
|
46
|
-
* @returns {string}
|
|
47
|
-
*/
|
|
48
|
-
function sanitise(string) {
|
|
49
|
-
// @ts-ignore
|
|
50
|
-
if (String.prototype.normalize) {
|
|
51
|
-
// Reduces diacritic characters to plain characters
|
|
52
|
-
string.trim().normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase().replace(/-/g, '');
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Some browsers can't normalise strings
|
|
56
|
-
return string.trim().toLowerCase().replace(/-/g, '');
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Sets a minimum length for a search
|
|
61
|
-
* @param {string} string
|
|
62
|
-
* @returns
|
|
63
|
-
*/
|
|
64
|
-
function isLongEnough(string) {
|
|
65
|
-
return string.length > 1;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
*
|
|
70
|
-
* @param {string} string
|
|
71
|
-
* @returns {string[]}
|
|
72
|
-
*/
|
|
73
|
-
function explode(string) {
|
|
74
|
-
return string.split(' ').filter(isLongEnough).map(sanitise);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export { contains, containsWord, sanitise, explode, highlight };
|
package/public/js/modules/toc.js
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
// @ts-check
|
|
2
|
-
|
|
3
|
-
import { qsa } from './query.js';
|
|
4
|
-
|
|
5
|
-
let links = [];
|
|
6
|
-
let current = '';
|
|
7
|
-
const headings = [];
|
|
8
|
-
const highlightClass = 'highlight';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Makes an entire block clickable based on a data-attribute, usually "data-destination"
|
|
12
|
-
*
|
|
13
|
-
* Example: You have a list of blog posts, including featured images. If you make the title
|
|
14
|
-
* clickable, clicks on the image won't open the blog. Adding links to the images means
|
|
15
|
-
* keyboard users have to tab twice as much to get through the list.
|
|
16
|
-
*
|
|
17
|
-
* Use clickable blocks to allow keyboard users to tab through the real links, but still
|
|
18
|
-
* capture clicks elsewhere on the block.
|
|
19
|
-
*
|
|
20
|
-
*/
|
|
21
|
-
function highlightCurrentHeading(tocSelector) {
|
|
22
|
-
links = qsa(tocSelector);
|
|
23
|
-
|
|
24
|
-
links.forEach((link) => {
|
|
25
|
-
const bookmarkLink = getBookmarkLink(link.href);
|
|
26
|
-
if (bookmarkLink) {
|
|
27
|
-
headings.push(document.getElementById(bookmarkLink));
|
|
28
|
-
}
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
recheck();
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function getBookmarkLink(link) {
|
|
35
|
-
const linkParts = link.split('#');
|
|
36
|
-
if (linkParts.length === 2) {
|
|
37
|
-
return linkParts[1];
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return '';
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function highlight(id) {
|
|
44
|
-
links.forEach((link) => {
|
|
45
|
-
link.classList.remove(highlightClass);
|
|
46
|
-
|
|
47
|
-
const bookmarkLink = getBookmarkLink(link.href);
|
|
48
|
-
if (bookmarkLink === id) {
|
|
49
|
-
link.classList.add(highlightClass);
|
|
50
|
-
}
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function recheck() {
|
|
55
|
-
const docTop = Math.floor(document.documentElement.scrollTop);
|
|
56
|
-
const vh = Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0)
|
|
57
|
-
|
|
58
|
-
const validItems = [];
|
|
59
|
-
|
|
60
|
-
headings.forEach((elem) => {
|
|
61
|
-
|
|
62
|
-
const hasPassed = (elem.offsetTop < docTop);
|
|
63
|
-
const inView = (elem.offsetTop > docTop) && (elem.offsetTop < (docTop + vh));
|
|
64
|
-
const isValid = (docTop + vh) - elem.offsetTop > (vh / 1.5);
|
|
65
|
-
|
|
66
|
-
if (isValid) {
|
|
67
|
-
validItems.push(elem);
|
|
68
|
-
}
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
const item = validItems.pop();
|
|
72
|
-
|
|
73
|
-
if (item && item.id !== current) {
|
|
74
|
-
console.log('Reading', item.id);
|
|
75
|
-
current = item.id;
|
|
76
|
-
highlight(item.id);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
window.setTimeout(recheck, 1000);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export { highlightCurrentHeading };
|
|
@@ -1,44 +0,0 @@
|
|
|
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
|
-
if (video.parentNode.childNodes.length > 1) {
|
|
10
|
-
// Don't turn video into embed if it's part of a longer paragraph, for example.
|
|
11
|
-
continue;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const id = new URL(video.href).searchParams.get('v');
|
|
15
|
-
video.setAttribute('data-youtube', id);
|
|
16
|
-
video.classList.add('init');
|
|
17
|
-
video.setAttribute('role', 'button');
|
|
18
|
-
|
|
19
|
-
video.innerHTML = `<div class="yt-video">
|
|
20
|
-
<div class="play-icon" style="background-image: url(https://img.youtube.com/vi/${id}/0.jpg)">▶</div>
|
|
21
|
-
<div class="title">${video.textContent}</div>
|
|
22
|
-
</div>`;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function clickHandler (event) {
|
|
26
|
-
var link = event.target.closest('[data-youtube]');
|
|
27
|
-
|
|
28
|
-
if (!link) {
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
event.preventDefault();
|
|
33
|
-
var id = link.getAttribute('data-youtube');
|
|
34
|
-
|
|
35
|
-
var player = document.createElement('div');
|
|
36
|
-
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>`;
|
|
37
|
-
|
|
38
|
-
link.replaceWith(player);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
document.addEventListener('click', clickHandler);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export { enhanceYoutubeLinks }
|