Rewrite search suggestions module

This commit is contained in:
Cadence Ember 2022-10-24 18:13:54 +13:00
parent d8d4e4375e
commit 3c7a2f8453
Signed by: cadence
GPG Key ID: BC1C2C61CF521B17
4 changed files with 71 additions and 57 deletions

View File

@ -112,10 +112,11 @@
(nav (@ (class "sitesearch"))
(form (@ (action ,(format "/~a/search" wikiname))
(class "bw-search-form")
(id "bw-pr-search"))
(id "bw-pr-search-form"))
(label (@ (for "bw-search-input")) "Search ")
(input (@ (type "text") (name "q") (id "bw-search-input") (autocomplete "off")))
(div (@ (class "bw-ss__container"))))))
(div (@ (id "bw-pr-search-input"))
(input (@ (type "text") (name "q") (id "bw-search-input") (autocomplete "off"))))
(div (@ (class "bw-ss__container") (id "bw-pr-search-suggestions"))))))
(div (@ (id "content") #;(class "page-content"))
(div (@ (id "mw-content-text"))
,content))

View File

@ -269,7 +269,7 @@ figcaption, .lightbox-caption, .thumbcaption {
.bw-ss__item {
display: grid; /* make buttons take the full size */
}
.bw-ss__item:hover {
.bw-ss__button:hover, .bw-ss__button:focus {
background-color: #ddd;
}
.bw-ss__button {

View File

@ -13,9 +13,24 @@ const htm = (function() {
export {htm};
// hooks
const { useCallback, useContext, useDebugValue, useEffect, useErrorBoundary, useId, useImperativeHandle, useLayoutEffect, useMemo, useReducer, useState } = (function() {
const { useCallback, useContext, useDebugValue, useEffect, useErrorBoundary, useId, useImperativeHandle, useLayoutEffect, useMemo, useReducer, useRef, useState } = (function() {
var n = options;
var t,r,u,i,o = 0,f = [],c = [],e = n.__b,a = n.__r,v = n.diffed,l = n.__c,m = n.unmount;function d(t, u) {n.__h && n.__h(r, t, o || u), o = 0;var i = r.__H || (r.__H = { __: [], __h: [] });return t >= i.__.length && i.__.push({ __V: c }), i.__[t];}function p(n) {return o = 1, y(B, n);}function y(n, u, i) {var o = d(t++, 2);if (o.t = n, !o.__c && (o.__ = [i ? i(u) : B(void 0, u), function (n) {var t = o.__N ? o.__N[0] : o.__[0],r = o.t(t, n);t !== r && (o.__N = [r, o.__[1]], o.__c.setState({}));}], o.__c = r, !r.u)) {r.u = !0;var f = r.shouldComponentUpdate;r.shouldComponentUpdate = function (n, t, r) {if (!o.__c.__H) return !0;var u = o.__c.__H.__.filter(function (n) {return n.__c;});if (u.every(function (n) {return !n.__N;})) return !f || f.call(this, n, t, r);var i = !1;return u.forEach(function (n) {if (n.__N) {var t = n.__[0];n.__ = n.__N, n.__N = void 0, t !== n.__[0] && (i = !0);}}), !(!i && o.__c.props === n) && (!f || f.call(this, n, t, r));};}return o.__N || o.__;}function h(u, i) {var o = d(t++, 3);!n.__s && z(o.__H, i) && (o.__ = u, o.i = i, r.__H.__h.push(o));}function s(u, i) {var o = d(t++, 4);!n.__s && z(o.__H, i) && (o.__ = u, o.i = i, r.__h.push(o));}function _(n) {return o = 5, F(function () {return { current: n };}, []);}function A(n, t, r) {o = 6, s(function () {return "function" == typeof n ? (n(t()), function () {return n(null);}) : n ? (n.current = t(), function () {return n.current = null;}) : void 0;}, null == r ? r : r.concat(n));}function F(n, r) {var u = d(t++, 7);return z(u.__H, r) ? (u.__V = n(), u.i = r, u.__h = n, u.__V) : u.__;}function T(n, t) {return o = 8, F(function () {return n;}, t);}function q(n) {var u = r.context[n.__c],i = d(t++, 9);return i.c = n, u ? (null == i.__ && (i.__ = !0, u.sub(r)), u.props.value) : n.__;}function x(t, r) {n.useDebugValue && n.useDebugValue(r ? r(t) : t);}function P(n) {var u = d(t++, 10),i = p();return u.__ = n, r.componentDidCatch || (r.componentDidCatch = function (n, t) {u.__ && u.__(n, t), i[1](n);}), [i[0], function () {i[1](void 0);}];}function V() {var n = d(t++, 11);return n.__ || (n.__ = "P" + function (n) {for (var t = 0, r = n.length; r > 0;) t = (t << 5) - t + n.charCodeAt(--r) | 0;return t;}(r.__v.__m) + t), n.__;}function b() {for (var t; t = f.shift();) if (t.__P && t.__H) try {t.__H.__h.forEach(k), t.__H.__h.forEach(w), t.__H.__h = [];} catch (r) {t.__H.__h = [], n.__e(r, t.__v);}}n.__b = function (n) {"function" != typeof n.type || n.__m || null === n.__ ? n.__m || (n.__m = n.__ && n.__.__m ? n.__.__m : "") : n.__m = (n.__ && n.__.__m ? n.__.__m : "") + (n.__ && n.__.__k ? n.__.__k.indexOf(n) : 0), r = null, e && e(n);}, n.__r = function (n) {a && a(n), t = 0;var i = (r = n.__c).__H;i && (u === r ? (i.__h = [], r.__h = [], i.__.forEach(function (n) {n.__N && (n.__ = n.__N), n.__V = c, n.__N = n.i = void 0;})) : (i.__h.forEach(k), i.__h.forEach(w), i.__h = [])), u = r;}, n.diffed = function (t) {v && v(t);var o = t.__c;o && o.__H && (o.__H.__h.length && (1 !== f.push(o) && i === n.requestAnimationFrame || ((i = n.requestAnimationFrame) || j)(b)), o.__H.__.forEach(function (n) {n.i && (n.__H = n.i), n.__V !== c && (n.__ = n.__V), n.i = void 0, n.__V = c;})), u = r = null;}, n.__c = function (t, r) {r.some(function (t) {try {t.__h.forEach(k), t.__h = t.__h.filter(function (n) {return !n.__ || w(n);});} catch (u) {r.some(function (n) {n.__h && (n.__h = []);}), r = [], n.__e(u, t.__v);}}), l && l(t, r);}, n.unmount = function (t) {m && m(t);var r,u = t.__c;u && u.__H && (u.__H.__.forEach(function (n) {try {k(n);} catch (n) {r = n;}}), u.__H = void 0, r && n.__e(r, u.__v));};var g = "function" == typeof requestAnimationFrame;function j(n) {var t,r = function () {clearTimeout(u), g && cancelAnimationFrame(t), setTimeout(n);},u = setTimeout(r, 100);g && (t = requestAnimationFrame(r));}function k(n) {var t = r,u = n.__c;"function" == typeof u && (n.__c = void 0, u()), r = t;}function w(n) {var t = r;n.__c = n.__(), r = t;}function z(n, t) {return !n || n.length !== t.length || t.some(function (t, r) {return t !== n[r];});}function B(n, t) {return "function" == typeof t ? t(n) : t;}
return { useCallback: T, useContext: q, useDebugValue: x, useEffect: h, useErrorBoundary: P, useId: V, useImperativeHandle: A, useLayoutEffect: s, useMemo: F, useReducer: y, useRef: _, useState: p };
})();
export { useCallback, useContext, useDebugValue, useEffect, useErrorBoundary, useId, useImperativeHandle, useLayoutEffect, useMemo, useReducer, useState };
export {useCallback, useContext, useDebugValue, useEffect, useErrorBoundary, useId, useImperativeHandle, useLayoutEffect, useMemo, useReducer, useRef, useState};
// signals-core
const {Signal, batch, computed, effect, signal} = (function() {
function i(){throw new Error("Cycle detected")}function t(){if(!(n>1)){var i,t=!1;while(void 0!==r){var h=r;r=void 0;s++;while(void 0!==h){var o=h.o;h.o=void 0;h.f&=-3;if(!(8&h.f)&&d(h))try{h.c()}catch(h){if(!t){i=h;t=!0}}h=o}}s=0;n--;if(t)throw i}else n--}function h(i){if(n>0)return i();n++;try{return i()}finally{t()}}var o=void 0,r=void 0,n=0,s=0,f=0;function v(i){if(void 0!==o){var t=i.n;if(void 0===t||t.t!==o){o.s=t={i:0,S:i,p:void 0,n:o.s,t:o,e:void 0,x:void 0,r:t};i.n=t;if(32&o.f)i.S(t);return t}else if(-1===t.i){t.i=0;if(void 0!==t.p){t.p.n=t.n;if(void 0!==t.n)t.n.p=t.p;t.p=void 0;t.n=o.s;o.s.p=t;o.s=t}return t}}}function e(i){this.v=i;this.i=0;this.n=void 0;this.t=void 0}e.prototype.h=function(){return!0};e.prototype.S=function(i){if(this.t!==i&&void 0===i.e){i.x=this.t;if(void 0!==this.t)this.t.e=i;this.t=i}};e.prototype.U=function(i){var t=i.e,h=i.x;if(void 0!==t){t.x=h;i.e=void 0}if(void 0!==h){h.e=t;i.x=void 0}if(i===this.t)this.t=h};e.prototype.subscribe=function(i){var t=this;return b(function(){var h=t.value,o=32&this.f;this.f&=-33;try{i(h)}finally{this.f|=o}})};e.prototype.valueOf=function(){return this.value};e.prototype.toString=function(){return this.value+""};e.prototype.peek=function(){return this.v};Object.defineProperty(e.prototype,"value",{get:function(){var i=v(this);if(void 0!==i)i.i=this.i;return this.v},set:function(h){if(h!==this.v){if(s>100)i();this.v=h;this.i++;f++;n++;try{for(var o=this.t;void 0!==o;o=o.x)o.t.N()}finally{t()}}}});function u(i){return new e(i)}function d(i){for(var t=i.s;void 0!==t;t=t.n)if(t.S.i!==t.i||!t.S.h()||t.S.i!==t.i)return!0;return!1}function c(i){for(var t=i.s;void 0!==t;t=t.n){var h=t.S.n;if(void 0!==h)t.r=h;t.S.n=t;t.i=-1}}function a(i){var t=i.s,h=void 0;while(void 0!==t){var o=t.n;if(-1===t.i){t.S.U(t);t.n=void 0}else{if(void 0!==h)h.p=t;t.p=void 0;t.n=h;h=t}t.S.n=t.r;if(void 0!==t.r)t.r=void 0;t=o}i.s=h}function l(i){e.call(this,void 0);this.x=i;this.s=void 0;this.g=f-1;this.f=4}(l.prototype=new e).h=function(){this.f&=-3;if(1&this.f)return!1;if(32==(36&this.f))return!0;this.f&=-5;if(this.g===f)return!0;this.g=f;this.f|=1;if(this.i>0&&!d(this)){this.f&=-2;return!0}var i=o;try{c(this);o=this;var t=this.x();if(16&this.f||this.v!==t||0===this.i){this.v=t;this.f&=-17;this.i++}}catch(i){this.v=i;this.f|=16;this.i++}o=i;a(this);this.f&=-2;return!0};l.prototype.S=function(i){if(void 0===this.t){this.f|=36;for(var t=this.s;void 0!==t;t=t.n)t.S.S(t)}e.prototype.S.call(this,i)};l.prototype.U=function(i){e.prototype.U.call(this,i);if(void 0===this.t){this.f&=-33;for(var t=this.s;void 0!==t;t=t.n)t.S.U(t)}};l.prototype.N=function(){if(!(2&this.f)){this.f|=6;for(var i=this.t;void 0!==i;i=i.x)i.t.N()}};l.prototype.peek=function(){if(!this.h())i();if(16&this.f)throw this.v;return this.v};Object.defineProperty(l.prototype,"value",{get:function(){if(1&this.f)i();var t=v(this);this.h();if(void 0!==t)t.i=this.i;if(16&this.f)throw this.v;return this.v}});function w(i){return new l(i)}function y(i){var h=i.u;i.u=void 0;if("function"==typeof h){n++;var r=o;o=void 0;try{h()}catch(t){i.f&=-2;i.f|=8;_(i);throw t}finally{o=r;t()}}}function _(i){for(var t=i.s;void 0!==t;t=t.n)t.S.U(t);i.x=void 0;i.s=void 0;y(i)}function g(i){if(o!==this)throw new Error("Out-of-order effect");a(this);o=i;this.f&=-2;if(8&this.f)_(this);t()}function p(i){this.x=i;this.u=void 0;this.s=void 0;this.o=void 0;this.f=32}p.prototype.c=function(){var i=this.S();try{if(!(8&this.f)&&void 0!==this.x)this.u=this.x()}finally{i()}};p.prototype.S=function(){if(1&this.f)i();this.f|=1;this.f&=-9;y(this);c(this);n++;var t=o;o=this;return g.bind(this,t)};p.prototype.N=function(){if(!(2&this.f)){this.f|=2;this.o=r;r=this}};p.prototype.d=function(){this.f|=8;if(!(1&this.f))_(this)};function b(i){var t=new p(i);t.c();return t.d.bind(t)}
return {Signal: e, batch: h, computed: w, effect: b, signal: u};
})();
export {Signal, batch, computed, effect, signal};
// signals
const {useComputed, useSignal, useSignalEffect} = (function() {
var n = Component, i = options, r = useMemo, t = useRef, f = useEffect, o = Signal, e = computed, u = signal, a = effect;
var c,v;function s(n,r){i[n]=r.bind(null,i[n]||function(){})}function l(n){if(v)v();v=n&&n.S()}function p(n){var i=this,t=n.data,f=useSignal(t);f.value=t;var o=r(function(){var n=i.__v;while(n=n.__)if(n.__c){n.__c.__$f|=4;break}i.__$u.c=function(){i.base.data=o.peek()};return e(function(){var n=f.value.value;return 0===n?0:!0===n?"":n||""})},[]);return o.value}p.displayName="_st";Object.defineProperties(o.prototype,{constructor:{configurable:!0},type:{configurable:!0,value:p},props:{configurable:!0,get:function(){return{data:this}}},__b:{configurable:!0,value:1}});s("__b",function(n,i){if("string"==typeof i.type){var r,t=i.props;for(var f in t)if("children"!==f){var e=t[f];if(e instanceof o){if(!r)i.__np=r={};r[f]=e;t[f]=e.peek()}}}n(i)});s("__r",function(n,i){l();var r,t=i.__c;if(t){t.__$f&=-2;if(void 0===(r=t.__$u))t.__$u=r=function(n){var i;a(function(){i=this});i.c=function(){t.__$f|=1;t.setState({})};return i}()}c=t;l(r);n(i)});s("__e",function(n,i,r,t){l();c=void 0;n(i,r,t)});s("diffed",function(n,i){l();c=void 0;var r;if("string"==typeof i.type&&(r=i.__e)){var t=i.__np,f=i.props;if(t){var o=r.U;if(o)for(var e in o){var u=o[e];if(void 0!==u&&!(e in t)){u.d();o[e]=void 0}}else r.U=o={};for(var a in t){var v=o[a],s=t[a];if(void 0===v){v=d(r,a,s,f);o[a]=v}else v.o(s,f)}}}n(i)});function d(n,i,r,t){var f=i in n&&void 0===n.ownerSVGElement,o=u(r);return{o:function(n,i){o.value=n;t=i},d:a(function(){var r=o.value.value;if(t[i]!==r){t[i]=r;if(f)n[i]=r;else if(r)n.setAttribute(i,r);else n.removeAttribute(i)}})}}s("unmount",function(n,i){if("string"==typeof i.type){var r=i.__e;if(r){var t=r.U;if(t){r.U=void 0;for(var f in t){var o=t[f];if(o)o.d()}}}}else{var e=i.__c;if(e){var u=e.__$u;if(u){e.__$u=void 0;u.d()}}}n(i)});s("__h",function(n,i,r,t){if(t<3)i.__$f|=2;n(i,r,t)});n.prototype.shouldComponentUpdate=function(n,i){var r=this.__$u;if(!(r&&void 0!==r.s||4&this.__$f))return!0;if(3&this.__$f)return!0;for(var t in i)return!0;for(var f in n)if("__source"!==f&&n[f]!==this.props[f])return!0;for(var o in this.props)if(!(o in n))return!0;return!1};function useSignal(n){return r(function(){return u(n)},[])}function useComputed(n){var i=t(n);i.current=n;c.__$f|=4;return r(function(){return e(function(){return i.current()})},[])}function useSignalEffect(n){var i=t(n);i.current=n;f(function(){return a(function(){i.current()})},[])}
return {useComputed, useSignal, useSignalEffect}
})();
export {useComputed, useSignal, useSignalEffect};

View File

@ -1,17 +1,20 @@
import {h, htm, render, useState, useEffect, createContext, useContext} from "./preact.js"
import {h, htm, render, useState, useEffect, createContext, useContext, signal, computed, effect} from "./preact.js"
const html = htm.bind(h)
const classNames = classArr => classArr.filter(el => el).join(" ")
const form = document.getElementById("bw-pr-search")
const eForm = document.getElementById("bw-pr-search-form")
const eInput = document.getElementById("bw-pr-search-input")
const eSuggestions = document.getElementById("bw-pr-search-suggestions")
const AcceptSuggestion = createContext(null)
const hitsPromise = new Map()
const hitsDone = new Set()
function Suggestion(props) {
const acceptSuggestion = useContext(AcceptSuggestion)
return html`<li class="bw-ss__item"><button type="button" class="bw-ss__button" onClick=${() => acceptSuggestion(props)}>${props.title}</button></li>`
}
const query = signal("")
const focus = signal(false)
const st = signal("ready")
const suggestions = signal([])
// processing functions
function fetchSuggestions(query, setSuggestions) {
if (query === "") query = "\0"
@ -36,58 +39,53 @@ function fetchSuggestions(query, setSuggestions) {
return promise
}
function SuggestionList(props) {
return html`
<div class="bw-ss__container">
<ul class=${classNames(["bw-ss__list", props.focus && "bw-ss__list--focus", `bw-ss__list--${props.st}`])}>
${props.hits.map(hit => html`<${Suggestion} ...${hit} />`)}
</ul>
</div>`
function acceptSuggestion(hit) {
st.value = "accepted"
query.value = hit.title
const dest = new URL(hit.url).pathname.match("/wiki/.*")
location = `/${BWData.wikiname}${dest}`
}
function ControlledInput() {
const [query, setQuery] = useState("")
const [focus, setFocus] = useState(false)
const [st, setSt] = useState("ready")
const [suggestions, setSuggestions] = useState([])
// suggestion list view
useEffect(() => {
if (st === "accepted") return
setSt("loading")
fetchSuggestions(query).then(s => {
setSuggestions(s)
if (hitsDone.size === hitsPromise.size) {
setSt("ready")
}
})
}, [query])
function Suggestion(hit) {
return html`<li class="bw-ss__item"><button type="button" class="bw-ss__button" onClick=${() => acceptSuggestion(hit)}>${hit.title}</button></li>`
}
function acceptSuggestion(suggestion) {
setQuery(suggestion.title)
setSt("accepted")
const dest = new URL(suggestion.url).pathname.match("/wiki/.*")
location = `/${BWData.wikiname}${dest}`
}
function SuggestionList() {
return html`
<ul class=${classNames(["bw-ss__list", focus.value && "bw-ss__list--focus", `bw-ss__list--${st.value}`])}>
${suggestions.value.map(hit => html`<${Suggestion} ...${hit} />`)}
</ul>`
}
useEffect(() => {
function listener(event) {
if (event.type === "focusin") setFocus(true)
else setFocus(false)
}
form.addEventListener("focusin", listener)
form.addEventListener("focusout", listener)
return () => {
form.removeEventListener("focusin", listener)
form.removeEventListener("focusout", listener)
render(html`<${SuggestionList} />`, eSuggestions)
// input view
effect(() => {
if (st.peek() === "accepted") return // lock results from changing during navigation
st.value = "loading"
fetchSuggestions(query.value).then(res => {
suggestions.value = res
if (hitsDone.size === hitsPromise.size) {
st.value = "ready"
}
})
})
document.addEventListener("pageshow", () => {
st.value = "ready" // unlock results from changing after returning to page
})
function SuggestionInput() {
return html`
<${AcceptSuggestion.Provider} value=${acceptSuggestion}>
<label for="bw-search-input">Search </label>
<input type="text" name="q" id="bw-search-input" autocomplete="off" onInput=${e => setQuery(e.target.value)} value=${query} class=${classNames(["bw-ss__input", `bw-ss__input--${st}`])} />
<${SuggestionList} hits=${suggestions} focus=${focus} st=${st}/>
</${AcceptSuggestion.Provider}`
<input type="text" name="q" id="bw-search-input" autocomplete="off" onInput=${e => query.value = e.target.value} value=${query.value} class=${classNames(["bw-ss__input", `bw-ss__input--${st.value}`])} />`
}
render(html`<${ControlledInput} />`, form)
render(html`<${SuggestionInput} />`, eInput)
// form focus
eForm.addEventListener("focusin", () => focus.value = true)
eForm.addEventListener("focusout", () => focus.value = false)