mirror of
https://github.com/philomena-dev/philomena.git
synced 2025-02-20 04:14:23 +01:00
convert local autocompleter to typescript
This commit is contained in:
parent
6d85535abd
commit
cccbcd041f
2 changed files with 26 additions and 53 deletions
|
@ -12,7 +12,7 @@ export function leftClick<E extends MouseEvent, Target extends EventTarget>(func
|
||||||
return (event: E, target: Target) => { if (event.button === 0) return func(event, target); };
|
return (event: E, target: Target) => { if (event.button === 0) return func(event, target); };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function delegate<K extends keyof GlobalEventHandlersEventMap>(node: GlobalEventHandlers, event: K, selectors: { [k: string]: ((e: GlobalEventHandlersEventMap[K], target: Element) => boolean) }) {
|
export function delegate<K extends keyof GlobalEventHandlersEventMap>(node: GlobalEventHandlers, event: K, selectors: Record<string, ((e: GlobalEventHandlersEventMap[K], target: Element) => boolean)>) {
|
||||||
node.addEventListener(event, e => {
|
node.addEventListener(event, e => {
|
||||||
for (const selector in selectors) {
|
for (const selector in selectors) {
|
||||||
const evtTarget = e.target as EventTarget | Element | null;
|
const evtTarget = e.target as EventTarget | Element | null;
|
||||||
|
|
|
@ -1,31 +1,23 @@
|
||||||
// Client-side tag completion.
|
// Client-side tag completion.
|
||||||
import store from './store';
|
import store from './store';
|
||||||
|
|
||||||
/**
|
interface Result {
|
||||||
* @typedef {object} Result
|
name: string;
|
||||||
* @property {string} name
|
imageCount: number;
|
||||||
* @property {number} imageCount
|
associations: number[];
|
||||||
* @property {number[]} associations
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compare two strings, C-style.
|
* Compare two strings, C-style.
|
||||||
*
|
|
||||||
* @param {string} a
|
|
||||||
* @param {string} b
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
*/
|
||||||
function strcmp(a, b) {
|
function strcmp(a: string, b: string):number {
|
||||||
return a < b ? -1 : Number(a > b);
|
return a < b ? -1 : Number(a > b);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the name of a tag without any namespace component.
|
* Returns the name of a tag without any namespace component.
|
||||||
*
|
|
||||||
* @param {string} s
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
*/
|
||||||
function nameInNamespace(s) {
|
function nameInNamespace(s: string): string {
|
||||||
const v = s.split(':', 2);
|
const v = s.split(':', 2);
|
||||||
|
|
||||||
if (v.length === 2) return v[1];
|
if (v.length === 2) return v[1];
|
||||||
|
@ -39,25 +31,24 @@ function nameInNamespace(s) {
|
||||||
* the JS heap and speed up the execution of the search.
|
* the JS heap and speed up the execution of the search.
|
||||||
*/
|
*/
|
||||||
export class LocalAutocompleter {
|
export class LocalAutocompleter {
|
||||||
|
private data: Uint8Array;
|
||||||
|
private view: DataView;
|
||||||
|
private decoder: TextDecoder;
|
||||||
|
private numTags: number;
|
||||||
|
private referenceStart: number;
|
||||||
|
private secondaryStart: number;
|
||||||
|
private formatVersion: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build a new local autocompleter.
|
* Build a new local autocompleter.
|
||||||
*
|
|
||||||
* @param {ArrayBuffer} backingStore
|
|
||||||
*/
|
*/
|
||||||
constructor(backingStore) {
|
constructor(backingStore: ArrayBuffer) {
|
||||||
/** @type {Uint8Array} */
|
|
||||||
this.data = new Uint8Array(backingStore);
|
this.data = new Uint8Array(backingStore);
|
||||||
/** @type {DataView} */
|
|
||||||
this.view = new DataView(backingStore);
|
this.view = new DataView(backingStore);
|
||||||
/** @type {TextDecoder} */
|
|
||||||
this.decoder = new TextDecoder();
|
this.decoder = new TextDecoder();
|
||||||
/** @type {number} */
|
|
||||||
this.numTags = this.view.getUint32(backingStore.byteLength - 4, true);
|
this.numTags = this.view.getUint32(backingStore.byteLength - 4, true);
|
||||||
/** @type {number} */
|
|
||||||
this.referenceStart = this.view.getUint32(backingStore.byteLength - 8, true);
|
this.referenceStart = this.view.getUint32(backingStore.byteLength - 8, true);
|
||||||
/** @type {number} */
|
|
||||||
this.secondaryStart = this.referenceStart + 8 * this.numTags;
|
this.secondaryStart = this.referenceStart + 8 * this.numTags;
|
||||||
/** @type {number} */
|
|
||||||
this.formatVersion = this.view.getUint32(backingStore.byteLength - 12, true);
|
this.formatVersion = this.view.getUint32(backingStore.byteLength - 12, true);
|
||||||
|
|
||||||
if (this.formatVersion !== 2) {
|
if (this.formatVersion !== 2) {
|
||||||
|
@ -67,11 +58,8 @@ export class LocalAutocompleter {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a tag's name and its associations given a byte location inside the file.
|
* Get a tag's name and its associations given a byte location inside the file.
|
||||||
*
|
|
||||||
* @param {number} location
|
|
||||||
* @returns {[string, number[]]}
|
|
||||||
*/
|
*/
|
||||||
getTagFromLocation(location) {
|
getTagFromLocation(location: number): [string, number[]] {
|
||||||
const nameLength = this.view.getUint8(location);
|
const nameLength = this.view.getUint8(location);
|
||||||
const assnLength = this.view.getUint8(location + 1 + nameLength);
|
const assnLength = this.view.getUint8(location + 1 + nameLength);
|
||||||
|
|
||||||
|
@ -88,11 +76,8 @@ export class LocalAutocompleter {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a Result object as the ith tag inside the file.
|
* Get a Result object as the ith tag inside the file.
|
||||||
*
|
|
||||||
* @param {number} i
|
|
||||||
* @returns {[string, Result]}
|
|
||||||
*/
|
*/
|
||||||
getResultAt(i) {
|
getResultAt(i: number): [string, Result] {
|
||||||
const nameLocation = this.view.getUint32(this.referenceStart + i * 8, true);
|
const nameLocation = this.view.getUint32(this.referenceStart + i * 8, true);
|
||||||
const imageCount = this.view.getInt32(this.referenceStart + i * 8 + 4, true);
|
const imageCount = this.view.getInt32(this.referenceStart + i * 8 + 4, true);
|
||||||
const [ name, associations ] = this.getTagFromLocation(nameLocation);
|
const [ name, associations ] = this.getTagFromLocation(nameLocation);
|
||||||
|
@ -107,23 +92,16 @@ export class LocalAutocompleter {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a Result object as the ith tag inside the file, secondary ordering.
|
* Get a Result object as the ith tag inside the file, secondary ordering.
|
||||||
*
|
|
||||||
* @param {number} i
|
|
||||||
* @returns {[string, Result]}
|
|
||||||
*/
|
*/
|
||||||
getSecondaryResultAt(i) {
|
getSecondaryResultAt(i: number): [string, Result] {
|
||||||
const referenceIndex = this.view.getUint32(this.secondaryStart + i * 4, true);
|
const referenceIndex = this.view.getUint32(this.secondaryStart + i * 4, true);
|
||||||
return this.getResultAt(referenceIndex);
|
return this.getResultAt(referenceIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Perform a binary search to fetch all results matching a condition.
|
* Perform a binary search to fetch all results matching a condition.
|
||||||
*
|
|
||||||
* @param {(i: number) => [string, Result]} getResult
|
|
||||||
* @param {(name: string) => number} compare
|
|
||||||
* @param {{[key: string]: Result}} results
|
|
||||||
*/
|
*/
|
||||||
scanResults(getResult, compare, results) {
|
scanResults(getResult: (i: number) => [string, Result], compare: (name: string) => number, results: Record<string, Result>) {
|
||||||
const unfilter = store.get('unfilter_tag_suggestions');
|
const unfilter = store.get('unfilter_tag_suggestions');
|
||||||
|
|
||||||
let min = 0;
|
let min = 0;
|
||||||
|
@ -132,7 +110,7 @@ export class LocalAutocompleter {
|
||||||
const hiddenTags = window.booru.hiddenTagList;
|
const hiddenTags = window.booru.hiddenTagList;
|
||||||
|
|
||||||
while (min < max - 1) {
|
while (min < max - 1) {
|
||||||
const med = (min + (max - min) / 2) | 0;
|
const med = min + (max - min) / 2 | 0;
|
||||||
const sortKey = getResult(med)[0];
|
const sortKey = getResult(med)[0];
|
||||||
|
|
||||||
if (compare(sortKey) >= 0) {
|
if (compare(sortKey) >= 0) {
|
||||||
|
@ -161,25 +139,20 @@ export class LocalAutocompleter {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find the top k results by image count which match the given string prefix.
|
* Find the top k results by image count which match the given string prefix.
|
||||||
*
|
|
||||||
* @param {string} prefix
|
|
||||||
* @param {number} k
|
|
||||||
* @returns {Result[]}
|
|
||||||
*/
|
*/
|
||||||
topK(prefix, k) {
|
topK(prefix: string, k:number): Result[] {
|
||||||
/** @type {{[key: string]: Result}} */
|
const results: Record<string, Result> = {};
|
||||||
const results = {};
|
|
||||||
|
|
||||||
if (prefix === '') {
|
if (prefix === '') {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find normally, in full name-sorted order
|
// Find normally, in full name-sorted order
|
||||||
const prefixMatch = (/** @type {string} */ name) => strcmp(name.slice(0, prefix.length), prefix);
|
const prefixMatch = (name: string) => strcmp(name.slice(0, prefix.length), prefix);
|
||||||
this.scanResults(this.getResultAt.bind(this), prefixMatch, results);
|
this.scanResults(this.getResultAt.bind(this), prefixMatch, results);
|
||||||
|
|
||||||
// Find in secondary order
|
// Find in secondary order
|
||||||
const namespaceMatch = (/** @type {string} */ name) => strcmp(nameInNamespace(name).slice(0, prefix.length), prefix);
|
const namespaceMatch = (name: string) => strcmp(nameInNamespace(name).slice(0, prefix.length), prefix);
|
||||||
this.scanResults(this.getSecondaryResultAt.bind(this), namespaceMatch, results);
|
this.scanResults(this.getSecondaryResultAt.bind(this), namespaceMatch, results);
|
||||||
|
|
||||||
// Sort results by image count
|
// Sort results by image count
|
Loading…
Reference in a new issue