mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-26 14:32:52 -05:00
246 lines
6.7 KiB
JavaScript
246 lines
6.7 KiB
JavaScript
.pragma library
|
|
|
|
const Weights = {
|
|
exactMatch: 10000,
|
|
prefixMatch: 5000,
|
|
wordBoundary: 1000,
|
|
substring: 500,
|
|
fuzzy: 100,
|
|
frecency: 2000,
|
|
typeBonus: {
|
|
app: 1000,
|
|
plugin: 900,
|
|
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 matrix = []
|
|
|
|
for (var i = 0; i <= len1; i++) {
|
|
matrix[i] = [i]
|
|
}
|
|
for (var j = 0; j <= len2; j++) {
|
|
matrix[0][j] = j
|
|
}
|
|
|
|
for (var i = 1; i <= len1; i++) {
|
|
for (var j = 1; j <= len2; j++) {
|
|
var cost = s1[i - 1] === s2[j - 1] ? 0 : 1
|
|
matrix[i][j] = Math.min(
|
|
matrix[i - 1][j] + 1,
|
|
matrix[i][j - 1] + 1,
|
|
matrix[i - 1][j - 1] + cost
|
|
)
|
|
}
|
|
}
|
|
return matrix[len1][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 getTimeBucketWeight(daysSinceUsed) {
|
|
for (var i = 0; i < TimeBuckets.length; i++) {
|
|
if (daysSinceUsed <= TimeBuckets[i].maxDays) {
|
|
return TimeBuckets[i].weight
|
|
}
|
|
}
|
|
return 10
|
|
}
|
|
|
|
function calculateTextScore(name, query) {
|
|
if (name === query) return Weights.exactMatch
|
|
if (name.startsWith(query)) return Weights.prefixMatch
|
|
if (name.includes(query)) return Weights.substring
|
|
if (hasWordBoundaryMatch(name, query)) return Weights.wordBoundary
|
|
|
|
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) return 0
|
|
|
|
var usageBonus = frecencyData ? Math.min(frecencyData.usageCount * 10, 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 frecencyData = getFrecencyFn ? getFrecencyFn(item) : null
|
|
var itemScore = score(item, query, frecencyData)
|
|
|
|
if (itemScore > 0 || !query || query.length === 0) {
|
|
scored.push({
|
|
item: item,
|
|
score: itemScore
|
|
})
|
|
}
|
|
}
|
|
|
|
scored.sort(function (a, b) {
|
|
return b.score - a.score
|
|
})
|
|
|
|
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
|
|
}
|
|
}
|
|
|
|
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 = []
|
|
|
|
for (var i = 0; i < sections.length; i++) {
|
|
var section = sections[i]
|
|
|
|
flat.push({
|
|
isHeader: true,
|
|
section: section,
|
|
sectionId: section.id,
|
|
sectionIndex: i
|
|
})
|
|
|
|
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
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
return flat
|
|
}
|