summaryrefslogtreecommitdiff
path: root/components/Searchable.qml
diff options
context:
space:
mode:
authorlittleblack111 <littleblack11111@gmail.com>2026-09-02 18:26:45 +0800
committerlittleblack111 <littleblack11111@gmail.com>2026-09-02 18:26:45 +0800
commitf7a43077fe4e139dd7e3f46f448e0951740ec148 (patch)
treef6a68248fe42bd899e99049bab7fdb7e5d26ea60 /components/Searchable.qml
parent2f7576faf493b4667b02dcb60ddbf3aa0a9f654b (diff)
fix: qmlfmt
IListView: animation
Diffstat (limited to 'components/Searchable.qml')
-rw-r--r--components/Searchable.qml510
1 files changed, 255 insertions, 255 deletions
diff --git a/components/Searchable.qml b/components/Searchable.qml
index a3f1034..666e2d9 100644
--- a/components/Searchable.qml
+++ b/components/Searchable.qml
@@ -6,259 +6,259 @@ import "../utils/fuzzysort.js" as Fuzzy
import "../utils/levendist.js" as Levendist
Scope {
- required property var list
- property string key: "name"
-
- enum SearchAlgorithm {
- Levendist,
- Fzf,
- Fuzzysort,
- Include
- }
-
- property int algorithm: SearchAlgorithm.Levendist
- property int scoreThreshold: 50
- property var extraOpts: ({})
- property list<string> keys: [key]
- property list<real> weights: [1]
- property var queryCache: ({})
-
- readonly property var fzf: algorithm === Searchable.SearchAlgorithm.Fzf ? new Fzf.Finder(list, Object.assign({
- selector
- }, extraOpts)) : null
-
- readonly property list<var> fzfFinders: algorithm === Searchable.SearchAlgorithm.Fzf ? keys.map(k => {
- return new Fzf.Finder(list, Object.assign({
- selector: item => fieldString(item, k)
- }, extraOpts));
- }) : []
-
- readonly property list<var> fuzzyPrepped: algorithm === Searchable.SearchAlgorithm.Fuzzysort ? list.map(e => {
- const obj = {
- _item: e
- };
- for (const k of keys)
- obj[k] = Fuzzy.prepare(String(e[k] ?? ""));
- return obj;
- }) : []
-
- function transformSearch(search: string): string {
- return search;
- }
-
- function fieldString(item: var, k: string): string {
- const v = item[k];
- return v == null ? "" : String(v);
- }
-
- function selector(item: var): string {
- return fieldString(item, keys[0]);
- }
-
- function getCachedSource(search) {
- if (queryCache[search])
- return {
- hit: true,
- data: queryCache[search]
- };
- let source = list;
- let previousLists = Object.keys(queryCache).filter(key => key && search.startsWith(key));
- if (previousLists.length > 0) {
- var longest = previousLists.reduce((a, b) => a.length >= b.length ? a : b);
- source = queryCache[longest];
- }
- return {
- hit: false,
- data: source
- };
- }
-
- function levendistQuery(search: string): list<var> {
- const prefixGroups = keys.map(() => []);
- const fuzzyGroups = keys.map(() => []);
-
- for (const s of list) {
- let best = null;
- for (let i = 0; i < keys.length; i++) {
- const t = fieldString(s, keys[i]).toLowerCase();
- if (!t)
- continue;
-
- if (t.startsWith(search)) {
- if (!best || i < best.index || best.type !== "prefix") {
- best = {
- type: "prefix",
- index: i,
- score: 0
- };
- if (best.index === 0)
- break;
- }
- continue;
- }
-
- let pos = 0;
- for (const c of search) {
- pos = t.indexOf(c, pos);
- if (pos < 0) {
- pos = 0;
- break;
- }
- pos++;
- }
- if (pos) {
- const d = Levendist.distance(search, t);
- if (d <= scoreThreshold) {
- if (!best || i < best.index || (best.type === "fuzzy" && i === best.index && d < best.score) || best.type === "prefix") {
- if (!best || i < best.index || d < best.score) {
- best = {
- type: "fuzzy",
- index: i,
- score: d
- };
- }
- }
- }
- }
- }
-
- if (best) {
- if (best.type === "prefix")
- prefixGroups[best.index].push(s);
- else
- fuzzyGroups[best.index].push({
- s,
- score: best.score
- });
- }
- }
-
- const results = [];
- const seen = new Set();
- for (let i = 0; i < keys.length; i++) {
- for (const item of prefixGroups[i]) {
- if (!seen.has(item)) {
- results.push(item);
- seen.add(item);
- }
- }
- fuzzyGroups[i].sort((a, b) => a.score - b.score).forEach(({
- s
- }) => {
- if (!seen.has(s)) {
- results.push(s);
- seen.add(s);
- }
- });
- }
-
- return results;
- }
-
- function fuzzysortQuery(search: string): list<var> {
- const res = Fuzzy.go(search, fuzzyPrepped, Object.assign({
- all: true,
- keys,
- scoreFn: r => weights.reduce((a, w, i) => a + r[i].score * w, 0)
- }, extraOpts));
-
- const groups = keys.map(() => []);
- const noMatch = [];
- for (const r of res) {
- let idx = -1;
- for (let i = 0; i < keys.length; i++) {
- const kr = r[i];
- if (kr && kr.score !== -Infinity) {
- idx = i;
- break;
- }
- }
- if (idx === -1)
- noMatch.push(r);
- else
- groups[idx].push(r);
- }
-
- const ordered = [];
- for (let i = 0; i < groups.length; i++) {
- for (const r of groups[i])
- ordered.push(r.obj._item);
- }
- for (const r of noMatch)
- ordered.push(r.obj._item);
-
- return ordered;
- }
-
- function fzfQuery(search: string): list<var> {
- let results;
- if (keys.length <= 1) {
- results = fzf.find(search).sort((a, b) => {
- if (a.score === b.score)
- return (selector(a.item).trim().length - selector(b.item).trim().length);
- return b.score - a.score;
- }).map(r => r.item);
- } else {
- const groups = keys.map((_, i) => {
- const arr = fzfFinders[i].find(search);
- arr.sort((a, b) => {
- if (a.score === b.score) {
- const la = fieldString(a.item, keys[i]).trim().length;
- const lb = fieldString(b.item, keys[i]).trim().length;
- return la - lb;
- }
- return b.score - a.score;
- });
- return arr;
- });
-
- const seen = new Set();
- const out = [];
- for (let i = 0; i < groups.length; i++) {
- for (const r of groups[i]) {
- const it = r.item;
- if (!seen.has(it)) {
- out.push(it);
- seen.add(it);
- }
- }
- }
- results = out;
- }
-
- return results;
- }
-
- function includeQuery(search: string): list<var> {
- return list.filter(item => keys.some(k => transformSearch(fieldString(item, k)).includes(search)));
- }
-
- function query(search: string): list<var> {
- search = transformSearch(search);
- if (!search)
- return list;
-
- const cacheCheck = getCachedSource(search);
- if (cacheCheck.hit)
- return cacheCheck.data;
-
- let result;
-
- if (algorithm === Searchable.SearchAlgorithm.Levendist)
- result = levendistQuery(search.toLowerCase());
-
- if (algorithm === Searchable.SearchAlgorithm.Fuzzysort)
- result = fuzzysortQuery(search);
-
- if (algorithm === Searchable.SearchAlgorithm.Fzf)
- result = fzfQuery(search);
-
- if (algorithm === Searchable.SearchAlgorithm.Include)
- result = includeQuery(search);
-
- queryCache[search] = result;
-
- return result;
- return [];
- }
+ required property var list
+ property string key: "name"
+
+ enum SearchAlgorithm {
+ Levendist,
+ Fzf,
+ Fuzzysort,
+ Include
+ }
+
+ property int algorithm: SearchAlgorithm.Levendist
+ property int scoreThreshold: 50
+ property var extraOpts: ({})
+ property list<string> keys: [key]
+ property list<real> weights: [1]
+ property var queryCache: ({})
+
+ readonly property var fzf: algorithm === Searchable.SearchAlgorithm.Fzf ? new Fzf.Finder(list, Object.assign({
+ selector
+ }, extraOpts)) : null
+
+ readonly property list<var> fzfFinders: algorithm === Searchable.SearchAlgorithm.Fzf ? keys.map(k => {
+ return new Fzf.Finder(list, Object.assign({
+ selector: item => fieldString(item, k)
+ }, extraOpts));
+ }) : []
+
+ readonly property list<var> fuzzyPrepped: algorithm === Searchable.SearchAlgorithm.Fuzzysort ? list.map(e => {
+ const obj = {
+ _item: e
+ };
+ for (const k of keys)
+ obj[k] = Fuzzy.prepare(String(e[k] ?? ""));
+ return obj;
+ }) : []
+
+ function transformSearch(search: string): string {
+ return search;
+ }
+
+ function fieldString(item: var, k: string): string {
+ const v = item[k];
+ return v == null ? "" : String(v);
+ }
+
+ function selector(item: var): string {
+ return fieldString(item, keys[0]);
+ }
+
+ function getCachedSource(search) {
+ if (queryCache[search])
+ return {
+ hit: true,
+ data: queryCache[search]
+ };
+ let source = list;
+ let previousLists = Object.keys(queryCache).filter(key => key && search.startsWith(key));
+ if (previousLists.length > 0) {
+ var longest = previousLists.reduce((a, b) => a.length >= b.length ? a : b);
+ source = queryCache[longest];
+ }
+ return {
+ hit: false,
+ data: source
+ };
+ }
+
+ function levendistQuery(search: string): list<var> {
+ const prefixGroups = keys.map(() => []);
+ const fuzzyGroups = keys.map(() => []);
+
+ for (const s of list) {
+ let best = null;
+ for (let i = 0; i < keys.length; i++) {
+ const t = fieldString(s, keys[i]).toLowerCase();
+ if (!t)
+ continue;
+
+ if (t.startsWith(search)) {
+ if (!best || i < best.index || best.type !== "prefix") {
+ best = {
+ type: "prefix",
+ index: i,
+ score: 0
+ };
+ if (best.index === 0)
+ break;
+ }
+ continue;
+ }
+
+ let pos = 0;
+ for (const c of search) {
+ pos = t.indexOf(c, pos);
+ if (pos < 0) {
+ pos = 0;
+ break;
+ }
+ pos++;
+ }
+ if (pos) {
+ const d = Levendist.distance(search, t);
+ if (d <= scoreThreshold) {
+ if (!best || i < best.index || (best.type === "fuzzy" && i === best.index && d < best.score) || best.type === "prefix") {
+ if (!best || i < best.index || d < best.score) {
+ best = {
+ type: "fuzzy",
+ index: i,
+ score: d
+ };
+ }
+ }
+ }
+ }
+ }
+
+ if (best) {
+ if (best.type === "prefix")
+ prefixGroups[best.index].push(s);
+ else
+ fuzzyGroups[best.index].push({
+ s,
+ score: best.score
+ });
+ }
+ }
+
+ const results = [];
+ const seen = new Set();
+ for (let i = 0; i < keys.length; i++) {
+ for (const item of prefixGroups[i]) {
+ if (!seen.has(item)) {
+ results.push(item);
+ seen.add(item);
+ }
+ }
+ fuzzyGroups[i].sort((a, b) => a.score - b.score).forEach(({
+ s
+ }) => {
+ if (!seen.has(s)) {
+ results.push(s);
+ seen.add(s);
+ }
+ });
+ }
+
+ return results;
+ }
+
+ function fuzzysortQuery(search: string): list<var> {
+ const res = Fuzzy.go(search, fuzzyPrepped, Object.assign({
+ all: true,
+ keys,
+ scoreFn: r => weights.reduce((a, w, i) => a + r[i].score * w, 0)
+ }, extraOpts));
+
+ const groups = keys.map(() => []);
+ const noMatch = [];
+ for (const r of res) {
+ let idx = -1;
+ for (let i = 0; i < keys.length; i++) {
+ const kr = r[i];
+ if (kr && kr.score !== -Infinity) {
+ idx = i;
+ break;
+ }
+ }
+ if (idx === -1)
+ noMatch.push(r);
+ else
+ groups[idx].push(r);
+ }
+
+ const ordered = [];
+ for (let i = 0; i < groups.length; i++) {
+ for (const r of groups[i])
+ ordered.push(r.obj._item);
+ }
+ for (const r of noMatch)
+ ordered.push(r.obj._item);
+
+ return ordered;
+ }
+
+ function fzfQuery(search: string): list<var> {
+ let results;
+ if (keys.length <= 1) {
+ results = fzf.find(search).sort((a, b) => {
+ if (a.score === b.score)
+ return (selector(a.item).trim().length - selector(b.item).trim().length);
+ return b.score - a.score;
+ }).map(r => r.item);
+ } else {
+ const groups = keys.map((_, i) => {
+ const arr = fzfFinders[i].find(search);
+ arr.sort((a, b) => {
+ if (a.score === b.score) {
+ const la = fieldString(a.item, keys[i]).trim().length;
+ const lb = fieldString(b.item, keys[i]).trim().length;
+ return la - lb;
+ }
+ return b.score - a.score;
+ });
+ return arr;
+ });
+
+ const seen = new Set();
+ const out = [];
+ for (let i = 0; i < groups.length; i++) {
+ for (const r of groups[i]) {
+ const it = r.item;
+ if (!seen.has(it)) {
+ out.push(it);
+ seen.add(it);
+ }
+ }
+ }
+ results = out;
+ }
+
+ return results;
+ }
+
+ function includeQuery(search: string): list<var> {
+ return list.filter(item => keys.some(k => transformSearch(fieldString(item, k)).includes(search)));
+ }
+
+ function query(search: string): list<var> {
+ search = transformSearch(search);
+ if (!search)
+ return list;
+
+ const cacheCheck = getCachedSource(search);
+ if (cacheCheck.hit)
+ return cacheCheck.data;
+
+ let result;
+
+ if (algorithm === Searchable.SearchAlgorithm.Levendist)
+ result = levendistQuery(search.toLowerCase());
+
+ if (algorithm === Searchable.SearchAlgorithm.Fuzzysort)
+ result = fuzzysortQuery(search);
+
+ if (algorithm === Searchable.SearchAlgorithm.Fzf)
+ result = fzfQuery(search);
+
+ if (algorithm === Searchable.SearchAlgorithm.Include)
+ result = includeQuery(search);
+
+ queryCache[search] = result;
+
+ return result;
+ return [];
+ }
}