@xiee/utils 1.3.18 → 1.3.20

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
@@ -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,86 @@
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
+ output.innerHTML = '';
74
+ // display search results in <section> and highlight keywords
75
+ for (let res of fuse.search(input.value, {'limit': lim})) {
76
+ const sec = tpl.cloneNode(true);
77
+ const a = sec.querySelector('a');
78
+ a.href = res.item.uri;
79
+ a.innerHTML = highlight(res, 'title', len);
80
+ sec.querySelector('.search-preview').innerHTML = highlight(res, 'content', len);
81
+ output.appendChild(sec);
82
+ }
83
+ }
84
+ const isMobi = /Mobi/i.test(navigator.userAgent);
85
+ input.addEventListener(isMobi ? 'change' : 'input', isMobi ? search : debounce(search, delay));
86
+ })(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 o;function r(e,t,n){let i;for(let n of e.matches)n.key===t&&(i=n.indices);const o=e.item[t];if(!i)return o.substr(0,n);let r,s,c=0,l=Math.ceil(n/2);for(;s=i.shift();)s[1]-s[0]>=c&&(r=s,c=r[1]-r[0]);return(r[0]-l>0?"[...] ":"")+o.substring(r[0]-l,r[0])+"<b>"+o.substring(r[0],r[1]+1)+"</b>"+o.substring(r[1]+1,r[1]+1+l)+(r[1]+1+l<o.length?" [...] ":"")}t.addEventListener("focus",(e=>{if(o)return;t.placeholder=i.infoInit||"Loading search index... Please hold on.";const n=new XMLHttpRequest;n.responseType="json",n.addEventListener("load",(e=>{const r=n.response;r&&0!==r.length?(t.placeholder=i.infoOk||"Type to search:",t.focus(),o=new Fuse(r,{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,c=i.limit||50,l=i.delay||500,a=n.firstElementChild.cloneNode(!0);function u(){if(o){n.innerHTML="";for(let e of o.search(t.value,{limit:c})){const t=a.cloneNode(!0),i=t.querySelector("a");i.href=e.item.uri,i.innerHTML=r(e,"title",s),t.querySelector(".search-preview").innerHTML=r(e,"content",s),n.appendChild(t)}}}n.innerHTML="";const d=/Mobi/i.test(navigator.userAgent);t.addEventListener(d?"change":"input",d?u:function(e,t){let n;return function(...i){clearTimeout(n),n=setTimeout((()=>e(...i)),t)}}(u,l))}(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 (a1 && e.which == 37) { // Left arrow
8
- u = a1.href;
9
- } else if (a2 && e.which == 39) { // Right arrow
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
  });
@@ -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;r&&37==e.which?n=r.href:t&&39==e.which&&(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);
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xiee/utils",
3
- "version": "1.3.18",
3
+ "version": "1.3.20",
4
4
  "description": "Miscellaneous tools and utilities to manipulate HTML pages",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1"