fix search sorting

This commit is contained in:
cenfun 2025-03-15 21:24:41 +08:00
parent 39eaa76b8a
commit dfd953b2ae
2 changed files with 164 additions and 17 deletions

View File

@ -347,6 +347,55 @@ export function md5(inputString) {
return rh(a)+rh(b)+rh(c)+rh(d); return rh(a)+rh(b)+rh(c)+rh(d);
} }
const levenArray = [];
const levenCodeCache = [];
export const leven = (first, second) => {
if (first === second) {
return 0;
}
const swap = first;
if (first.length > second.length) {
first = second;
second = swap;
}
let firstLength = first.length;
let secondLength = second.length;
while (firstLength > 0 && (first.charCodeAt(~-firstLength) === second.charCodeAt(~-secondLength))) {
firstLength--;
secondLength--;
}
let start = 0;
while (start < firstLength && (first.charCodeAt(start) === second.charCodeAt(start))) {
start++;
}
firstLength -= start;
secondLength -= start;
if (firstLength === 0) {
return secondLength;
}
let bCharacterCode;
let result;
let temporary;
let temporary2;
let index = 0;
let index2 = 0;
while (index < firstLength) {
levenCodeCache[index] = first.charCodeAt(start + index);
levenArray[index] = ++index;
}
while (index2 < secondLength) {
bCharacterCode = second.charCodeAt(start + index2);
temporary = index2++;
result = index2;
for (index = 0; index < firstLength; index++) {
temporary2 = bCharacterCode === levenCodeCache[index] ? temporary : temporary + 1;
temporary = levenArray[index];
result = levenArray[index] = temporary > result ? (temporary2 > result ? result + 1 : temporary2) : (temporary2 > temporary ? temporary + 1 : temporary2);
}
}
return result;
}
export async function fetchData(route, options) { export async function fetchData(route, options) {
let err; let err;
const res = await api.fetchApi(route, options).catch(e => { const res = await api.fetchApi(route, options).catch(e => {
@ -595,12 +644,10 @@ export function showPopover(target, text, className, options) {
} }
let $tooltip; let $tooltip;
export function hideTooltip(target) { export function hideTooltip() {
if ($tooltip) { if ($tooltip) {
$tooltip.style.display = "none"; $tooltip.remove();
$tooltip.innerHTML = ""; $tooltip = null;
$tooltip.style.top = "0px";
$tooltip.style.left = "0px";
} }
} }
export function showTooltip(target, text, className = 'cn-tooltip', styleMap = {}) { export function showTooltip(target, text, className = 'cn-tooltip', styleMap = {}) {
@ -639,11 +686,7 @@ function initTooltip () {
} }
}; };
const mouseleaveHandler = (e) => { const mouseleaveHandler = (e) => {
const target = e.target; hideTooltip();
const text = target.getAttribute('tooltip');
if (text) {
hideTooltip(target);
}
}; };
document.body.removeEventListener('mouseenter', mouseenterHandler, true); document.body.removeEventListener('mouseenter', mouseenterHandler, true);
document.body.removeEventListener('mouseleave', mouseleaveHandler, true); document.body.removeEventListener('mouseleave', mouseleaveHandler, true);

View File

@ -7,7 +7,7 @@ import {
fetchData, md5, icons, show_message, customConfirm, customAlert, customPrompt, fetchData, md5, icons, show_message, customConfirm, customAlert, customPrompt,
sanitizeHTML, infoToast, showTerminal, setNeedRestart, sanitizeHTML, infoToast, showTerminal, setNeedRestart,
storeColumnWidth, restoreColumnWidth, getTimeAgo, copyText, loadCss, storeColumnWidth, restoreColumnWidth, getTimeAgo, copyText, loadCss,
showPopover, hidePopover showPopover, hidePopover, leven
} from "./common.js"; } from "./common.js";
// https://cenfun.github.io/turbogrid/api.html // https://cenfun.github.io/turbogrid/api.html
@ -418,11 +418,7 @@ export class CustomNodesManager {
".cn-manager-keywords": { ".cn-manager-keywords": {
input: (e) => { input: (e) => {
const keywords = `${e.target.value}`.trim(); this.onKeywordsChange(e);
if (keywords !== this.keywords) {
this.keywords = keywords;
this.updateGrid();
}
}, },
focus: (e) => e.target.select() focus: (e) => e.target.select()
}, },
@ -539,7 +535,7 @@ export class CustomNodesManager {
this.addHighlight(d.rowItem); this.addHighlight(d.rowItem);
if (d.columnItem.id === "nodes") { if (d.columnItem && d.columnItem.id === "nodes") {
this.showNodes(d); this.showNodes(d);
return; return;
} }
@ -596,6 +592,14 @@ export class CustomNodesManager {
return autoHeightColumns.includes(columnItem.id) return autoHeightColumns.includes(columnItem.id)
}, },
rowFilteredSort: () => {
if (this.keywords) {
return {
id: 'sort_score'
};
}
},
// updateGrid handler for filter and keywords // updateGrid handler for filter and keywords
rowFilter: (rowItem) => { rowFilter: (rowItem) => {
@ -612,12 +616,109 @@ export class CustomNodesManager {
} }
} }
// calculate sort score
if (shouldShown && this.keywords) {
rowItem.sort_score = this.calculateSortScore(rowItem, searchableColumns);
}
return shouldShown; return shouldShown;
} }
}); });
} }
onKeywordsChange(e) {
this.grid.showLoading();
// debounce for performance
clearTimeout(this.timeKeywords);
this.timeKeywords = setTimeout(() => {
this.grid.hideLoading();
const keywords = `${e.target.value}`.trim();
if (keywords !== this.keywords) {
this.keywords = keywords;
if (keywords) {
this.grid.removeSortColumn();
} else {
this.grid.sortRows("id", {
sortAsc: true
});
}
this.updateGrid();
}
}, 300);
}
calculateSortScore(rowItem, searchableColumns) {
const keywords = this.keywords.split(/\s+/g).filter((s) => s);
const lowerKeywords = keywords.map(k => k.toLowerCase());
const matchedList = searchableColumns.map(id => {
const { highlightKey, textKey } = this.grid.options.highlightKeywords;
const highlight = rowItem[`${highlightKey}${id}`];
if (!highlight) {
return;
}
const text = `${rowItem[`${textKey}${id}`] || rowItem[id]}`;
const lowerText = text.toLowerCase();
const matchedItems = keywords.map((key, i) => {
// multiple matched
const lowerKey = lowerKeywords[i];
const len = lowerKey.length;
const matches = [];
let index = lowerText.indexOf(lowerKey);
while (index !== -1) {
matches.push(index);
index = lowerText.indexOf(lowerKey, index + len);
}
if (!matches.length) {
return
}
const distances = matches.map(start => {
const end = start + len
const str = text.slice(start, end);
let distance = leven(key, str);
const prev = text.slice(start - 1, start);
if (prev) {
if (/[A-Za-z]/.test(prev)) {
distance += 1;
} else if (/[0-9]/.test(prev)) {
distance += 0.8;
}
}
const next = text.slice(end, end + 1);
if (next) {
if (/[A-Za-z]/.test(next)) {
distance += 0.8;
} else if (/[0-9]/.test(next)) {
distance += 0.5;
}
}
return distance;
});
// console.log(rowItem.title, distances)
return {
// min
distance: Math.min.apply(null, distances)
}
}).filter(it => it);
if (matchedItems.length < keywords.length) {
return;
}
return {
// avg
distance: matchedItems.map(it => it.distance).reduce((p, v) => p + v, 0) / matchedItems.length
}
}).filter(it => it);
// by distance
let distance = Math.min.apply(null, matchedList.map(it => it.distance));
// by matched count
distance += 1 - matchedList.length / searchableColumns.length;
// by stars
const stars = TG.Util.toNum(rowItem.stars);
distance += 1 - stars / 10000;
// score
return 1 / distance;
}
hasAlternatives() { hasAlternatives() {
return this.filter === ShowMode.ALTERNATIVES return this.filter === ShowMode.ALTERNATIVES
} }
@ -756,6 +857,7 @@ export class CustomNodesManager {
id: "nodes", id: "nodes",
name: "Nodes", name: "Nodes",
width: 100, width: 100,
sortAsc: false,
formatter: (v, rowItem, columnItem) => { formatter: (v, rowItem, columnItem) => {
if (!rowItem.nodes) { if (!rowItem.nodes) {
return ''; return '';
@ -796,6 +898,7 @@ export class CustomNodesManager {
id: 'stars', id: 'stars',
name: '★', name: '★',
align: 'center', align: 'center',
sortAsc: false,
classMap: "cn-pack-stars", classMap: "cn-pack-stars",
formatter: (stars) => { formatter: (stars) => {
if (stars < 0) { if (stars < 0) {
@ -811,6 +914,7 @@ export class CustomNodesManager {
name: 'Last Update', name: 'Last Update',
align: 'center', align: 'center',
type: 'date', type: 'date',
sortAsc: false,
width: 100, width: 100,
classMap: "cn-pack-last-update", classMap: "cn-pack-last-update",
formatter: (last_update) => { formatter: (last_update) => {