Separate search and fix subrecipes
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
5ed479af20
commit
2066991942
3 changed files with 199 additions and 197 deletions
181
lib/search.ts
Normal file
181
lib/search.ts
Normal file
|
@ -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 ? `<strong>${c}</strong>` : 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;
|
|
@ -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<HTMLElement>("*[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<HTMLElement>(".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 {
|
|||
</table>`;
|
||||
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 ? `<strong>${c}</strong>` : 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 <p> on random parts
|
||||
// Ideally I'd like a <p> 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<HTMLElement>(".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<HTMLElement>(
|
||||
"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<HTMLElement>("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<HTMLElement>("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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
Reference in a new issue