From 2066991942ad33845742c9aad90c1854cd125a86 Mon Sep 17 00:00:00 2001 From: Hamcha Date: Wed, 17 Jun 2020 18:11:55 +0200 Subject: [PATCH] Separate search and fix subrecipes --- lib/search.ts | 181 +++++++++++++++++++++++++++++++++++++++ lib/userscript.ts | 209 ++++------------------------------------------ style/bgus.scss | 6 +- 3 files changed, 199 insertions(+), 197 deletions(-) create mode 100644 lib/search.ts diff --git a/lib/search.ts b/lib/search.ts new file mode 100644 index 0000000..cc60828 --- /dev/null +++ b/lib/search.ts @@ -0,0 +1,181 @@ +interface SearchOption { + alignment: ScrollLogicalPosition; +} + +export function searchBox( + el: HTMLElement[], + searchCandidate, + options: SearchOption = { + alignment: "center", + } +): HTMLElement { + // Fuzzy search box + const resultList = document.createElement("ul"); + const searchBoxElem = document.createElement("div"); + let selectedResult = null; + let results = []; + const jumpTo = (id: number) => { + el[id].scrollIntoView({ + block: options.alignment, + inline: "nearest", + behavior: "auto", + }); + document + .querySelectorAll("table.wikitable .bgus_fz_selected") + .forEach((sel) => sel.classList.remove("bgus_fz_selected")); + el[id].parentElement.classList.add("bgus_fz_selected"); + }; + + const setSelectedResult = (i) => { + selectedResult = i; + resultList + .querySelectorAll(".selected") + .forEach((sel) => sel.classList.remove("selected")); + resultList.children[i].classList.add("selected"); + jumpTo(results[i].id); + }; + + const search = (str) => { + if (!str) { + return; + } + const combinations = str + .split("") + .map((c) => (c.includes(["\\", "]", "^"]) ? `\\${c}` : c)) + .join("])(.*?)(["); + const regex = new RegExp(`^(.*?)([${combinations}])(.*?)$`, "i"); + const arr = searchCandidate + .map((o) => { + o.matches = (o.str.match(regex) || []) + .slice(1) + .reduce((list, group, i, or) => { + // Initialize first placeholder (always empty) and first matching "sections" + if (i < 2) { + list.push([group]); + } + // If group is second match in a row join to previous section + else if (or[i - 1] === "") { + list[list.length - 1].push(group); + } + // If group is a match create a new section + else if (group !== "") { + list.push([group]); + } + return list; + }, []) + .map((cstr) => cstr.join("")); + return o; + }) + // Strike non-matching rows + .filter((o) => o.matches.length > 0) + .sort((oA, oB) => { + const iA = oA.id, + a = oA.matches; + const iB = oB.id, + b = oB.matches; + + // Exact match + if (a.length === 1 && b.length !== 1) return -1; + if (a.length !== 1 && b.length === 1) return 1; + + // Most complete groups (alphanumeric) + const clean = (cel) => !/[^a-zA-Z0-9]*$/.test(cel); + const cLen = a.filter(clean).length - b.filter(clean).length; + if (cLen !== 0) return cLen; + + // Least distant first gropus + for (let i = 0; i < Math.min(a.length, b.length) - 1; i += 2) { + const gLen = a[i].length - b[i].length; + if (gLen !== 0) return gLen; + } + + // Most complete groups (raw) + const len = a.length - b.length; + if (len !== 0) return len; + + // Make the search stable since ECMAScript doesn't mandate it + return iA - iB; + }); + results = arr; + window.requestAnimationFrame(() => { + resultList.innerHTML = ""; + arr.forEach(({ matches, id }) => { + const li = document.createElement("li"); + li.innerHTML = matches + .map((c, i) => (i % 2 ? `${c}` : c)) + .join(""); + li.addEventListener("click", () => { + jumpTo(id); + searchBoxElem.classList.add("bgus_hidden"); + }); + resultList.appendChild(li); + }); + if (results.length > 0) { + setSelectedResult(0); + } + }); + }; + + // Create fuzzy search box + const sel = document.createElement("input"); + + // Bind events + let oldValue = ""; + sel.addEventListener("keyup", (event) => { + switch (event.keyCode) { + case 27: // Escape - Hide bar + searchBoxElem.classList.add("bgus_hidden"); + return; + case 13: // Enter - Jump to first result and hide bar + if (results.length > 0) { + jumpTo(results[selectedResult].id); + } + searchBoxElem.classList.add("bgus_hidden"); + return; + case 40: // Down arrow - Select next result + if (selectedResult < results.length - 1) { + setSelectedResult(selectedResult + 1); + } + return; + case 38: // Up arrow - Select previous result + if (selectedResult > 0) { + setSelectedResult(selectedResult - 1); + } + return; + default: + if (sel.value !== oldValue) { + search(sel.value); + oldValue = sel.value; + } + } + }); + + document.body.addEventListener("keyup", (ev) => { + if (ev.keyCode === 83) { + sel.focus(); + } + }); + + document.body.addEventListener("keydown", (ev) => { + if (ev.shiftKey) { + switch (ev.keyCode) { + // SHIFT+S = Fuzzy search + case 83: { + searchBoxElem.classList.remove("bgus_hidden"); + sel.value = ""; + break; + } + default: + // Do nothing + } + } + }); + + searchBoxElem.id = "bgus_fz_searchbox"; + searchBoxElem.classList.add("bgus_hidden"); + searchBoxElem.appendChild(sel); + searchBoxElem.appendChild(resultList); + return searchBoxElem; +} + +export default searchBox; diff --git a/lib/userscript.ts b/lib/userscript.ts index 3d2e7af..ece4258 100644 --- a/lib/userscript.ts +++ b/lib/userscript.ts @@ -1,8 +1,5 @@ import { darken, ColorFmt, lighten } from "./darkmode"; - -const DEFAULT_OPTS = { - alignment: "center", -}; +import searchBox from "./search"; export default function userscript(root: HTMLElement, docname: string): void { root.querySelectorAll(".mw-editsection").forEach((editLink) => { @@ -20,7 +17,7 @@ export default function userscript(root: HTMLElement, docname: string): void { } td.setAttribute("bgcolor", darken(bgcolor, ColorFmt.HEX).slice(1)); }); - root.querySelectorAll("*[style]").forEach((td: HTMLElement) => { + root.querySelectorAll("*[style]").forEach((td) => { if (td.style.backgroundColor !== "") { td.style.backgroundColor = darken(td.style.backgroundColor, ColorFmt.RGB); } @@ -63,7 +60,7 @@ export default function userscript(root: HTMLElement, docname: string): void { div.className = "mw-headline-cont"; }); - root.querySelectorAll(".mw-headline").forEach((span: HTMLElement) => { + root.querySelectorAll(".mw-headline").forEach((span) => { // Find nearest container let parent = span.parentElement; while (parent !== null) { @@ -71,6 +68,7 @@ export default function userscript(root: HTMLElement, docname: string): void { parent.id = span.id; span.id += "-span"; parent.dataset.name = span.innerText; + return; } parent = parent.parentElement; } @@ -87,179 +85,6 @@ export default function userscript(root: HTMLElement, docname: string): void { `; postbody.insertBefore(statusMessage, postbody.firstChild); - // TODO Refactor this mess - function searchBox(el, searchCandidate, options = DEFAULT_OPTS) { - // Fuzzy search box - const resultList = document.createElement("ul"); - const searchBoxElem = document.createElement("div"); - let selectedResult = null; - let results = []; - - const jumpTo = (id) => { - el[id].scrollIntoView({ - block: options.alignment, - inline: "nearest", - behavior: "auto", - }); - document - .querySelectorAll("table.wikitable .bgus_fz_selected") - .forEach((sel) => sel.classList.remove("bgus_fz_selected")); - el[id].parentElement.classList.add("bgus_fz_selected"); - }; - - const setSelectedResult = (i) => { - selectedResult = i; - resultList - .querySelectorAll(".selected") - .forEach((sel) => sel.classList.remove("selected")); - resultList.children[i].classList.add("selected"); - jumpTo(results[i].id); - }; - - const search = (str) => { - if (!str) { - return; - } - const regex = new RegExp( - `^(.*?)([${str - .split("") - .map((c) => (c.includes(["\\", "]", "^"]) ? `\\${c}` : c)) - .join("])(.*?)([")}])(.*?)$`, - "i" - ); - const arr = searchCandidate - .map((o) => { - o.matches = (o.str.match(regex) || []) - .slice(1) - .reduce((list, group, i, or) => { - // Initialize first placeholder (always empty) and first matching "sections" - if (i < 2) { - list.push([group]); - } - // If group is second match in a row join to previous section - else if (or[i - 1] === "") { - list[list.length - 1].push(group); - } - // If group is a match create a new section - else if (group !== "") { - list.push([group]); - } - return list; - }, []) - .map((cstr) => cstr.join("")); - return o; - }) - // Strike non-matching rows - .filter((o) => o.matches.length > 0) - .sort((oA, oB) => { - const iA = oA.id, - a = oA.matches; - const iB = oB.id, - b = oB.matches; - - // Exact match - if (a.length === 1 && b.length !== 1) return -1; - if (a.length !== 1 && b.length === 1) return 1; - - // Most complete groups (alphanumeric) - const clean = (cel) => !/[^a-zA-Z0-9]*$/.test(cel); - const cLen = a.filter(clean).length - b.filter(clean).length; - if (cLen !== 0) return cLen; - - // Least distant first gropus - for (let i = 0; i < Math.min(a.length, b.length) - 1; i += 2) { - const gLen = a[i].length - b[i].length; - if (gLen !== 0) return gLen; - } - - // Most complete groups (raw) - const len = a.length - b.length; - if (len !== 0) return len; - - // Make the search stable since ECMAScript doesn't mandate it - return iA - iB; - }); - results = arr; - window.requestAnimationFrame(() => { - resultList.innerHTML = ""; - arr.forEach(({ matches, id }) => { - const li = document.createElement("li"); - li.innerHTML = matches - .map((c, i) => (i % 2 ? `${c}` : c)) - .join(""); - li.addEventListener("click", () => { - jumpTo(id); - searchBoxElem.classList.add("bgus_hidden"); - }); - resultList.appendChild(li); - }); - if (results.length > 0) { - setSelectedResult(0); - } - }); - }; - - // Create fuzzy search box - const sel = document.createElement("input"); - searchBoxElem.id = "bgus_fz_searchbox"; - searchBoxElem.classList.add("bgus_hidden"); - searchBoxElem.appendChild(sel); - searchBoxElem.appendChild(resultList); - root.appendChild(searchBoxElem); - - // Bind events - let oldValue = ""; - sel.addEventListener("keyup", (event) => { - switch (event.keyCode) { - case 27: // Escape - Hide bar - searchBoxElem.classList.add("bgus_hidden"); - return; - case 13: // Enter - Jump to first result and hide bar - if (results.length > 0) { - jumpTo(results[selectedResult].id); - } - searchBoxElem.classList.add("bgus_hidden"); - return; - case 40: // Down arrow - Select next result - if (selectedResult < results.length - 1) { - setSelectedResult(selectedResult + 1); - } - return; - case 38: // Up arrow - Select previous result - if (selectedResult > 0) { - setSelectedResult(selectedResult - 1); - } - return; - default: - if (sel.value !== oldValue) { - search(sel.value); - oldValue = sel.value; - } - } - }); - - document.body.addEventListener("keyup", (ev) => { - if (ev.keyCode === 83) { - sel.focus(); - } - }); - - document.body.addEventListener("keydown", (ev) => { - if (ev.shiftKey) { - switch (ev.keyCode) { - // SHIFT+S = Fuzzy search - case 83: { - searchBoxElem.classList.remove("bgus_hidden"); - sel.value = ""; - break; - } - default: - // Do nothing - } - } - }); - } - function betterChemistry() { // Fix inconsistencies with

on random parts // Ideally I'd like a

or something on every part, wrapping it completely, but for now let's just kill 'em @@ -272,12 +97,10 @@ export default function userscript(root: HTMLElement, docname: string): void { // The cast to Array is necessary because, while childNodes's NodeList technically has a forEach method, it's a live list and operations mess with its lenght in the middle of the loop. // Nodes can only have one parent so append removes them from the original NodeList and shifts the following one back into the wrong index. Array.from(td.childNodes).forEach((el) => { - if (el instanceof HTMLElement) { - if (el.tagName === "P") { - tmp.append(...el.childNodes); - } else { - tmp.append(el); - } + if (el instanceof Element && el.tagName === "P") { + tmp.append(...el.childNodes); + } else { + tmp.append(el); } }); td.parentNode.replaceChild(tmp, td); @@ -324,7 +147,7 @@ export default function userscript(root: HTMLElement, docname: string): void { }); // Wrap every recipe with extra metadata - root.querySelectorAll(".bgus_part").forEach((el: HTMLElement) => { + root.querySelectorAll(".bgus_part").forEach((el) => { if ("parts" in el.parentElement.dataset) { el.parentElement.dataset.parts = ( parseInt(el.parentElement.dataset.parts, 10) + @@ -365,7 +188,7 @@ export default function userscript(root: HTMLElement, docname: string): void { root.classList.add("bchem"); // Init fuzzy search with elements const el = Array.from( - root.querySelectorAll( + root.querySelectorAll( "table.wikitable > tbody > tr:not(:first-child) > th" ) ); @@ -378,10 +201,11 @@ export default function userscript(root: HTMLElement, docname: string): void { }); return partial.trim(); }); - searchBox( + const box = searchBox( el, name.map((e, i) => ({ id: i, str: e })) ); + root.appendChild(box); // Remove "Removed medicines" section const remTable = root.querySelector( @@ -390,8 +214,8 @@ export default function userscript(root: HTMLElement, docname: string): void { remTable.parentElement.removeChild(remTable); root - .querySelectorAll("div[data-name] .wikitable.sortable tr") - .forEach((row: HTMLElement) => { + .querySelectorAll("div[data-name] .wikitable.sortable tr") + .forEach((row) => { let sectionEl = row.parentElement; while (!sectionEl.dataset.name) { sectionEl = sectionEl.parentElement; @@ -504,16 +328,17 @@ export default function userscript(root: HTMLElement, docname: string): void { function betterGeneric() { const el = Array.from( - root.querySelectorAll("div.mw-headline-cont[id][data-name]") + root.querySelectorAll("div.mw-headline-cont[id][data-name]") ); const name = el.map((elem: HTMLDivElement) => elem.dataset.name.trim()); // Init fuzzy search with headlines - searchBox( + const box = searchBox( el, name.map((e, i) => ({ id: i, str: e })), { alignment: "start" } ); + root.appendChild(box); } switch (docname) { diff --git a/style/bgus.scss b/style/bgus.scss index 4676686..b472076 100644 --- a/style/bgus.scss +++ b/style/bgus.scss @@ -55,11 +55,7 @@ div.tooltiptext { display: none; border: 1px solid #384e68; - background: linear-gradient( - to bottom, - darken(#384e68, 20%), - darken(#384e68, 25%) - ); + background: linear-gradient(to bottom, darken(#384e68, 20%), darken(#384e68, 25%)); } span.bgus_nested_element:not(.bgus_collapsed) + div.tooltiptext { z-index: unset;