@xiee/utils 1.3.18 → 1.3.19
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 +6 -0
- package/js/fuse-search.js +87 -0
- package/js/fuse-search.min.js +1 -0
- package/js/post-nav.js +5 -5
- package/js/post-nav.min.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -62,6 +62,12 @@ Find `<pre>`, `<table>`, and TOC (with ID `TableOfContents`) elements and add
|
|
|
62
62
|
the `fullwidth` class to them if they are too wide, so they can be styled
|
|
63
63
|
differently (e.g., [full bleed](https://css-tricks.com/full-bleed/)).
|
|
64
64
|
|
|
65
|
+
## fuse-search.js
|
|
66
|
+
|
|
67
|
+
Perform client-side site searching via [Fuse.js](https://www.fusejs.io). See
|
|
68
|
+
[this post](https://yihui.org/en/2023/09/fuse-search/) for an application to
|
|
69
|
+
Hugo sites.
|
|
70
|
+
|
|
65
71
|
## hash-notes.js
|
|
66
72
|
|
|
67
73
|
Convert HTML comments of the form `<!--# comments -->` to
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// perform searching via Fuse.js with data from /index.json
|
|
2
|
+
(function(d) {
|
|
3
|
+
const input = d.querySelector('#search-input'),
|
|
4
|
+
output = d.querySelector('.search-results');
|
|
5
|
+
if (!input || !output) return;
|
|
6
|
+
|
|
7
|
+
const ds = input.dataset;
|
|
8
|
+
// load search index for Fuse.js
|
|
9
|
+
let fuse;
|
|
10
|
+
input.addEventListener('focus', e => {
|
|
11
|
+
if (fuse) return;
|
|
12
|
+
input.placeholder = ds.infoInit ||
|
|
13
|
+
'Loading search index... Please hold on.';
|
|
14
|
+
const request = new XMLHttpRequest();
|
|
15
|
+
request.responseType = 'json';
|
|
16
|
+
request.addEventListener('load', e => {
|
|
17
|
+
const res = request.response;
|
|
18
|
+
if (!res || res.length === 0) {
|
|
19
|
+
input.placeholder = ds.infoFail ||
|
|
20
|
+
'Failed to load search index!';
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
input.placeholder = ds.infoOk || 'Type to search:';
|
|
24
|
+
input.focus();
|
|
25
|
+
fuse = new Fuse(res, {
|
|
26
|
+
keys: [{name: 'title', weight: 5}, 'content'],
|
|
27
|
+
useExtendedSearch: true,
|
|
28
|
+
includeMatches: true,
|
|
29
|
+
ignoreLocation: true,
|
|
30
|
+
threshold: 0.1
|
|
31
|
+
});
|
|
32
|
+
}, false);
|
|
33
|
+
request.open('GET', ds.indexUrl || '/index.json');
|
|
34
|
+
request.send(null);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// highlight the keyword of the maximum length in content
|
|
38
|
+
function highlight(res, key, len) {
|
|
39
|
+
let indices;
|
|
40
|
+
for (let m of res.matches) {
|
|
41
|
+
if (m.key === key) indices = m.indices;
|
|
42
|
+
}
|
|
43
|
+
const text = res.item[key];
|
|
44
|
+
if (!indices) return text.substr(0, len);
|
|
45
|
+
let p, pair, k = 0, n = Math.ceil(len / 2);
|
|
46
|
+
while (pair = indices.shift()) {
|
|
47
|
+
if (pair[1] - pair[0] >= k) {
|
|
48
|
+
p = pair;
|
|
49
|
+
k = p[1] - p[0];
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return (p[0] - n > 0 ? '[...] ' : '') + text.substring(p[0] - n, p[0]) +
|
|
53
|
+
'<b>' + text.substring(p[0], p[1] + 1) + '</b>' +
|
|
54
|
+
text.substring(p[1] + 1, p[1] + 1 + n) +
|
|
55
|
+
(p[1] + 1 + n < text.length ? ' [...] ' : '');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// debounce the search for better performance and UX
|
|
59
|
+
function debounce(fn, delay) {
|
|
60
|
+
let timeout;
|
|
61
|
+
return function(...args) {
|
|
62
|
+
clearTimeout(timeout);
|
|
63
|
+
timeout = setTimeout(() => fn(...args), delay);
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
const len = ds.textLength || 300, // number of chars for each search result
|
|
67
|
+
lim = ds.limit || 50, // max number of search results
|
|
68
|
+
delay = ds.delay || 500, // search delay after input
|
|
69
|
+
tpl = output.firstElementChild.cloneNode(true); // search result template
|
|
70
|
+
output.innerHTML = '';
|
|
71
|
+
function search() {
|
|
72
|
+
if (!fuse) return;
|
|
73
|
+
let res, sec, u;
|
|
74
|
+
output.innerHTML = '';
|
|
75
|
+
// display search results in <section> and highlight keywords
|
|
76
|
+
for (res of fuse.search(input.value, {'limit': lim})) {
|
|
77
|
+
sec = tpl.cloneNode(true);
|
|
78
|
+
a = sec.querySelector('a');
|
|
79
|
+
a.href = res.item.uri;
|
|
80
|
+
a.innerHTML = highlight(res, 'title', len);
|
|
81
|
+
sec.querySelector('.search-preview').innerHTML = highlight(res, 'content', len);
|
|
82
|
+
output.appendChild(sec);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const isMobi = /Mobi/i.test(navigator.userAgent);
|
|
86
|
+
input.addEventListener(isMobi ? 'change' : 'input', isMobi ? search : debounce(search, delay));
|
|
87
|
+
})(document);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
!function(e){const t=e.querySelector("#search-input"),n=e.querySelector(".search-results");if(!t||!n)return;const i=t.dataset;let r;function o(e,t,n){let i;for(let n of e.matches)n.key===t&&(i=n.indices);const r=e.item[t];if(!i)return r.substr(0,n);let o,s,l=0,c=Math.ceil(n/2);for(;s=i.shift();)s[1]-s[0]>=l&&(o=s,l=o[1]-o[0]);return(o[0]-c>0?"[...] ":"")+r.substring(o[0]-c,o[0])+"<b>"+r.substring(o[0],o[1]+1)+"</b>"+r.substring(o[1]+1,o[1]+1+c)+(o[1]+1+c<r.length?" [...] ":"")}t.addEventListener("focus",(e=>{if(r)return;t.placeholder=i.infoInit||"Loading search index... Please hold on.";const n=new XMLHttpRequest;n.responseType="json",n.addEventListener("load",(e=>{const o=n.response;o&&0!==o.length?(t.placeholder=i.infoOk||"Type to search:",t.focus(),r=new Fuse(o,{keys:[{name:"title",weight:5},"content"],useExtendedSearch:!0,includeMatches:!0,ignoreLocation:!0,threshold:.1})):t.placeholder=i.infoFail||"Failed to load search index!"}),!1),n.open("GET",i.indexUrl||"/index.json"),n.send(null)}));const s=i.textLength||300,l=i.limit||50,c=i.delay||500,u=n.firstElementChild.cloneNode(!0);function d(){if(!r)return;let e,i;for(e of(n.innerHTML="",r.search(t.value,{limit:l})))i=u.cloneNode(!0),a=i.querySelector("a"),a.href=e.item.uri,a.innerHTML=o(e,"title",s),i.querySelector(".search-preview").innerHTML=o(e,"content",s),n.appendChild(i)}n.innerHTML="";const h=/Mobi/i.test(navigator.userAgent);t.addEventListener(h?"change":"input",h?d:function(e,t){let n;return function(...i){clearTimeout(n),n=setTimeout((()=>e(...i)),t)}}(d,c))}(document);
|
package/js/post-nav.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
// navigate to previous/next posts by Left/Right arrows
|
|
1
|
+
// navigate to previous/next posts by Left/Right arrows, and Alt + <-/-> for back/forward
|
|
2
2
|
(function(d) {
|
|
3
3
|
const a1 = d.querySelector('.nav-prev > a'), a2 = d.querySelector('.nav-next > a');
|
|
4
4
|
d.addEventListener('keyup', function(e) {
|
|
5
5
|
if (e.target.nodeName.toUpperCase() != 'BODY') return;
|
|
6
6
|
let u;
|
|
7
|
-
if (
|
|
8
|
-
u = a1.href;
|
|
9
|
-
} else if (
|
|
10
|
-
u = a2.href;
|
|
7
|
+
if (e.key === 'ArrowLeft') {
|
|
8
|
+
e.altKey ? history.back() : (a1 && (u = a1.href));
|
|
9
|
+
} else if (e.key == 'ArrowRight') {
|
|
10
|
+
e.altKey ? history.forward() : (a2 && (u = a2.href));
|
|
11
11
|
}
|
|
12
12
|
if (u) window.location = u;
|
|
13
13
|
});
|
package/js/post-nav.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
!function(e){const r=e.querySelector(".nav-prev > a"),t=e.querySelector(".nav-next > a");e.addEventListener("keyup",(function(e){if("BODY"!=e.target.nodeName.toUpperCase())return;let n;
|
|
1
|
+
!function(e){const r=e.querySelector(".nav-prev > a"),t=e.querySelector(".nav-next > a");e.addEventListener("keyup",(function(e){if("BODY"!=e.target.nodeName.toUpperCase())return;let n;"ArrowLeft"===e.key?e.altKey?history.back():r&&(n=r.href):"ArrowRight"==e.key&&(e.altKey?history.forward():t&&(n=t.href)),n&&(window.location=n)}));const n=e.querySelectorAll(".unlist");if(0===n.length)return;if(null!==sessionStorage.getItem("hide-notes"))return n.forEach((e=>e.classList.remove("unlist")));r&&t&&(window.location=e.referrer===t.href?r.href:t.href)}(document);
|