mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-05-02 02:20:00 -07:00
website: swap ufuzzy for quickmatch
This commit is contained in:
@@ -1,6 +1,4 @@
|
||||
/**
|
||||
* @import * as _ from "./modules/leeoniya-ufuzzy/1.0.19/dist/uFuzzy.d.ts"
|
||||
*
|
||||
* @import { IChartApi, ISeriesApi as _ISeriesApi, SeriesDefinition, SingleValueData as _SingleValueData, CandlestickData as _CandlestickData, BaselineData as _BaselineData, HistogramData as _HistogramData, SeriesType as LCSeriesType, IPaneApi, LineSeriesPartialOptions as _LineSeriesPartialOptions, HistogramSeriesPartialOptions as _HistogramSeriesPartialOptions, BaselineSeriesPartialOptions as _BaselineSeriesPartialOptions, CandlestickSeriesPartialOptions as _CandlestickSeriesPartialOptions, WhitespaceData, DeepPartial, ChartOptions, Time, LineData as _LineData, createChart as CreateLCChart, LineStyle, createSeriesMarkers as CreateSeriesMarkers, SeriesMarker, ISeriesMarkersPluginApi } from './modules/lightweight-charts/5.1.0/dist/typings.js'
|
||||
*
|
||||
* @import * as Brk from "./modules/brk-client/index.js"
|
||||
|
||||
@@ -109,6 +109,7 @@ export function initOptions(brk) {
|
||||
* @param {Option} option
|
||||
*/
|
||||
function selectOption(option) {
|
||||
if (selected.value === option) return;
|
||||
pushHistory(option.path);
|
||||
resetParams(option);
|
||||
writeToStorage(LS_SELECTED_KEY, JSON.stringify(option.path));
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
searchLabelElement,
|
||||
searchResultsElement,
|
||||
} from "../utils/elements.js";
|
||||
import ufuzzy from "../modules/leeoniya-ufuzzy/1.0.19/dist/uFuzzy.mjs";
|
||||
import { QuickMatch } from "../modules/quickmatch-js/0.3.1/src/index.js";
|
||||
|
||||
/**
|
||||
* @param {Options} options
|
||||
@@ -11,148 +11,47 @@ import ufuzzy from "../modules/leeoniya-ufuzzy/1.0.19/dist/uFuzzy.mjs";
|
||||
export function initSearch(options) {
|
||||
console.log("search: init");
|
||||
|
||||
const haystack = options.list.map((option) => option.title);
|
||||
|
||||
const RESULTS_PER_PAGE = 100;
|
||||
|
||||
/**
|
||||
* @param {uFuzzy.SearchResult} searchResult
|
||||
* @param {number} pageIndex
|
||||
*/
|
||||
function computeResultPage(searchResult, pageIndex) {
|
||||
/** @type {{ option: Option, title: string }[]} */
|
||||
let list = [];
|
||||
|
||||
let [indexes, _info, order] = searchResult || [null, null, null];
|
||||
|
||||
const minIndex = pageIndex * RESULTS_PER_PAGE;
|
||||
|
||||
if (indexes?.length) {
|
||||
const maxIndex = Math.min(
|
||||
(order || indexes).length - 1,
|
||||
minIndex + RESULTS_PER_PAGE - 1,
|
||||
);
|
||||
|
||||
list = Array(maxIndex - minIndex + 1);
|
||||
|
||||
for (let i = minIndex; i <= maxIndex; i++) {
|
||||
let index = indexes[i];
|
||||
|
||||
const title = haystack[index];
|
||||
|
||||
list[i % 100] = {
|
||||
option: options.list[index],
|
||||
title,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/** @type {uFuzzy.Options} */
|
||||
const config = {
|
||||
intraIns: Infinity,
|
||||
intraChars: `[a-z\d' ]`,
|
||||
};
|
||||
|
||||
const fuzzyMultiInsert = /** @type {uFuzzy} */ (
|
||||
ufuzzy({
|
||||
intraIns: 1,
|
||||
})
|
||||
);
|
||||
const fuzzyMultiInsertFuzzier = /** @type {uFuzzy} */ (ufuzzy(config));
|
||||
const fuzzySingleError = /** @type {uFuzzy} */ (
|
||||
ufuzzy({
|
||||
intraMode: 1,
|
||||
...config,
|
||||
})
|
||||
);
|
||||
const fuzzySingleErrorFuzzier = /** @type {uFuzzy} */ (
|
||||
ufuzzy({
|
||||
intraMode: 1,
|
||||
...config,
|
||||
})
|
||||
const haystack = options.list.map((option) => option.title.toLowerCase());
|
||||
const titleToOption = new Map(
|
||||
options.list.map((option) => [option.title.toLowerCase(), option]),
|
||||
);
|
||||
|
||||
const matcher = new QuickMatch(haystack);
|
||||
|
||||
function inputEvent() {
|
||||
const needle = /** @type {string} */ (searchInput.value);
|
||||
const needle = /** @type {string} */ (searchInput.value).trim();
|
||||
|
||||
searchResultsElement.scrollTo({
|
||||
top: 0,
|
||||
});
|
||||
searchResultsElement.scrollTo({ top: 0 });
|
||||
searchResultsElement.innerHTML = "";
|
||||
|
||||
if (!needle) {
|
||||
searchResultsElement.innerHTML = "";
|
||||
if (needle.length < 3) {
|
||||
const li = window.document.createElement("li");
|
||||
li.textContent = 'e.g. "BTC"';
|
||||
li.style.color = "var(--off-color)";
|
||||
searchResultsElement.appendChild(li);
|
||||
return;
|
||||
}
|
||||
|
||||
const outOfOrder = 5;
|
||||
const infoThresh = 5_000;
|
||||
const matches = matcher.matches(needle);
|
||||
|
||||
let result = fuzzyMultiInsert?.search(
|
||||
haystack,
|
||||
needle,
|
||||
undefined,
|
||||
infoThresh,
|
||||
);
|
||||
|
||||
if (!result?.[0]?.length || !result?.[1]) {
|
||||
result = fuzzyMultiInsert?.search(
|
||||
haystack,
|
||||
needle,
|
||||
outOfOrder,
|
||||
infoThresh,
|
||||
);
|
||||
if (!matches.length) {
|
||||
const li = window.document.createElement("li");
|
||||
li.textContent = "No results";
|
||||
li.style.color = "var(--off-color)";
|
||||
searchResultsElement.appendChild(li);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!result?.[0]?.length || !result?.[1]) {
|
||||
result = fuzzySingleError?.search(
|
||||
haystack,
|
||||
needle,
|
||||
outOfOrder,
|
||||
infoThresh,
|
||||
);
|
||||
}
|
||||
matches.forEach((title) => {
|
||||
const option = titleToOption.get(title);
|
||||
if (!option) return;
|
||||
|
||||
if (!result?.[0]?.length || !result?.[1]) {
|
||||
result = fuzzySingleErrorFuzzier?.search(
|
||||
haystack,
|
||||
needle,
|
||||
outOfOrder,
|
||||
infoThresh,
|
||||
);
|
||||
}
|
||||
|
||||
if (!result?.[0]?.length || !result?.[1]) {
|
||||
result = fuzzyMultiInsertFuzzier?.search(
|
||||
haystack,
|
||||
needle,
|
||||
undefined,
|
||||
infoThresh,
|
||||
);
|
||||
}
|
||||
|
||||
if (!result?.[0]?.length || !result?.[1]) {
|
||||
result = fuzzyMultiInsertFuzzier?.search(
|
||||
haystack,
|
||||
needle,
|
||||
outOfOrder,
|
||||
infoThresh,
|
||||
);
|
||||
}
|
||||
|
||||
searchResultsElement.innerHTML = "";
|
||||
|
||||
const list = computeResultPage(result, 0);
|
||||
|
||||
list.forEach(({ option, title }) => {
|
||||
const li = window.document.createElement("li");
|
||||
searchResultsElement.appendChild(li);
|
||||
|
||||
const element = options.createOptionElement({
|
||||
option,
|
||||
name: title,
|
||||
name: option.title,
|
||||
});
|
||||
|
||||
if (element) {
|
||||
@@ -161,11 +60,11 @@ export function initSearch(options) {
|
||||
});
|
||||
}
|
||||
|
||||
if (searchInput.value) {
|
||||
inputEvent();
|
||||
}
|
||||
inputEvent();
|
||||
|
||||
searchInput.addEventListener("input", inputEvent);
|
||||
const len = searchInput.value.length;
|
||||
searchInput.setSelectionRange(len, len);
|
||||
}
|
||||
|
||||
document.addEventListener("keydown", (e) => {
|
||||
|
||||
@@ -165,52 +165,6 @@ export function createLabeledInput({
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HTMLElement} parent
|
||||
* @param {HTMLElement} child
|
||||
* @param {number} index
|
||||
*/
|
||||
export function insertElementAtIndex(parent, child, index) {
|
||||
if (!index) index = 0;
|
||||
if (index >= parent.children.length) {
|
||||
parent.appendChild(child);
|
||||
} else {
|
||||
parent.insertBefore(child, parent.children[index]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} url
|
||||
* @param {boolean} [targetBlank]
|
||||
*/
|
||||
export function open(url, targetBlank) {
|
||||
console.log(`open: ${url}`);
|
||||
const a = window.document.createElement("a");
|
||||
window.document.body.append(a);
|
||||
a.href = url;
|
||||
|
||||
if (targetBlank) {
|
||||
a.target = "_blank";
|
||||
a.rel = "noopener noreferrer";
|
||||
}
|
||||
|
||||
a.click();
|
||||
a.remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} href
|
||||
*/
|
||||
export function importStyle(href) {
|
||||
const link = document.createElement("link");
|
||||
link.href = href;
|
||||
link.type = "text/css";
|
||||
link.rel = "stylesheet";
|
||||
link.media = "screen,print";
|
||||
const head = window.document.getElementsByTagName("head")[0];
|
||||
head.appendChild(link);
|
||||
return link;
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
@@ -233,9 +187,6 @@ export function createRadios({
|
||||
const field = window.document.createElement("div");
|
||||
field.classList.add("field");
|
||||
|
||||
const div = window.document.createElement("div");
|
||||
field.append(div);
|
||||
|
||||
const initialKey = toKey(initialValue);
|
||||
|
||||
/** @param {string} key */
|
||||
@@ -245,7 +196,7 @@ export function createRadios({
|
||||
if (choices.length === 1) {
|
||||
const span = window.document.createElement("span");
|
||||
span.textContent = toLabel(choices[0]);
|
||||
div.append(span);
|
||||
field.append(span);
|
||||
} else {
|
||||
const fieldId = id ?? "";
|
||||
choices.forEach((choice) => {
|
||||
@@ -261,7 +212,7 @@ export function createRadios({
|
||||
|
||||
const text = window.document.createTextNode(choiceLabel);
|
||||
label.append(text);
|
||||
div.append(label);
|
||||
field.append(label);
|
||||
});
|
||||
|
||||
field.addEventListener("change", (event) => {
|
||||
@@ -297,9 +248,8 @@ export function createSelect({
|
||||
? unsortedChoices.toSorted((a, b) => toLabel(a).localeCompare(toLabel(b)))
|
||||
: unsortedChoices;
|
||||
|
||||
const select = window.document.createElement("select");
|
||||
select.id = id ?? "";
|
||||
select.name = id ?? "";
|
||||
const field = window.document.createElement("div");
|
||||
field.classList.add("field");
|
||||
|
||||
const initialKey = toKey(initialValue);
|
||||
|
||||
@@ -307,21 +257,39 @@ export function createSelect({
|
||||
const fromKey = (key) =>
|
||||
choices.find((c) => toKey(c) === key) ?? initialValue;
|
||||
|
||||
choices.forEach((choice) => {
|
||||
const option = window.document.createElement("option");
|
||||
option.value = toKey(choice);
|
||||
option.textContent = toLabel(choice);
|
||||
if (toKey(choice) === initialKey) {
|
||||
option.selected = true;
|
||||
if (choices.length === 1) {
|
||||
const span = window.document.createElement("span");
|
||||
span.textContent = toLabel(choices[0]);
|
||||
field.append(span);
|
||||
} else {
|
||||
const select = window.document.createElement("select");
|
||||
select.id = id ?? "";
|
||||
select.name = id ?? "";
|
||||
field.append(select);
|
||||
|
||||
choices.forEach((choice) => {
|
||||
const option = window.document.createElement("option");
|
||||
option.value = toKey(choice);
|
||||
option.textContent = toLabel(choice);
|
||||
if (toKey(choice) === initialKey) {
|
||||
option.selected = true;
|
||||
}
|
||||
select.append(option);
|
||||
});
|
||||
|
||||
select.addEventListener("change", () => {
|
||||
onChange?.(fromKey(select.value));
|
||||
});
|
||||
|
||||
const remaining = choices.length - 1;
|
||||
if (remaining > 0) {
|
||||
const small = window.document.createElement("small");
|
||||
small.textContent = `+${remaining}`;
|
||||
field.append(small);
|
||||
}
|
||||
select.append(option);
|
||||
});
|
||||
}
|
||||
|
||||
select.addEventListener("change", () => {
|
||||
onChange?.(fromKey(select.value));
|
||||
});
|
||||
|
||||
return select;
|
||||
return field;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -361,39 +329,6 @@ export function createOption(arg) {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {Object} args
|
||||
* @param {string} args.title
|
||||
* @param {string} args.description
|
||||
* @param {HTMLElement} args.input
|
||||
*/
|
||||
export function createFieldElement({ title, description, input }) {
|
||||
const div = window.document.createElement("div");
|
||||
|
||||
const label = window.document.createElement("label");
|
||||
div.append(label);
|
||||
|
||||
const titleElement = window.document.createElement("span");
|
||||
titleElement.innerHTML = title;
|
||||
label.append(titleElement);
|
||||
|
||||
const descriptionElement = window.document.createElement("small");
|
||||
descriptionElement.innerHTML = description;
|
||||
label.append(descriptionElement);
|
||||
|
||||
div.append(input);
|
||||
|
||||
const forId = input.id || input.firstElementChild?.id;
|
||||
|
||||
if (!forId) {
|
||||
console.log(input);
|
||||
throw `Input should've an ID`;
|
||||
}
|
||||
|
||||
label.htmlFor = forId;
|
||||
|
||||
return div;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {'left' | 'bottom' | 'top' | 'right'} position
|
||||
|
||||
Reference in New Issue
Block a user