mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-21 18:54:01 -04:00
292 lines
7.9 KiB
JavaScript
292 lines
7.9 KiB
JavaScript
.pragma library
|
|
|
|
const Weights = {
|
|
exactMatch: 10000,
|
|
prefixMatch: 5000,
|
|
wordBoundary: 3000,
|
|
substring: 500,
|
|
genericPrefix: 800,
|
|
generic: 400,
|
|
id: 350,
|
|
fuzzy: 100,
|
|
frecency: 2000,
|
|
typeBonus: {
|
|
app: 1000,
|
|
plugin: 900,
|
|
setting: 850,
|
|
clipboard: 825,
|
|
file: 800,
|
|
action: 600
|
|
}
|
|
};
|
|
|
|
function tokenize(text) {
|
|
return text.toLowerCase().trim().split(/[\s\-_]+/).filter(function (w) {
|
|
return w.length > 0;
|
|
});
|
|
}
|
|
|
|
function hasWordBoundaryMatch(text, query) {
|
|
var textWords = tokenize(text);
|
|
var queryWords = tokenize(query);
|
|
|
|
if (queryWords.length === 0)
|
|
return false;
|
|
if (queryWords.length > textWords.length)
|
|
return false;
|
|
|
|
for (var i = 0; i <= textWords.length - queryWords.length; i++) {
|
|
var allMatch = true;
|
|
for (var j = 0; j < queryWords.length; j++) {
|
|
if (!textWords[i + j].startsWith(queryWords[j])) {
|
|
allMatch = false;
|
|
break;
|
|
}
|
|
}
|
|
if (allMatch)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function levenshteinDistance(s1, s2) {
|
|
var len1 = s1.length;
|
|
var len2 = s2.length;
|
|
var prev = new Array(len2 + 1);
|
|
var curr = new Array(len2 + 1);
|
|
|
|
for (var j = 0; j <= len2; j++)
|
|
prev[j] = j;
|
|
|
|
for (var i = 1; i <= len1; i++) {
|
|
curr[0] = i;
|
|
for (var j = 1; j <= len2; j++) {
|
|
var cost = s1[i - 1] === s2[j - 1] ? 0 : 1;
|
|
curr[j] = Math.min(prev[j] + 1, curr[j - 1] + 1, prev[j - 1] + cost);
|
|
}
|
|
var tmp = prev;
|
|
prev = curr;
|
|
curr = tmp;
|
|
}
|
|
return prev[len2];
|
|
}
|
|
|
|
function fuzzyScore(text, query) {
|
|
var maxDistance = query.length === 3 ? 1 : query.length <= 6 ? 2 : 3;
|
|
var bestScore = 0;
|
|
|
|
if (Math.abs(text.length - query.length) <= maxDistance) {
|
|
var distance = levenshteinDistance(text, query);
|
|
if (distance <= maxDistance) {
|
|
var maxLen = Math.max(text.length, query.length);
|
|
bestScore = 1 - (distance / maxLen);
|
|
}
|
|
}
|
|
|
|
var words = tokenize(text);
|
|
for (var i = 0; i < words.length && bestScore < 0.8; i++) {
|
|
if (Math.abs(words[i].length - query.length) > maxDistance)
|
|
continue;
|
|
var wordDistance = levenshteinDistance(words[i], query);
|
|
if (wordDistance <= maxDistance) {
|
|
var wordMaxLen = Math.max(words[i].length, query.length);
|
|
var score = 1 - (wordDistance / wordMaxLen);
|
|
bestScore = Math.max(bestScore, score);
|
|
}
|
|
}
|
|
|
|
return bestScore;
|
|
}
|
|
|
|
function calculateTextScore(name, query) {
|
|
if (name === query)
|
|
return Weights.exactMatch;
|
|
if (name.startsWith(query))
|
|
return Weights.prefixMatch;
|
|
if (hasWordBoundaryMatch(name, query))
|
|
return Weights.wordBoundary;
|
|
if (name.includes(query))
|
|
return Weights.substring;
|
|
|
|
if (query.length >= 3) {
|
|
var fs = fuzzyScore(name, query);
|
|
if (fs > 0)
|
|
return fs * Weights.fuzzy;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
function score(item, query, frecencyData) {
|
|
var typeBonus = Weights.typeBonus[item.type] || 0;
|
|
|
|
if (!query || query.length === 0) {
|
|
var usageCount = frecencyData ? frecencyData.usageCount : 0;
|
|
return typeBonus + (usageCount * 100);
|
|
}
|
|
|
|
var name = (item.name || "").toLowerCase();
|
|
var q = query.toLowerCase();
|
|
|
|
var textScore = calculateTextScore(name, q);
|
|
|
|
if (textScore === 0 && item.subtitle) {
|
|
var subtitleScore = calculateTextScore(item.subtitle.toLowerCase(), q);
|
|
textScore = subtitleScore * 0.5;
|
|
}
|
|
|
|
if (textScore === 0 && item.keywords) {
|
|
for (var i = 0; i < item.keywords.length; i++) {
|
|
var keywordScore = calculateTextScore(item.keywords[i].toLowerCase(), q);
|
|
if (keywordScore > 0) {
|
|
textScore = keywordScore * 0.3;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (textScore === 0 && item.data && item.data.genericName) {
|
|
var genericLower = item.data.genericName.toLowerCase();
|
|
if (genericLower.startsWith(q))
|
|
textScore = Weights.genericPrefix;
|
|
else if (genericLower.includes(q))
|
|
textScore = Weights.generic;
|
|
}
|
|
|
|
if (textScore === 0 && item.id && item.type === "app") {
|
|
var id = item.id.toLowerCase().replace(/\.desktop$/, "");
|
|
if (id.includes(q)) {
|
|
textScore = Weights.id;
|
|
}
|
|
}
|
|
|
|
if (textScore === 0)
|
|
return 0;
|
|
|
|
var usageBonus = frecencyData ? Math.min(frecencyData.usageCount * 50, Weights.frecency) : 0;
|
|
|
|
return textScore + usageBonus + typeBonus;
|
|
}
|
|
|
|
function scoreItems(items, query, getFrecencyFn) {
|
|
var scored = [];
|
|
|
|
for (var i = 0; i < items.length; i++) {
|
|
var item = items[i];
|
|
var itemScore;
|
|
|
|
if (item._preScored !== undefined && (query || item._preScored > 900)) {
|
|
itemScore = item._preScored;
|
|
} else {
|
|
var frecencyData = getFrecencyFn ? getFrecencyFn(item) : null;
|
|
itemScore = score(item, query, frecencyData);
|
|
}
|
|
|
|
if (itemScore > 0 || !query || query.length === 0) {
|
|
scored.push({
|
|
item: item,
|
|
score: itemScore,
|
|
order: scored.length
|
|
});
|
|
}
|
|
}
|
|
|
|
scored.sort(function (a, b) {
|
|
if (a.score !== b.score)
|
|
return b.score - a.score;
|
|
return a.order - b.order;
|
|
});
|
|
|
|
return scored;
|
|
}
|
|
|
|
function groupBySection(scoredItems, sectionOrder, sortAlphabetically, maxPerSection) {
|
|
var sections = {};
|
|
var result = [];
|
|
var limit = maxPerSection || 50;
|
|
|
|
for (var i = 0; i < sectionOrder.length; i++) {
|
|
var sectionId = sectionOrder[i].id;
|
|
sections[sectionId] = {
|
|
id: sectionId,
|
|
title: sectionOrder[i].title,
|
|
icon: sectionOrder[i].icon,
|
|
priority: sectionOrder[i].priority,
|
|
items: [],
|
|
collapsed: false,
|
|
flatStartIndex: 0
|
|
};
|
|
}
|
|
|
|
for (var i = 0; i < scoredItems.length; i++) {
|
|
var scoredItem = scoredItems[i];
|
|
var item = scoredItem.item;
|
|
var sectionId = item.section || "apps";
|
|
|
|
if (sections[sectionId] && sections[sectionId].items.length < limit) {
|
|
sections[sectionId].items.push(item);
|
|
} else if (sections["apps"] && sections["apps"].items.length < limit) {
|
|
sections["apps"].items.push(item);
|
|
}
|
|
}
|
|
|
|
for (var i = 0; i < sectionOrder.length; i++) {
|
|
var section = sections[sectionOrder[i].id];
|
|
if (section && section.items.length > 0) {
|
|
if (sortAlphabetically && section.id === "apps") {
|
|
section.items.sort(function (a, b) {
|
|
return (a.name || "").localeCompare(b.name || "");
|
|
});
|
|
}
|
|
result.push(section);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function flattenSections(sections) {
|
|
var flat = [];
|
|
flat._sectionBounds = null;
|
|
var bounds = {};
|
|
|
|
for (var i = 0; i < sections.length; i++) {
|
|
var section = sections[i];
|
|
|
|
flat.push({
|
|
isHeader: true,
|
|
section: section,
|
|
sectionId: section.id,
|
|
sectionIndex: i
|
|
});
|
|
|
|
var itemStart = flat.length;
|
|
section.flatStartIndex = itemStart;
|
|
|
|
if (!section.collapsed) {
|
|
for (var j = 0; j < section.items.length; j++) {
|
|
flat.push({
|
|
isHeader: false,
|
|
item: section.items[j],
|
|
sectionId: section.id,
|
|
sectionIndex: i,
|
|
indexInSection: j
|
|
});
|
|
}
|
|
}
|
|
|
|
var itemEnd = flat.length - 1;
|
|
var itemCount = flat.length - itemStart;
|
|
if (itemCount > 0) {
|
|
bounds[section.id] = {
|
|
start: itemStart,
|
|
end: itemEnd,
|
|
count: itemCount
|
|
};
|
|
}
|
|
}
|
|
|
|
flat._sectionBounds = bounds;
|
|
return flat;
|
|
}
|