staticsearch
Advanced tools
+12
| // utility functions | ||
| import process from 'node:process'; | ||
| // process an environment variable | ||
| export function env(name, def = null) { | ||
| const val = process.env[name]; | ||
| if (!val) return def; | ||
| const num = +val; | ||
| return isNaN(num) ? val : num; | ||
| } |
@@ -1,1 +0,1 @@ | ||
| :host{--fsize: var(--staticsearch-fontsize, 1em);--dmax: var(--staticsearch-maxwidth, 60ch);--dmargin: var(--staticsearch-margin, 3vmin);--dpadding: var(--staticsearch-padding, 2vmin);--sheight: var(--staticsearch-fieldset-height, calc(3em + (2 * var(--dpadding))) );--colback0: var(--staticsearch-color-back, Canvas);--colbord0: var(--staticsearch-color-border, ButtonFace);--colfore0: var(--staticsearch-color-fore0, CanvasText);--colfore1: var(--staticsearch-color-fore1, color-mix(in oklab, CanvasText 80%, Canvas));--colfore2: var(--staticsearch-color-fore2, color-mix(in oklab, CanvasText 60%, Canvas));--collinkd: var(--staticsearch-color-link, color-mix(in oklab, LinkText 80%, CanvasText));--collinkv: var(--staticsearch-color-visited, color-mix(in oklab, VisitedText 80%, CanvasText));--colshad0: var(--staticsearch-color-shadow, #000);--colshad1: var(--staticsearch-color-backdrop, color-mix(in srgb, var(--colshad0), transparent 25%));--colblur0: var(--staticsearch-backdrop-blur, 3px)}template{display:none}*{box-sizing:border-box;font-size:inherit;padding:0;margin:0}@media(prefers-reduced-motion:reduce){*,*:before,*:after,::backdrop{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important;transition-behavior:normal!important;scroll-behavior:auto!important}}dialog{position:fixed;font-size:var(--fsize);inline-size:var(--dmax);block-size:100%;max-inline-size:calc(100dvw - (2 * var(--dmargin)));max-block-size:calc(100dvh - (2 * var(--dmargin)));margin:auto;padding:0;color:var(--colfore0);background-color:var(--colback0);border:1px solid var(--colbord0);border-radius:5px;box-shadow:0 2px 4px var(--colshad0);overflow:hidden;transform-origin:50% 0;&,&::backdrop{transition:all .3s ease-in-out;transition-behavior:allow-discrete}&[open]{opacity:1;transform:scale(1);@starting-style{display:block;opacity:.5;transform:scaleX(.5) scaleY(0)}&::backdrop{background-color:var(--colshad1);-webkit-backdrop-filter:blur(var(--colblur0));backdrop-filter:blur(var(--colblur0));@starting-style{background-color:#0000;-webkit-backdrop-filter:blur(0);backdrop-filter:blur(0)}}}&:not([open]){display:none;opacity:0;transform:scaleX(.5) scaleY(0)}}[part=close]{position:absolute;inset-inline-end:0;inset-block-start:0;block-size:1.4em;inline-size:1.4em;min-block-size:unset;min-inline-size:unset;padding:0;margin:0;background-color:transparent;border:0 none;cursor:pointer;z-index:3;&:is(:hover,:focus,:active){color:var(--collinkd)}}[part=search]{position:absolute;block-size:var(--sheight);inset-inline:0;inset-block-start:0;padding-inline:var(--dpadding);padding-block-start:var(--dpadding);box-shadow:0 1px 5px var(--colshad0);overflow:hidden;z-index:2;&:focus-within{label,input{color:var(--colfore0)}input{border-color:var(--colfore1)}}}[part=searchlabel]{display:block;font-size:.8em;padding-inline-start:.625em;padding-block-end:.2em;color:var(--colfore1);-webkit-user-select:none;user-select:none}input[part=searchinput]{display:block;inline-size:100%;line-height:1.5;padding-block:.2em;padding-inline:.5em;color:var(--colfore1);background-color:transparent;border:1px solid transparent;border-radius:3px;outline-style:none}[part=results]{position:absolute;inset-inline:0;inset-block:0;inset-block-start:var(--sheight);padding:var(--dpadding);overflow-x:hidden;overflow-y:auto;&:after{position:fixed;content:"";block-size:5vh;inset-inline:0;inset-block-end:0;background:linear-gradient(transparent,var(--colback0));pointer-events:none}}[part=searchterm]{font-style:italic;&:before,&:after{content:'"'}}[part=item]{margin-block:1.5em;margin-inline-start:1.5em}[part=title]{font-weight:bolder;line-height:1.2;text-wrap:balance;text-underline-offset:.2em;margin-block-end:.1em}[part=meta]{font-size:.75em;line-height:1.4;color:var(--colfore2)}[part=description]{font-size:.85em;line-height:1.4;text-wrap:pretty;color:var(--colfore1)}a:focus-visible{outline-color:var(--collinkd);outline-style:solid;outline-offset:.2em;outline-width:.2rem}a:link{display:block;text-decoration:none;color:var(--collinkd)}a:visited{color:var(--collinkv)}a:hover,a:focus,a:active{color:var(--collinkd);[part=title]{text-decoration:underline}} | ||
| :host{--fsize: var(--staticsearch-fontsize, 1em);--dmax: var(--staticsearch-maxwidth, 60ch);--dmargin: var(--staticsearch-margin, 3vmin);--dpadding: var(--staticsearch-padding, 2vmin);--sheight: var(--staticsearch-fieldset-height, calc(3em + (2 * var(--dpadding))) );--colback0: var(--staticsearch-color-back, Canvas);--colbord0: var(--staticsearch-color-border, ButtonFace);--colfore0: var(--staticsearch-color-fore0, CanvasText);--colfore1: var(--staticsearch-color-fore1, color-mix(in oklab, CanvasText 80%, Canvas));--colfore2: var(--staticsearch-color-fore2, color-mix(in oklab, CanvasText 60%, Canvas));--collinkd: var(--staticsearch-color-link, color-mix(in oklab, LinkText 80%, CanvasText));--collinkv: var(--staticsearch-color-visited, color-mix(in oklab, VisitedText 80%, CanvasText));--colshad0: var(--staticsearch-color-shadow, #000);--colshad1: var(--staticsearch-color-backdrop, color-mix(in srgb, var(--colshad0), transparent 25%));--colblur0: var(--staticsearch-backdrop-blur, 3px)}template{display:none}*{box-sizing:border-box;font-size:inherit;padding:0;margin:0}@media(prefers-reduced-motion:reduce){*,*:before,*:after,::backdrop{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important;transition-behavior:normal!important;scroll-behavior:auto!important}}[part=activate]{color:var(--colfore2);outline:none;&:hover,&:active,&:focus{color:var(--collinkd)}}dialog{position:fixed;font-size:var(--fsize);inline-size:var(--dmax);block-size:100%;max-inline-size:calc(100dvi - (2 * var(--dmargin)));max-block-size:calc(100dvb - (2 * var(--dmargin)));margin:auto;padding:0;color:var(--colfore0);background-color:var(--colback0);border:1px solid var(--colbord0);border-radius:5px;box-shadow:0 2px 4px var(--colshad0);overflow:hidden;transform-origin:50% 0;&,&::backdrop{transition:all .3s ease-in-out;transition-behavior:allow-discrete}&[open]{opacity:1;transform:scale(1);@starting-style{display:block;opacity:.5;transform:scaleX(.5) scaleY(0)}&::backdrop{background-color:var(--colshad1);-webkit-backdrop-filter:blur(var(--colblur0));backdrop-filter:blur(var(--colblur0));@starting-style{background-color:#0000;-webkit-backdrop-filter:blur(0);backdrop-filter:blur(0)}}}&:not([open]){display:none;opacity:0;transform:scaleX(.5) scaleY(0)}}[part=close]{position:absolute;inset-inline-end:0;inset-block-start:0;block-size:1.4em;inline-size:1.4em;min-block-size:unset;min-inline-size:unset;padding:0;margin:0;background-color:transparent;border:0 none;cursor:pointer;z-index:3;&:is(:hover,:focus,:active){color:var(--collinkd)}}[part=search]{position:absolute;block-size:var(--sheight);inset-inline:0;inset-block-start:0;padding-inline:var(--dpadding);padding-block-start:var(--dpadding);box-shadow:0 1px 5px var(--colshad0);overflow:hidden;z-index:2;&:focus-within{label,input{color:var(--colfore0)}input{border-color:var(--colfore1)}}}[part=searchlabel]{display:block;font-size:.8em;padding-inline-start:.625em;padding-block-end:.2em;color:var(--colfore1);-webkit-user-select:none;user-select:none}input[part=searchinput]{display:block;inline-size:100%;line-height:1.5;padding-block:.2em;padding-inline:.5em;color:var(--colfore1);background-color:transparent;border:1px solid transparent;border-radius:3px;outline-style:none}[part=results]{position:absolute;inset-inline:0;inset-block:0;inset-block-start:var(--sheight);padding:var(--dpadding);overflow:hidden auto;scrollbar-width:thin;scrollbar-color:var(--colfore2) transparent;overscroll-behavior-block:contain;outline:none;&:after{position:fixed;content:"";block-size:5vb;inset-inline:var(--dpadding);inset-block-end:0;background:linear-gradient(transparent,var(--colback0));pointer-events:none}}[part=searchterm]{font-style:italic;&:before,&:after{content:'"'}}[part=item]{margin-block:1.5em;margin-inline-start:1.5em}[part=title]{font-weight:bolder;line-height:1.2;text-wrap:balance;text-underline-offset:.2em;margin-block-end:.1em}[part=meta]{font-size:.75em;line-height:1.4;color:var(--colfore2)}[part=description]{font-size:.85em;line-height:1.4;text-wrap:pretty;color:var(--colfore1)}a:focus-visible{outline-color:var(--collinkd);outline-style:solid;outline-offset:.2em;outline-width:.2rem}a:link{display:block;text-decoration:none;color:var(--collinkd)}a:visited{color:var(--collinkv)}a:hover,a:focus,a:active{color:var(--collinkd);[part=title]{text-decoration:underline}} |
@@ -1,1 +0,1 @@ | ||
| import{staticsearch as v}from"./__SSDIR__/staticsearch.js";var E="q",y=0,b="__AGENT__",S=Symbol(b),C=500,w={date:new Intl.DateTimeFormat([],{dateStyle:"long"}).format,number:new Intl.NumberFormat([],{maximumFractionDigits:0}).format};(()=>{let t=document.getElementById("staticsearch_search"),e=document.getElementById("staticsearch_result");e&&A(e,{minFound:e.getAttribute("minfound"),minScore:e.getAttribute("minscore"),maxResults:e.getAttribute("maxresults"),highlight:e.hasAttribute("highlight")}),t&&t.tagName==="INPUT"&&R(t)})();function x(){return new URLSearchParams(location.search).get(E)||""}function L(t,e){let a=new URL(location);t?a.searchParams.set(E,t):a.searchParams.delete(E),a.hash=e||"",history.replaceState({},"",a.href)}function R(t){if(t[S])return;t[S]=!0,t.name&&(E=t.name);let e=x()||sessionStorage.getItem(b)||"";e&&(t.value=e);let a=t.value;a&&m(a);let h;t.addEventListener("input",n=>{let d=n.target.value;clearTimeout(h),h=setTimeout(()=>m(d),C)},!1);function m(n){sessionStorage.setItem(b,n),!(n.length<2)&&v.find(n).then(d=>{})}}function A(t,e={}){if(t[S])return;t[S]=!0;let a="",h=parseFloat(e.minFound)||0,m=parseFloat(e.minScore)||0,n=parseFloat(e.maxResults)||0,d=!!e.highlight,I=e.resultElement||"ol",p=e.messageTemplate||document.getElementById("staticsearch_resultmessage");p||(p=document.createElement("template"),p.innerHTML='<p part="resultmessage"><span part="resultcount">0</span> found for <span part="searchterm"></span>…</p>');let f=e.itemTemplate||document.getElementById("staticsearch_item");f||(f=document.createElement("template"),f.innerHTML='<li part="item"><a part="link"><h2 part="title"></h2><p part="meta"><time part="date"></time> – <span part="words">0</span> words</p><p part="description"></p></a></li>'),t.addEventListener("click",i=>{let c=i.target.closest("a");c&&a&&L(a,c.id)}),document.addEventListener("staticsearch:result",i=>{a=i.detail.search;let c=i.detail.result,g=d?"#:~:"+a.split(/\W+/).reverse().map(r=>"text="+encodeURIComponent(r.trim().toLowerCase())).join("&"):"";t.innerHTML="";let l=document.createElement(I);l.setAttribute("part","searchresult"),c.forEach((r,T)=>{if(r.found<h||m&&r.relevancy<m||n&&T>=n)return;let u=f.content.cloneNode(!0);o(u,"link",null,{href:r.url+g,id:`staticsearchresult-${r.id}`}),o(u,"title",r.title),o(u,"description",r.description),r.date&&o(u,"date",w.date(new Date(r.date)),{datetime:r.date}),r.words&&o(u,"words",w.number(r.words)),l.appendChild(u)});let s=p.content.cloneNode(!0);if(o(s,"resultcount",l.childElementCount),o(s,"searchterm",a),t.appendChild(s),t.appendChild(l),!y&&location.hash){let r=t.querySelector(location.hash);r&&(r.focus(),r.scrollIntoView({behavior:"smooth",block:"nearest"}))}else t.scrollTop=0;y++},!1);function o(i,c,g,l={}){i.querySelectorAll(`[part="${c}"]`).forEach(s=>{for(let[r,T]of Object.entries(l))s.setAttribute(r,T);if(g){for(;s.firstElementChild;)s=s.firstElementChild;s.textContent=g}})}}export{R as staticSearchInput,x as staticSearchQuery,A as staticSearchResult,L as staticSearchSetQuery}; | ||
| import{staticsearch as v}from"./__SSDIR__/staticsearch.js";var S="q",w=0,T="__AGENT__",y=Symbol(T),C=500,I={date:new Intl.DateTimeFormat([],{dateStyle:"long"}).format,number:new Intl.NumberFormat([],{maximumFractionDigits:0}).format};(()=>{let t=document.getElementById("staticsearch_search"),r=document.getElementById("staticsearch_result");r&&L(r,{minFound:r.getAttribute("minfound"),minScore:r.getAttribute("minscore"),maxResults:r.getAttribute("maxresults"),highlight:r.hasAttribute("highlight")}),t&&t.tagName==="INPUT"&&F(t,{fuzzy:t.getAttribute("fuzzy")})})();function x(){return new URLSearchParams(location.search).get(S)||""}function A(t,r){let a=new URL(location);t?a.searchParams.set(S,t):a.searchParams.delete(S),a.hash=r||"",history.replaceState({},"",a.href)}function F(t,r={}){if(t[y])return;t[y]=!0;let a=parseFloat(r.fuzzy);(isNaN(a)||a<0)&&(a=6),t.name&&(S=t.name);let p=x()||sessionStorage.getItem(T)||"";p&&(t.value=p);let h=t.value;h&&f(h);let d;t.addEventListener("input",n=>{let o=n.target.value;clearTimeout(d),d=setTimeout(()=>f(o),C)},!1);function f(n){sessionStorage.setItem(T,n),!(n.length<2)&&v.find(n,a).then(o=>{})}}function L(t,r={}){if(t[y])return;t[y]=!0;let a="",p=parseFloat(r.minFound)||0,h=parseFloat(r.minScore)||0,d=parseFloat(r.maxResults)||0,f=!!r.highlight,n=r.resultElement||"ol",o=r.messageTemplate||document.getElementById("staticsearch_resultmessage");o||(o=document.createElement("template"),o.innerHTML='<p part="resultmessage"><span part="resultcount">0</span> found for <span part="searchterm"></span>…</p>');let g=r.itemTemplate||document.getElementById("staticsearch_item");g||(g=document.createElement("template"),g.innerHTML='<li part="item"><a part="link"><h2 part="title"></h2><p part="meta"><time part="date"></time> – <span part="words">0</span> words</p><p part="description"></p></a></li>'),t.addEventListener("click",l=>{let c=l.target.closest("a");c&&a&&A(a,c.id)}),document.addEventListener("staticsearch:result",l=>{a=l.detail.search;let c=l.detail.result,E=f?"#:~:"+a.split(/\W+/).reverse().map(e=>"text="+encodeURIComponent(e.trim().toLowerCase())).join("&"):"";t.innerHTML="";let u=document.createElement(n);u.setAttribute("part","searchresult"),c.forEach((e,b)=>{if(e.found<p||h&&e.relevancy<h||d&&b>=d)return;let m=g.content.cloneNode(!0);i(m,"link",null,{href:e.url+E,id:`staticsearchresult-${e.id}`}),i(m,"title",e.title),i(m,"description",e.description),e.date&&i(m,"date",I.date(new Date(e.date)),{datetime:e.date}),e.words&&i(m,"words",I.number(e.words)),u.appendChild(m)});let s=o.content.cloneNode(!0);if(i(s,"resultcount",u.childElementCount),i(s,"searchterm",a),t.appendChild(s),t.appendChild(u),!w&&location.hash){let e=t.querySelector(location.hash);e&&(e.focus(),e.scrollIntoView({behavior:"smooth",block:"nearest"}))}else t.scrollTop=0;w++},!1);function i(l,c,E,u={}){l.querySelectorAll(`[part="${c}"]`).forEach(s=>{for(let[e,b]of Object.entries(u))s.setAttribute(e,b);if(E){for(;s.firstElementChild;)s=s.firstElementChild;s.textContent=E}})}}export{F as staticSearchInput,x as staticSearchQuery,L as staticSearchResult,A as staticSearchSetQuery}; |
@@ -1,1 +0,1 @@ | ||
| import{staticSearchQuery as h,staticSearchSetQuery as l,staticSearchInput as o,staticSearchResult as n}from"./__SSDIR__/staticsearch-bind.js";var i=class r extends HTMLElement{static path=new URL(import.meta.url).pathname.replace("__FILENAME__","");#t=null;#e=null;#a=null;connectedCallback(){let t=this.firstElementChild;if(this.attachShadow({mode:"open"}),!t)return;t.setAttribute("part","startsearch");let s=document.createElement("link");s.rel="stylesheet",s.href=`${r.path}css/component.css`,this.shadowRoot.appendChild(s);let a=this.shadowRoot.appendChild(t);this.#r(),a.style.cursor="pointer",a.addEventListener("click",e=>{e.preventDefault(),this.#s()}),window.addEventListener("keydown",e=>{e.key==="k"&&(e.ctrlKey||e.metaKey)&&(e.preventDefault(),this.#s())}),h()&&this.#s()}#s(){this.#t.open?this.#i():(this.#t.showModal(),this.setAttribute("aria-expanded","true"),this.#e.select(),this.#e.focus())}#i(){this.#t.close(),this.removeAttribute("aria-expanded"),l()}#r(){let t=document.createElement("dialog");t.setAttribute("closedby","any"),t.setAttribute("part","dialog"),t.innerHTML=`<form method="dialog"><button part="close"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path xmlns="http://www.w3.org/2000/svg" fill="currentColor" d="M5.3 5.3a1 1 0 0 1 1.4 0l5.3 5.3 5.3-5.3a1 1 0 1 1 1.4 1.4L13.4 12l5.3 5.3a1 1 0 0 1-1.4 1.4L12 13.4l-5.3 5.3a1 1 0 0 1-1.4-1.4l5.3-5.3-5.3-5.3a1 1 0 0 1 0-1.4Z"/></svg></button></form><search part="search"><label for="search" part="searchlabel">${this.getAttribute("label")||"search"}</label><input type="search" id="search" name="q" minlength="2" maxlength="300" part="searchinput" /></search><div part="results"></div>`,this.#t=this.shadowRoot.appendChild(t),this.#e=this.shadowRoot.querySelector("#search"),this.#a=this.shadowRoot.querySelector('[part="results"]'),n(this.#a,{minFound:this.getAttribute("minfound"),minScore:this.getAttribute("minscore"),maxResults:this.getAttribute("maxresults"),highlight:this.hasAttribute("highlight")}),o(this.#e),this.#t.addEventListener("close",()=>this.#i()),this.#t.addEventListener("click",s=>{this.#t.open&&s.target===this.#t&&this.#i()})}};customElements.define("static-search",i); | ||
| import{staticSearchQuery as h,staticSearchSetQuery as l,staticSearchInput as n,staticSearchResult as o}from"./__SSDIR__/staticsearch-bind.js";var a=class r extends HTMLElement{static path=new URL(import.meta.url).pathname.replace("__FILENAME__","");#t=null;#s=null;#a=null;connectedCallback(){let e=this.firstElementChild;if(this.tabIndex=0,this.attachShadow({mode:"open",delegatesFocus:!0}),!e)return;e.getAttribute("part")||e.setAttribute("part","activate");let s=document.createElement("link");s.rel="stylesheet",s.href=`${r.path}css/component.css`,this.shadowRoot.appendChild(s);let i=this.shadowRoot.appendChild(e);this.#r(),i.tabIndex=0,i.style.cursor="pointer",i.addEventListener("click",t=>this.#e(t)),i.addEventListener("keydown",t=>{(t.key==="Enter"||t.key===" ")&&this.#e(t)}),window.addEventListener("keydown",t=>{t.key==="k"&&(t.ctrlKey||t.metaKey)&&this.#e(t)}),window.addEventListener("click",t=>{!this.#t.open&&t.target.dataset.staticSearch&&this.#e(t)}),h()&&this.#e()}#e(e){e&&e.preventDefault(),this.#t.open?this.#i():(this.#t.showModal(),this.setAttribute("aria-expanded","true"),this.#s.select(),this.#s.focus())}#i(){this.#t.close(),this.removeAttribute("aria-expanded"),l()}#r(){let e=document.createElement("dialog");e.setAttribute("closedby","any"),e.setAttribute("part","dialog"),e.innerHTML=`<form method="dialog"><button part="close"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path xmlns="http://www.w3.org/2000/svg" fill="currentColor" d="M5.3 5.3a1 1 0 0 1 1.4 0l5.3 5.3 5.3-5.3a1 1 0 1 1 1.4 1.4L13.4 12l5.3 5.3a1 1 0 0 1-1.4 1.4L12 13.4l-5.3 5.3a1 1 0 0 1-1.4-1.4l5.3-5.3-5.3-5.3a1 1 0 0 1 0-1.4Z"/></svg></button></form><search part="search"><label for="search" part="searchlabel">${this.getAttribute("label")||"search"}</label><input type="search" id="search" name="q" minlength="2" maxlength="300" part="searchinput" /></search><div part="results"></div>`,this.#t=this.shadowRoot.appendChild(e),this.#s=this.shadowRoot.querySelector("#search"),this.#a=this.shadowRoot.querySelector('[part="results"]'),o(this.#a,{minFound:this.getAttribute("minfound"),minScore:this.getAttribute("minscore"),maxResults:this.getAttribute("maxresults"),highlight:this.hasAttribute("highlight")}),n(this.#s,{fuzzy:this.getAttribute("fuzzy")}),this.#t.addEventListener("close",()=>this.#i()),this.#t.addEventListener("click",s=>{this.#t.open&&s.target===this.#t&&this.#i()})}};customElements.define("static-search",a); |
@@ -1,1 +0,1 @@ | ||
| import{stem as p}from"./stem/__STEMFILE__";function d(o,e,t=7,r=new Set){return new Set(o.normalize("NFD").replace(/[\u0300-\u036f]/g,"").replace(/[\u2018\u2019\u201A\u201B\u0027]/g,"").toLowerCase().replace(/[^a-z\s]/g," ").split(/\s+/g).map(s=>e(s.trim()).slice(0,t)).filter(s=>s.length>1&&!r.has(s)).sort())}var h=class{#t=null;#e=null;#r=null;constructor(o,e,t){return this.#e=o||"db",this.#r=e||1,this.#s(t)}connect(){return this.#s().then(()=>!0).catch(()=>!1)}close(){this.#t.close(),this.#t=null}#s(o){return new Promise((e,t)=>{if(!("indexedDB"in window)){t(new Error("No indexedDB support"));return}let r=indexedDB.open(this.#e,this.#r);r.onsuccess=()=>{this.#t=r.result,e(this)},r.onerror=s=>{t(new Error(`IndexedDB error ${s.target.errorCode}: ${s.target.message}`,{cause:s}))},o&&(r.onupgradeneeded=s=>{o(r.result,s.oldVersion,s.newVersion)})})}get isConnected(){return!!this.#t}get name(){return this.#e}get version(){return this.#r}#n(o,e,t){return new Promise((r,s)=>{let{transaction:i,store:l}=this.#a(o,null,!0);i.oncomplete=()=>r(),i.onerror=n=>{s(new Error(n.target.error.message,{cause:n}))},e=Array.isArray(e)?e:[e],e.forEach(n=>{t?l.put(n):l.add(n)}),i.commit()})}add({store:o,item:e=[]}={}){return this.#n(o,e,!1)}put({store:o,item:e=[]}={}){return this.#n(o,e,!0)}#o(o,e,t,r){return new Promise((s,i)=>{r=Array.isArray(r)?r:[r];let l=t==="delete"||t==="clear",n=this.#a(o,e,l).store[t](...r);n.onsuccess=()=>s(n.result),n.onerror=()=>i(n.error??!1)})}count({store:o,index:e,lowerBound:t,upperBound:r}={}){return this.#o(o,e,"count",this.#i(t,r))}get({store:o,index:e,key:t}={}){return this.#o(o,e,"get",t)}getAll({store:o,index:e,lowerBound:t,upperBound:r,count:s}={}){return this.#o(o,e,"getAll",[this.#i(t,r),s])}getAllKeys({store:o,index:e,lowerBound:t,upperBound:r,count:s}={}){return this.#o(o,e,"getAllKeys",[this.#i(t,r),s])}delete({store:o,key:e}={}){return this.#o(o,null,"delete",[e])}deleteAll({store:o,index:e,lowerBound:t,upperBound:r}={}){return this.#o(o,e,"delete",[this.#i(t,r)])}clear({store:o}={}){return this.#o(o,null,"clear")}getCursor({store:o,index:e,lowerBound:t,upperBound:r,direction:s="next",callback:i}={}){return new Promise((l,n)=>{let a=this.#a(o,e).store.openCursor(this.#i(t,r),s);a.onsuccess=()=>{let u=a.result;u?u.advance(i&&i(u)||1):l(!0)},a.onerror=()=>n(a.error)})}drop(){return new Promise((o,e)=>{this.close();let t=indexedDB.deleteDatabase(this.#e);t.onsuccess=()=>{this.#e=null,this.#r=null,o(!0)},t.onerror=()=>e(!1)})}#a(o,e,t){let r=this.#t.transaction(o,t?"readwrite":"readonly",{durability:t?"strict":"default"}),s=r.objectStore(o);return{transaction:r,store:e&&!t?s.index(e):s}}#i(o,e){let t;return o&&e?t=IDBKeyRange.bound(o,e):o?t=IDBKeyRange.lowerBound(o):e&&(t=IDBKeyRange.upperBound(e)),t}};var c=class o{static path=new URL(import.meta.url).pathname.replace("__FILENAME__","");static dbName="__AGENT__";static version="__VERSION__";static wordCrop=__WORDCROP__;#t=!1;#e=null;#r=null;fetchTimeout=5e3;constructor(){}async init(){if(this.#t)return!0;if(this.#e=this.#e||await new h(o.dbName,1,(r,s,i)=>{s===0&&(r.createObjectStore("cfg",{keyPath:"name"}),r.createObjectStore("page",{keyPath:"id"}),r.createObjectStore("file",{keyPath:"id"}),r.createObjectStore("index",{keyPath:"word"}))}),!this.#e?.isConnected)return!1;if((await this.#e.get({store:"cfg",key:"version"}))?.value===o.version)return this.#r=new Set((await this.#e.get({store:"cfg",key:"stopword"}))?.value),this.#t=!0,!0;await Promise.allSettled([this.#e.put({store:"cfg",item:{name:"version",value:o.version}}),this.#e.clear({store:"page"}),this.#e.clear({store:"file"}),this.#e.clear({store:"index"})]);let t=await this.#n("index.json");return t===null?!1:(this.#r=new Set(t.stopword),await this.#e.put({store:"cfg",item:{name:"stopword",value:t.stopword}}),await this.#e.add({store:"page",item:t.page.map((r,s)=>({id:s,url:r.u,title:r.t||r.u,description:r.d||"",date:r.p||"",words:r.w||0}))}),await this.#e.add({store:"file",item:t.file.map(r=>({id:r,loaded:!1}))}),this.#t=!0,!0)}async find(e){if(!this.#t)throw new Error("StaticSearch failed to initialize");e=e.trim(),this.#s("find",{search:e});let t=[],r=[...this.wordList(e)],s=r.length;if(!s)return this.#s("result",{search:e,result:t}),t;await Promise.allSettled(r.map(n=>this.loadIndex(n)));let i={};(await Promise.allSettled(r.map(n=>this.#e.get({store:"index",key:n})))).forEach(n=>{if(n.value?.page)for(let a in n.value.page)i[a]=i[a]||{rel:0,wc:0},i[a].rel+=n.value.page[a],i[a].wc++});let l=[];for(let n in i)l.push({id:n,relevancy:i[n].rel,found:i[n].wc/s});return(await Promise.allSettled(l.map(n=>this.#e.get({store:"page",key:parseFloat(n.id)})))).forEach((n,a)=>{n.value&&(t[a]=n.value,t[a].relevancy=l[a].relevancy,t[a].found=l[a].found)}),t.sort((n,a)=>a.relevancy-n.relevancy||a.found-n.found||new Date(a.date||"0")-new Date(n.date||"0")),this.#s("result",{search:e,result:t}),t}wordList(e){return d(e,p,o.wordCrop,this.#r)}async loadIndex(e){if(!this.#t)throw new Error("StaticSearch failed to initialize");if(e=e.toLowerCase().replace(/[^a-z]/g,"").slice(0,2),!e)return;let t=e.slice(0,e.length-1)+String.fromCharCode(e.charCodeAt(e.length-1)+1),r=(await this.#e.getAll({store:"file",lowerBound:e,upperBound:t})).filter(l=>!l.loaded);if(!r.length)return;let s=[],i=[];(await Promise.allSettled(r.map(l=>this.#n(`data/${l.id}.json`)))).map((l,n)=>{l.value&&(s.push({id:r[n].id,loaded:!0}),i.push(...Object.getOwnPropertyNames(l.value).map(a=>({word:a,page:l.value[a]}))))}),await Promise.allSettled([this.#e.put({store:"file",item:s}),this.#e.put({store:"index",item:i})])}#s(e="unknown",t={}){document.dispatchEvent(new CustomEvent("staticsearch:"+e,{detail:t}))}async#n(e){let t=o.path+e;try{let r=new AbortController,s=setTimeout(()=>r.abort(),this.fetchTimeout),i=await fetch(t,{signal:r.signal});if(clearTimeout(s),!i.ok)throw new Error(`Fetch failed ${t}: ${i.status}`);return await i.json()}catch{return null}}},w=new c;await w.init();export{w as staticsearch}; | ||
| import{stem as w}from"./stem/__STEMFILE__";function h(o,e,r=7,t=new Set){return new Set(o.normalize("NFD").replace(/[\u0300-\u036f]/g,"").replace(/[\u2018\u2019\u201A\u201B\u0027]/g,"").toLowerCase().replace(/[^a-z\s]/g," ").split(/\s+/g).map(s=>e(s.trim()).slice(0,r)).filter(s=>s.length>1&&!t.has(s)).sort())}var p=class{#t=null;#e=null;#r=null;constructor(o,e,r){return this.#e=o||"db",this.#r=e||1,this.#s(r)}connect(){return this.#s().then(()=>!0).catch(()=>!1)}close(){this.#t.close(),this.#t=null}#s(o){return new Promise((e,r)=>{if(!("indexedDB"in window)){r(new Error("No indexedDB support"));return}let t=indexedDB.open(this.#e,this.#r);t.onsuccess=()=>{this.#t=t.result,e(this)},t.onerror=s=>{r(new Error(`IndexedDB error ${s.target.errorCode}: ${s.target.message}`,{cause:s}))},o&&(t.onupgradeneeded=s=>{o(t.result,s.oldVersion,s.newVersion)})})}get isConnected(){return!!this.#t}get name(){return this.#e}get version(){return this.#r}#n(o,e,r){return new Promise((t,s)=>{let{transaction:l,store:a}=this.#a(o,null,!0);l.oncomplete=()=>t(),l.onerror=u=>{s(new Error(u.target.error.message,{cause:u}))},e=Array.isArray(e)?e:[e],e.forEach(u=>{r?a.put(u):a.add(u)}),l.commit()})}add({store:o,item:e=[]}={}){return this.#n(o,e,!1)}put({store:o,item:e=[]}={}){return this.#n(o,e,!0)}#o(o,e,r,t){return new Promise((s,l)=>{t=Array.isArray(t)?t:[t];let a=r==="delete"||r==="clear",u=this.#a(o,e,a).store[r](...t);u.onsuccess=()=>s(u.result),u.onerror=()=>l(u.error??!1)})}count({store:o,index:e,lowerBound:r,upperBound:t}={}){return this.#o(o,e,"count",this.#i(r,t))}get({store:o,index:e,key:r}={}){return this.#o(o,e,"get",r)}getAll({store:o,index:e,lowerBound:r,upperBound:t,count:s}={}){return this.#o(o,e,"getAll",[this.#i(r,t),s])}getAllKeys({store:o,index:e,lowerBound:r,upperBound:t,count:s}={}){return this.#o(o,e,"getAllKeys",[this.#i(r,t),s])}delete({store:o,key:e}={}){return this.#o(o,null,"delete",[e])}deleteAll({store:o,index:e,lowerBound:r,upperBound:t}={}){return this.#o(o,e,"delete",[this.#i(r,t)])}clear({store:o}={}){return this.#o(o,null,"clear")}getCursor({store:o,index:e,lowerBound:r,upperBound:t,direction:s="next",callback:l}={}){return new Promise((a,u)=>{let n=this.#a(o,e).store.openCursor(this.#i(r,t),s);n.onsuccess=()=>{let i=n.result;i?i.advance(l&&l(i)||1):a(!0)},n.onerror=()=>u(n.error)})}drop(){return new Promise((o,e)=>{this.close();let r=indexedDB.deleteDatabase(this.#e);r.onsuccess=()=>{this.#e=null,this.#r=null,o(!0)},r.onerror=()=>e(!1)})}#a(o,e,r){let t=this.#t.transaction(o,r?"readwrite":"readonly",{durability:r?"strict":"default"}),s=t.objectStore(o);return{transaction:t,store:e&&!r?s.index(e):s}}#i(o,e){let r;return o&&e?r=IDBKeyRange.bound(o,e):o?r=IDBKeyRange.lowerBound(o):e&&(r=IDBKeyRange.upperBound(e)),r}};var d=class o{static path=new URL(import.meta.url).pathname.replace("__FILENAME__","");static dbName="__AGENT__";static version="__VERSION__";static wordCrop=__WORDCROP__;#t=!1;#e=null;#r=null;fetchTimeout=5e3;constructor(){}async init(){if(this.#t)return!0;if(this.#e=this.#e||await new p(o.dbName,1,(t,s,l)=>{s===0&&(t.createObjectStore("cfg",{keyPath:"name"}),t.createObjectStore("page",{keyPath:"id"}),t.createObjectStore("file",{keyPath:"id"}),t.createObjectStore("index",{keyPath:"word"}))}),!this.#e?.isConnected)return!1;if((await this.#e.get({store:"cfg",key:"version"}))?.value===o.version)return this.#r=new Set((await this.#e.get({store:"cfg",key:"stopword"}))?.value),this.#t=!0,!0;await Promise.allSettled([this.#e.put({store:"cfg",item:{name:"version",value:o.version}}),this.#e.clear({store:"page"}),this.#e.clear({store:"file"}),this.#e.clear({store:"index"})]);let r=await this.#n("index.json");return r===null?!1:(this.#r=new Set(r.stopword),await this.#e.put({store:"cfg",item:{name:"stopword",value:r.stopword}}),await this.#e.add({store:"page",item:r.page.map((t,s)=>({id:s,url:t.u,title:t.t||t.u,description:t.d||"",date:t.p||"",words:t.w||0}))}),await this.#e.add({store:"file",item:r.file.map(t=>({id:t,loaded:!1}))}),this.#t=!0,!0)}async find(e,r=6){if(!this.#t)throw new Error("StaticSearch failed to initialize");e=e.trim(),this.#s("find",{search:e});let t=[],s=[...this.wordList(e)],l=s.length;if(!l)return this.#s("result",{search:e,result:t}),t;await Promise.allSettled(s.map(n=>this.loadIndex(n)));let a={};(await Promise.allSettled(r>1?s.map(n=>this.#e.getAll({store:"index",lowerBound:n,count:r}).then(i=>i.filter(c=>c.word.startsWith(n))).then(i=>i.map(c=>(c.key=n,c)))):s.map(n=>this.#e.get({store:"index",key:n}).then(i=>i?(i.key=n,[i]):[])))).forEach(n=>{n.value?.forEach(i=>{if(i.page)for(let c in i.page)a[c]=a[c]||{rel:0,wc:new Set},a[c].rel+=i.page[c],a[c].wc.add(i.key)})});let u=[];for(let n in a)u.push({id:n,relevancy:a[n].rel,found:a[n].wc.size/l});return(await Promise.allSettled(u.map(n=>this.#e.get({store:"page",key:parseFloat(n.id)})))).forEach((n,i)=>{n.value&&(t[i]=n.value,t[i].relevancy=u[i].relevancy,t[i].found=u[i].found)}),t.sort((n,i)=>i.relevancy-n.relevancy||i.found-n.found||new Date(i.date||"0")-new Date(n.date||"0")),this.#s("result",{search:e,result:t}),t}wordList(e){return h(e,w,o.wordCrop,this.#r)}async loadIndex(e){if(!this.#t)throw new Error("StaticSearch failed to initialize");if(e=e.toLowerCase().replace(/[^a-z]/g,"").slice(0,2),!e)return;let r=e.slice(0,e.length-1)+String.fromCharCode(e.charCodeAt(e.length-1)+1),t=(await this.#e.getAll({store:"file",lowerBound:e,upperBound:r})).filter(a=>!a.loaded);if(!t.length)return;let s=[],l=[];(await Promise.allSettled(t.map(a=>this.#n(`data/${a.id}.json`)))).map((a,u)=>{a.value&&(s.push({id:t[u].id,loaded:!0}),l.push(...Object.getOwnPropertyNames(a.value).map(n=>({word:n,page:a.value[n]}))))}),await Promise.allSettled([this.#e.put({store:"file",item:s}),this.#e.put({store:"index",item:l})])}#s(e="unknown",r={}){document.dispatchEvent(new CustomEvent("staticsearch:"+e,{detail:r}))}async#n(e){let r=o.path+e;try{let t=new AbortController,s=setTimeout(()=>t.abort(),this.fetchTimeout),l=await fetch(r,{signal:t.signal});if(clearTimeout(s),!l.ok)throw new Error(`Fetch failed ${r}: ${l.status}`);return await l.json()}catch{return null}}},g=new d;await g.init();export{g as staticsearch}; |
+53
-47
@@ -11,33 +11,36 @@ #!/usr/bin/env node | ||
| // option configuration | ||
| /* eslint-disable no-multi-spaces */ | ||
| config = [ | ||
| { env: null, cli: 'env', clis: 'e', prop: null, type: 'file', default: null, help: 'load defaults from an .env file' }, | ||
| { env: 'BUILD_DIR', cli: 'builddir', clis: 'b', prop: 'buildDir', type: 'path', default: './build/', help: 'directory containing static files' }, | ||
| { env: 'SEARCH_DIR', cli: 'searchdir', clis: 's', prop: 'searchDir', type: 'path', default: './build/search/', help: 'index file directory' }, | ||
| { env: 'SITE_DOMAIN', cli: 'domain', clis: 'd', prop: 'siteDomain', type: 'domain', default: 'http://localhost', help: 'site domain if links use full URL' }, | ||
| { env: 'BUILD_ROOT', cli: 'root', clis: 'r', prop: 'buildRoot', type: 'path', default: '/', help: 'site root path' }, | ||
| { env: 'SITE_INDEXFILE', cli: 'indexfile', clis: 'i', prop: 'siteIndexFile', type: 'name', default: 'index.html', help: 'default index file' }, | ||
| { env: 'SITE_PARSEROBOTSFILE', cli: 'ignorerobotfile', clis: 'f', prop: 'siteParseRobotsFile', type: 'true|false', default: true, help: 'parse robot.txt Disallows' }, | ||
| { env: 'SITE_PARSEROBOTSMETA', cli: 'ignorerobotmeta', clis: 'm', prop: 'siteParseRobotsMeta', type: 'true|false', default: true, help: 'parse robot meta noindex' }, | ||
| { env: 'PAGE_DOMSELECTORS', cli: 'dom', clis: 'D', prop: 'pageDOMSelectors', type: 'str', default: '', help: 'CSS selector: nodes to include' }, | ||
| { env: 'PAGE_DOMEXCLUDE', cli: 'domx', clis: 'X', prop: 'pageDOMExclude', type: 'str', default: '', help: 'CSS selector: nodes to exclude' }, | ||
| { env: 'LANGUAGE', cli: 'language', clis: 'l', prop: 'language', type: 'str', default: 'en', help: 'language' }, | ||
| { env: 'WORDCROP', cli: 'wordcrop', clis: 'c', prop: 'wordCrop', type: 'num', default: 7, help: 'crop word letters' }, | ||
| { env: 'STOPWORDS', cli: 'stopwords', clis: 'S', prop: 'stopWords', type: 'str', default: null, help: 'comma-separated list of stop words' }, | ||
| { env: 'WEIGHT_LINK', cli: 'weightlink', clis: '', prop: 'wordWeight.link', type: 'num', default: 5, help: 'word weight for inbound links' }, | ||
| { env: 'WEIGHT_TITLE', cli: 'weighttitle', clis: '', prop: 'wordWeight.title', type: 'num', default: 10, help: 'word weight for main title' }, | ||
| { env: 'WEIGHT_DESCRIPTION', cli: 'weightdesc', clis: '', prop: 'wordWeight.description', type: 'num', default: 8, help: 'word weight for description' }, | ||
| { env: 'WEIGHT_H2', cli: 'weighth2', clis: '', prop: 'wordWeight.h2', type: 'num', default: 6, help: 'word weight for H2 headings' }, | ||
| { env: 'WEIGHT_H3', cli: 'weighth3', clis: '', prop: 'wordWeight.h3', type: 'num', default: 5, help: 'word weight for H3 headings' }, | ||
| { env: 'WEIGHT_H4', cli: 'weighth4', clis: '', prop: 'wordWeight.h4', type: 'num', default: 4, help: 'word weight for H4 headings' }, | ||
| { env: 'WEIGHT_H5', cli: 'weighth5', clis: '', prop: 'wordWeight.h5', type: 'num', default: 3, help: 'word weight for H5 headings' }, | ||
| { env: 'WEIGHT_H6', cli: 'weighth6', clis: '', prop: 'wordWeight.h6', type: 'num', default: 2, help: 'word weight for H6 headings' }, | ||
| { env: 'WEIGHT_EMPHASIS', cli: 'weightemphasis', clis: '', prop: 'wordWeight.emphasis', type: 'num', default: 2, help: 'word weight for bold and italic' }, | ||
| { env: 'WEIGHT_ALT', cli: 'weightalt', clis: '', prop: 'wordWeight.alt', type: 'num', default: 1, help: 'word weight for alt tags' }, | ||
| { env: 'WEIGHT_CONTENT', cli: 'weightcontent', clis: '', prop: 'wordWeight.content', type: 'num', default: 1, help: 'word weight for content' }, | ||
| { env: 'LOGLEVEL', cli: 'loglevel', clis: 'L', prop: 'logLevel', type: 'num', default: 2, help: 'logging verbosity' }, | ||
| { env: null, cli: 'version', clis: 'v', prop: null, type: null, default: null, help: 'show application version' }, | ||
| { env: null, cli: 'help', clis: '?', prop: null, type: null, default: null, help: 'show help' }, | ||
| { env: null, cli: 'helpenv', clis: 'E', prop: null, type: null, default: null, help: 'show .env/environment variable help' }, | ||
| { env: null, cli: 'helpapi', clis: 'A', prop: null, type: null, default: null, help: 'show Node.js API help' }, | ||
| { env: null, cli: 'env', clis: 'e', prop: null, type: 'file', default: null, help: 'load defaults from an .env file' }, | ||
| { env: 'BUILD_DIR', cli: 'builddir', clis: 'b', prop: 'buildDir', type: 'path', default: './build/', help: 'directory containing static files' }, | ||
| { env: 'SEARCH_DIR', cli: 'searchdir', clis: 's', prop: 'searchDir', type: 'path', default: './build/search/', help: 'index file directory' }, | ||
| { env: 'SITE_DOMAIN', cli: 'domain', clis: 'd', prop: 'siteDomain', type: 'domain', default: 'http://localhost', help: 'site domain if links use full URL' }, | ||
| { env: 'BUILD_ROOT', cli: 'root', clis: 'r', prop: 'buildRoot', type: 'path', default: '/', help: 'site root path' }, | ||
| { env: 'SITE_INDEXFILE', cli: 'indexfile', clis: 'i', prop: 'siteIndexFile', type: 'name', default: 'index.html', help: 'default index file' }, | ||
| { env: 'SITE_PARSEROBOTSFILE', cli: 'ignorerobotfile', clis: 'f', prop: 'siteParseRobotsFile', type: 'true|false', default: true, help: 'parse robot.txt Disallows' }, | ||
| { env: 'SITE_PARSEROBOTSMETA', cli: 'ignorerobotmeta', clis: 'm', prop: 'siteParseRobotsMeta', type: 'true|false', default: true, help: 'parse robot meta noindex' }, | ||
| { env: 'PAGE_DOMSELECTORS', cli: 'dom', clis: 'D', prop: 'pageDOMSelectors', type: 'str', default: '', help: 'CSS selector: nodes to include' }, | ||
| { env: 'PAGE_DOMEXCLUDE', cli: 'domx', clis: 'X', prop: 'pageDOMExclude', type: 'str', default: '', help: 'CSS selector: nodes to exclude' }, | ||
| { env: 'LANGUAGE', cli: 'language', clis: 'l', prop: 'language', type: 'str', default: 'en', help: 'language' }, | ||
| { env: 'WORDCROP', cli: 'wordcrop', clis: 'c', prop: 'wordCrop', type: 'num', default: 7, help: 'crop word letters' }, | ||
| { env: 'STOPWORDS', cli: 'stopwords', clis: 'S', prop: 'stopWords', type: 'str', default: null, help: 'comma-separated list of stop words' }, | ||
| { env: 'STOPWORDS_DEFAULT', cli: 'ignorestopdefault', clis: 'W', prop: 'stopWordsDefault', type: 'true|false', default: true, help: 'use language default stop words' }, | ||
| { env: 'WEIGHT_LINK', cli: 'weightlink', clis: '', prop: 'wordWeight.link', type: 'num', default: 5, help: 'word weight for inbound links' }, | ||
| { env: 'WEIGHT_TITLE', cli: 'weighttitle', clis: '', prop: 'wordWeight.title', type: 'num', default: 10, help: 'word weight for main title' }, | ||
| { env: 'WEIGHT_DESCRIPTION', cli: 'weightdesc', clis: '', prop: 'wordWeight.description', type: 'num', default: 8, help: 'word weight for description' }, | ||
| { env: 'WEIGHT_H2', cli: 'weighth2', clis: '', prop: 'wordWeight.h2', type: 'num', default: 6, help: 'word weight for H2 headings' }, | ||
| { env: 'WEIGHT_H3', cli: 'weighth3', clis: '', prop: 'wordWeight.h3', type: 'num', default: 5, help: 'word weight for H3 headings' }, | ||
| { env: 'WEIGHT_H4', cli: 'weighth4', clis: '', prop: 'wordWeight.h4', type: 'num', default: 4, help: 'word weight for H4 headings' }, | ||
| { env: 'WEIGHT_H5', cli: 'weighth5', clis: '', prop: 'wordWeight.h5', type: 'num', default: 3, help: 'word weight for H5 headings' }, | ||
| { env: 'WEIGHT_H6', cli: 'weighth6', clis: '', prop: 'wordWeight.h6', type: 'num', default: 2, help: 'word weight for H6 headings' }, | ||
| { env: 'WEIGHT_EMPHASIS', cli: 'weightemphasis', clis: '', prop: 'wordWeight.emphasis', type: 'num', default: 2, help: 'word weight for bold and italic' }, | ||
| { env: 'WEIGHT_ALT', cli: 'weightalt', clis: '', prop: 'wordWeight.alt', type: 'num', default: 1, help: 'word weight for alt tags' }, | ||
| { env: 'WEIGHT_CONTENT', cli: 'weightcontent', clis: '', prop: 'wordWeight.content', type: 'num', default: 1, help: 'word weight for content' }, | ||
| { env: 'LOGLEVEL', cli: 'loglevel', clis: 'L', prop: 'logLevel', type: 'num', default: 2, help: 'logging verbosity' }, | ||
| { env: null, cli: 'version', clis: 'v', prop: null, type: null, default: null, help: 'show application version' }, | ||
| { env: null, cli: 'help', clis: '?', prop: null, type: null, default: null, help: 'show help' }, | ||
| { env: null, cli: 'helpenv', clis: 'E', prop: null, type: null, default: null, help: 'show .env/environment variable help' }, | ||
| { env: null, cli: 'helpapi', clis: 'A', prop: null, type: null, default: null, help: 'show Node.js API help' }, | ||
| ], | ||
| /* eslint-enable no-multi-spaces */ | ||
| helpLink = styleText(['cyanBright'], 'For full help, refer to https://staticsearch.com/'); | ||
@@ -105,3 +108,3 @@ | ||
| .filter(c => c.cli) | ||
| .map(c => ` ${ (c.clis ? `-${ c.clis }, ` : ' ') }--${ c.cli.padEnd(15) }${ styleText(['dim'], (c.type && c.type !== 'true|false' ? ' <' + c.type + '>' : '').padEnd(13)) } ${ c.help } ${ c.default ? styleText(['dim'], `(${ c.default })`) : '' }`) | ||
| .map(c => ` ${ (c.clis ? `-${ c.clis }, ` : ' ') }--${ c.cli.padEnd(18) }${ styleText(['dim'], (c.type && c.type !== 'true|false' ? ' <' + c.type + '>' : '').padEnd(10)) } ${ c.help } ${ c.default !== null && c.default !== '' ? styleText(['dim'], `(${ String( c.default ) })`) : '' }`) | ||
| .join('\n') | ||
@@ -119,2 +122,5 @@ } | ||
| When --builddir is not set, the indexer attempts to find static files in: | ||
| ./build/, ./dist/, ./dest/, ./out/, ./target/, or the current directory. | ||
| `); | ||
@@ -137,3 +143,3 @@ } | ||
| .filter(c => c.env) | ||
| .map(c => ` ${ c.env }${ styleText(['dim'], (c.type ? '=<' + c.type + '>' : '').padEnd(33 - c.env.length)) } ${ c.help } ${ c.default ? styleText(['dim'], `(${ c.default })`) : '' }`) | ||
| .map(c => ` ${ c.env }${ styleText(['dim'], (c.type ? '=<' + c.type + '>' : '').padEnd(34 - c.env.length)) } ${ c.help } ${ c.default ? styleText(['dim'], `(${ c.default })`) : '' }`) | ||
| .join('\n') | ||
@@ -144,3 +150,3 @@ } | ||
| BUILD_DIR=./dest/ | ||
| SEARCH_DIR=./dest/index/ | ||
| SEARCH_DIR=./dest/searchindex/ | ||
| BUILD_ROOT=/blog/ | ||
@@ -165,3 +171,3 @@ `) } | ||
| You can use the Node.js API to programmatically index a static site. | ||
| Use the Node.js API to programmatically index a static site. | ||
@@ -179,3 +185,3 @@ Install the module into a Node.js project: | ||
| staticsearch.buildDir = './dest/'; | ||
| staticsearch.searchDir = './dest/index/'; | ||
| staticsearch.searchDir = './dest/searchindex/'; | ||
| staticsearch.buildRoot = './blog/'; | ||
@@ -195,3 +201,3 @@ staticsearch.wordWeight.title = 20; | ||
| .filter(c => c.prop) | ||
| .map(c => ` ${ styleText(['green'], '.' + c.prop) }${ styleText(['dim'], (c.type ? ' = <' + c.type + '>;' : '').padEnd(35 - c.prop.length)) } ${ c.help } ${ c.default ? styleText(['dim'], `(${ c.type != 'num' && c.type != 'true|false' ? '\'' : ''}${ c.default }${ c.type != 'num' && c.type != 'true|false' ? '\'' : ''})`) : '' }`) | ||
| .map(c => ` ${ styleText(['green'], '.' + c.prop) }${ styleText(['dim'], (c.type ? ' = <' + c.type + '>;' : '').padEnd(36 - c.prop.length)) } ${ c.help } ${ c.default ? styleText(['dim'], `(${ c.type != 'num' && c.type != 'true|false' ? '\'' : ''}${ c.default }${ c.type != 'num' && c.type != 'true|false' ? '\'' : ''})`) : '' }`) | ||
| .join('\n') | ||
@@ -219,3 +225,2 @@ } | ||
| // set defaults | ||
@@ -232,14 +237,15 @@ config.forEach(c => { | ||
| case 'int': | ||
| case 'num': | ||
| value = parseFloat(value); | ||
| break; | ||
| case 'int': | ||
| case 'num': | ||
| value = parseFloat(value); | ||
| break; | ||
| case 'str': | ||
| value = value.trim(); | ||
| break; | ||
| case 'str': | ||
| value = value.trim(); | ||
| break; | ||
| case 'true|false': | ||
| if (c.cli.startsWith('ignore')) value = !value; | ||
| break; | ||
| case 'true|false': | ||
| value = String(value).trim().toLowerCase() !== 'false'; | ||
| if (opt[c.cli] && c.cli.startsWith('ignore')) value = !value; | ||
| break; | ||
@@ -249,3 +255,3 @@ } | ||
| // set property | ||
| const [p1,p2] = c.prop.split('.'); | ||
| const [p1, p2] = c.prop.split('.'); | ||
@@ -252,0 +258,0 @@ if (p2) { |
+43
-31
@@ -9,4 +9,5 @@ import process from 'node:process'; | ||
| import { env } from './lib/util.js'; | ||
| import { parseRobotsTxt, parseHTML } from './lib/parser.js'; | ||
| import { writePath, deletePath } from './lib/file.js'; | ||
| import { findReadableDir, writePath, deletePath } from './lib/file.js'; | ||
| import { stemFilename, stemFunction, stopWords } from './lib/lang.js'; | ||
@@ -30,32 +31,34 @@ | ||
| language = process.env.LOCALE; | ||
| wordCrop = process.env.WORDCROP; | ||
| stopWords = process.env.STOPWORDS; | ||
| buildDir = process.env.BUILD_DIR || './build/'; | ||
| searchDir = process.env.SEARCH_DIR || join(this.buildDir, 'search/'); | ||
| buildRoot = process.env.BUILD_ROOT || '/'; | ||
| siteDomain = process.env.SITE_DOMAIN || 'http://localhost'; | ||
| siteIndexFile = process.env.SITE_INDEXFILE || 'index.html'; | ||
| buildDir = env('BUILD_DIR'); | ||
| searchDir = env('SEARCH_DIR'); | ||
| buildRoot = env('BUILD_ROOT', '/'); | ||
| siteDomain = env('SITE_DOMAIN', 'http://localhost'); | ||
| siteIndexFile = env('SITE_INDEXFILE', 'index.html'); | ||
| siteParseRobotsFile = (process.env.SITE_PARSEROBOTSFILE?.toLowerCase() !== 'false'); | ||
| siteParseRobotsMeta = (process.env.SITE_PARSEROBOTSMETA?.toLowerCase() !== 'false'); | ||
| siteParseRobotsFile = (env('SITE_PARSEROBOTSFILE', 'true').toLowerCase() !== 'false'); | ||
| siteParseRobotsMeta = (env('SITE_PARSEROBOTSMETA', 'true').toLowerCase() !== 'false'); | ||
| pageDOMSelectors = (process.env.PAGE_DOMSELECTORS || ''); | ||
| pageDOMExclude = (process.env.PAGE_DOMEXCLUDE || ''); | ||
| pageDOMSelectors = env('PAGE_DOMSELECTORS', ''); | ||
| pageDOMExclude = env('PAGE_DOMEXCLUDE', ''); | ||
| language = env('LOCALE'); | ||
| wordCrop = env('WORDCROP'); | ||
| stopWords = env('STOPWORDS'); | ||
| stopWordsDefault = (env('STOPWORDS_DEFAULT', 'true').toLowerCase() !== 'false'); | ||
| wordWeight = { | ||
| title: parseFloat(process.env.WEIGHT_TITLE || 10), | ||
| description: parseFloat(process.env.WEIGHT_DESCRIPTION || 8), | ||
| h2: parseFloat(process.env.WEIGHT_H2 || 6), | ||
| h3: parseFloat(process.env.WEIGHT_H3 || 5), | ||
| h4: parseFloat(process.env.WEIGHT_H4 || 4), | ||
| h5: parseFloat(process.env.WEIGHT_H5 || 3), | ||
| h6: parseFloat(process.env.WEIGHT_H6 || 2), | ||
| content: parseFloat(process.env.WEIGHT_CONTENT || 1), | ||
| emphasis: parseFloat(process.env.WEIGHT_EMPHASIS || 2), | ||
| alt: parseFloat(process.env.WEIGHT_ALT || 1), | ||
| link: parseFloat(process.env.WEIGHT_LINK || 5) | ||
| title: env('WEIGHT_TITLE', 10), | ||
| description: env('WEIGHT_DESCRIPTION', 8), | ||
| h2: env('WEIGHT_H2', 6), | ||
| h3: env('WEIGHT_H3', 5), | ||
| h4: env('WEIGHT_H4', 4), | ||
| h5: env('WEIGHT_H5', 3), | ||
| h6: env('WEIGHT_H6', 2), | ||
| content: env('WEIGHT_CONTENT', 1), | ||
| emphasis: env('WEIGHT_EMPHASIS', 2), | ||
| alt: env('WEIGHT_ALT', 1), | ||
| link: env('WEIGHT_LINK', 5) | ||
| }; | ||
| logLevel = process.env.LOGLEVEL || 2; | ||
| logLevel = env('LOGLEVEL', 2); | ||
@@ -65,9 +68,18 @@ // start indexing | ||
| // resolved working directories | ||
| // concol defaults | ||
| concol = new ConCol('StaticSearch', 'magentaBright', parseFloat(this.logLevel) || 0); | ||
| // find readable build directory | ||
| const workingBuildDir = await findReadableDir(process.cwd(), this.buildDir || './build,./dist,./dest,./out,./target,./'); | ||
| if (!workingBuildDir) { | ||
| concol.error('no static site build directory found'); | ||
| return; | ||
| } | ||
| // set working directories | ||
| const | ||
| workingBuildDir = resolve(process.cwd(), this.buildDir), | ||
| workingSearchDir = resolve(process.cwd(), this.searchDir), | ||
| workingSearchDir = this.searchDir ? resolve(process.cwd(), this.searchDir) : resolve(workingBuildDir, './search'), | ||
| workingStaticSite = resolve( '/', dirname( import.meta.url.replace(/^[^/]*\/+/, '') ) ); | ||
| concol = new ConCol('StaticSearch', 'magentaBright', parseFloat(this.logLevel) || 0); | ||
| concol.log(['StaticSearch indexing started', '', ['processing HTML files in', workingBuildDir], ['writing index data to', workingSearchDir], '' ], 1); | ||
@@ -81,3 +93,3 @@ | ||
| stem = await stemFunction(this.language), | ||
| stopword = await stopWords(this.language, this.wordCrop, this.stopWords); | ||
| stopword = await stopWords(this.language, this.stopWordsDefault, this.stopWords, this.wordCrop); | ||
@@ -117,3 +129,3 @@ // parse robots.txt | ||
| } | ||
| slug = slug.replace(sep, '/'); | ||
| slug = slug.replaceAll(sep, '/'); | ||
@@ -120,0 +132,0 @@ return { file, slug }; |
+27
-3
| // file utilities | ||
| import { access, stat, writeFile, mkdir, rm } from 'node:fs/promises'; | ||
| import { constants as fsConstants } from 'node:fs'; | ||
| import { dirname } from 'node:path'; | ||
| import { dirname, resolve } from 'node:path'; | ||
@@ -33,3 +33,5 @@ import { concol } from '../indexer.js'; | ||
| } | ||
| catch (e) {} | ||
| catch (e) { | ||
| // no read access | ||
| } | ||
@@ -40,3 +42,5 @@ try { | ||
| } | ||
| catch (e) {} | ||
| catch (e) { | ||
| // no write access | ||
| } | ||
@@ -48,2 +52,22 @@ return info; | ||
| // find readable directory | ||
| export async function findReadableDir(rootDir, subDir) { | ||
| subDir = subDir.split(',').map(s => s.trim()).filter(s => s); | ||
| let fd; | ||
| for (let d = 0; d < subDir.length; d++) { | ||
| fd = resolve(rootDir, subDir[d]); | ||
| const fInfo = await fileInfo(fd); | ||
| if (fInfo.isDir && fInfo.canRead) break; | ||
| else fd = null; | ||
| } | ||
| return fd; | ||
| } | ||
| // write file content | ||
@@ -50,0 +74,0 @@ export async function writePath(filename, content) { |
+27
-17
| // returns stopwords and stem functions | ||
| import { readFile } from 'node:fs/promises'; | ||
| import { resolve, dirname } from 'node:path'; | ||
| import crypto from 'node:crypto'; | ||
@@ -9,6 +10,8 @@ import { fileInfo } from './file.js'; | ||
| const | ||
| dirStem = resolve( '/', dirname( import.meta.url.replace(/^[^/]*\/+/, '') ), '../stem/' ), | ||
| dirLoc = dirname( import.meta.url.replace(/^[^/]*\/+/, '') ), | ||
| dirStem = resolve( '/', dirLoc, '../stem/' ), | ||
| mapStem = new Map(), | ||
| dirWord = resolve( '/', dirname( import.meta.url.replace(/^[^/]*\/+/, '') ), '../stopwords/' ), | ||
| dirWord = resolve( '/', dirLoc, '../stopwords/' ), | ||
| mapWord = new Map(); | ||
@@ -56,6 +59,6 @@ | ||
| // get stopword list | ||
| export async function stopWords(language = 'en', maxLength = 7, extrawords = '') { | ||
| export async function stopWords(language = 'en', setDefaults, extrawords, maxLength = 7) { | ||
| // stopwords already loaded | ||
| const mapId = language + maxLength; | ||
| // stopwords already loaded? | ||
| const mapId = crypto.createHash('md5').update(language + (setDefaults ? 'T' : 'F') + maxLength + extrawords).digest('base64'); | ||
@@ -66,17 +69,25 @@ if (mapWord.has(mapId)) { | ||
| const | ||
| stem = await stemFunction(language), | ||
| swFile = resolve(dirWord, `stopwords_${language}.txt`), | ||
| swFileInfo = await fileInfo(swFile); | ||
| const stem = await stemFunction(language); | ||
| let words = extrawords || ''; | ||
| // read stopword file | ||
| if (swFileInfo.exists && swFileInfo.canRead) { | ||
| // read default stopword file | ||
| if (setDefaults) { | ||
| const | ||
| swFile = resolve(dirWord, `stopwords_${language}.txt`), | ||
| swFileInfo = await fileInfo(swFile); | ||
| if (swFileInfo.exists && swFileInfo.canRead) { | ||
| words += '\n' + await readFile(swFile, { encoding: 'utf8' }); | ||
| } | ||
| } | ||
| // generate stopword set | ||
| words = words.trim(); | ||
| if (words) { | ||
| mapWord.set( | ||
| mapId, | ||
| wordSet( | ||
| (await readFile(swFile, { encoding: 'utf8' })) + '\n' + extrawords, | ||
| stem, | ||
| maxLength | ||
| ) | ||
| wordSet(words, stem, maxLength) | ||
| ); | ||
@@ -87,5 +98,4 @@ | ||
| } | ||
| else return new Set(); | ||
| } |
+3
-1
@@ -267,3 +267,5 @@ // data parsing functions | ||
| } | ||
| catch (e) {} | ||
| catch (e) { | ||
| return; | ||
| } | ||
@@ -270,0 +272,0 @@ } |
+3
-3
| { | ||
| "name": "staticsearch", | ||
| "version": "0.6.0", | ||
| "version": "0.7.0", | ||
| "description": "Simple static site search system.", | ||
@@ -51,9 +51,9 @@ "type": "module", | ||
| "concol": "2.1.1", | ||
| "node-html-parser": "7.0.1", | ||
| "node-html-parser": "7.1.0", | ||
| "perfpro": "0.2.0" | ||
| }, | ||
| "devDependencies": { | ||
| "esbuild": "0.27.2", | ||
| "esbuild": "0.28.0", | ||
| "pixdb": "1.0.2" | ||
| } | ||
| } |
@@ -6,3 +6,2 @@ a | ||
| about | ||
| above | ||
| abroad | ||
@@ -18,5 +17,2 @@ accordance | ||
| adopted | ||
| affected | ||
| affecting | ||
| affects | ||
| afore | ||
@@ -101,3 +97,2 @@ after | ||
| aslant | ||
| associated | ||
| astraddle | ||
@@ -112,3 +107,2 @@ astride | ||
| august | ||
| available | ||
| aw | ||
@@ -145,6 +139,2 @@ away | ||
| believe | ||
| below | ||
| beneath | ||
| beside | ||
| besides | ||
| best | ||
@@ -155,3 +145,2 @@ better | ||
| betters | ||
| between | ||
| betwixt | ||
@@ -169,5 +158,3 @@ beyond | ||
| bo | ||
| boo | ||
| both | ||
| bottom | ||
| bravo | ||
@@ -213,3 +200,2 @@ brief | ||
| comes | ||
| computer | ||
| con | ||
@@ -220,5 +206,2 @@ concerning | ||
| considering | ||
| contain | ||
| containing | ||
| contains | ||
| conversely | ||
@@ -290,3 +273,2 @@ copy | ||
| eek | ||
| effect | ||
| eg | ||
@@ -334,3 +316,2 @@ ie | ||
| exactly | ||
| example | ||
| except | ||
@@ -340,3 +321,2 @@ excepted | ||
| excepts | ||
| excluding | ||
| exes | ||
@@ -434,6 +414,2 @@ f | ||
| greetings | ||
| group | ||
| grouped | ||
| grouping | ||
| groups | ||
| h | ||
@@ -490,4 +466,2 @@ had | ||
| homepage | ||
| hoo | ||
| hoos | ||
| hopefully | ||
@@ -561,4 +535,2 @@ how | ||
| kept | ||
| key | ||
| keys | ||
| kg | ||
@@ -623,8 +595,4 @@ kind | ||
| me | ||
| mean | ||
| means | ||
| meantime | ||
| meanwhile | ||
| member | ||
| members | ||
| men | ||
@@ -750,6 +718,2 @@ merely | ||
| or | ||
| order | ||
| ordered | ||
| ordering | ||
| orders | ||
| other | ||
@@ -775,11 +739,4 @@ others | ||
| owing | ||
| own | ||
| owned | ||
| owning | ||
| owns | ||
| owt | ||
| p | ||
| page | ||
| pages | ||
| part | ||
| parted | ||
@@ -800,3 +757,2 @@ particular | ||
| places | ||
| plaintiff | ||
| please | ||
@@ -860,3 +816,2 @@ pleased | ||
| relatively | ||
| res | ||
| research | ||
@@ -866,6 +821,2 @@ reserved | ||
| respectively | ||
| resulted | ||
| resulting | ||
| results | ||
| right | ||
| ring | ||
@@ -879,5 +830,2 @@ room | ||
| sames | ||
| sans | ||
| sanserif | ||
| sanserifs | ||
| sat | ||
@@ -1004,4 +952,2 @@ saturday | ||
| tenth | ||
| test | ||
| text | ||
| than | ||
@@ -1083,3 +1029,2 @@ thank | ||
| took | ||
| top | ||
| toward | ||
@@ -1165,5 +1110,2 @@ towards | ||
| we | ||
| web | ||
| webpage | ||
| website | ||
| wed | ||
@@ -1263,3 +1205,2 @@ wednesday | ||
| wow | ||
| write | ||
| x | ||
@@ -1266,0 +1207,0 @@ xi |
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 24 instances
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
135585
1.48%45
2.27%1102
4.75%8
-74.19%+ Added
- Removed
Updated