@xiee/utils 1.3.17 → 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 CHANGED
@@ -58,9 +58,15 @@ Fix the table of contents generated by lower versions of Hugo.
58
58
 
59
59
  ## fullwidth.js
60
60
 
61
- Find `<pre>` and `<table>` elements and add the `fullwidth` class to them if
62
- they are too wide, so they can be styled differently (e.g., [full
63
- bleed](https://css-tricks.com/full-bleed/)).
61
+ Find `<pre>`, `<table>`, and TOC (with ID `TableOfContents`) elements and add
62
+ the `fullwidth` class to them if they are too wide, so they can be styled
63
+ differently (e.g., [full bleed](https://css-tricks.com/full-bleed/)).
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.
64
70
 
65
71
  ## hash-notes.js
66
72
 
package/js/fullwidth.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // look for overflowed <pre> and <table>, and assign .fullwidth class to them
2
- document.querySelectorAll('pre,table').forEach(node => {
2
+ document.querySelectorAll('pre,table,#TableOfContents').forEach(node => {
3
3
  function fullwidth(el) {
4
4
  el.classList.add('fullwidth');
5
5
  }
@@ -13,5 +13,10 @@ document.querySelectorAll('pre,table').forEach(node => {
13
13
  p && p.offsetWidth < node.offsetWidth && fullwidth(node);
14
14
  break;
15
15
  default:
16
+ // assume it's #TableOfContents for now
17
+ node.querySelectorAll('a').forEach(a => {
18
+ // if a TOC line is wrapped, make TOC full-width
19
+ !node.classList.contains('fullwidth') && a.getClientRects().length > 1 && fullwidth(node);
20
+ });
16
21
  }
17
22
  });
@@ -1 +1 @@
1
- document.querySelectorAll("pre,table").forEach((t=>{function e(t){t.classList.add("fullwidth")}switch(t.tagName){case"PRE":const s=t.firstElementChild;"CODE"===s?.tagName&&s.scrollWidth>s.offsetWidth&&e(s);break;case"TABLE":const a=t.parentElement;a&&a.offsetWidth<t.offsetWidth&&e(t)}}));
1
+ document.querySelectorAll("pre,table,#TableOfContents").forEach((t=>{function e(t){t.classList.add("fullwidth")}switch(t.tagName){case"PRE":const l=t.firstElementChild;"CODE"===l?.tagName&&l.scrollWidth>l.offsetWidth&&e(l);break;case"TABLE":const a=t.parentElement;a&&a.offsetWidth<t.offsetWidth&&e(t);break;default:t.querySelectorAll("a").forEach((l=>{!t.classList.contains("fullwidth")&&l.getClientRects().length>1&&e(t)}))}}));
@@ -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 (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.17",
3
+ "version": "1.3.19",
4
4
  "description": "Miscellaneous tools and utilities to manipulate HTML pages",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1"