1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-04-03 20:32:07 -04:00
Files
Jon Rogers 3804d2f00b fix(Scorer): honour _preScored for no-query when value exceeds typeBonus (#2065)
Plugin items can set _preScored to signal a priority boost (e.g. recently
used items). Previously _preScored was only respected when a search query
was active, so no-query default lists always fell back to typeBonus+frecency
scoring, making plugin-controlled ordering impossible.

Change the condition from:
  if (query && item._preScored !== undefined)
to:
  if (item._preScored !== undefined && (query || item._preScored > 900))

This respects _preScored in no-query mode only when the value exceeds 900
(the plugin typeBonus), which avoids changing behaviour for "all" mode items
whose _preScored is set to 900-j by the controller (≤ 900). Items without
_preScored set continue to use the existing typeBonus + frecency formula.
2026-03-23 09:25:20 -04:00

266 lines
7.3 KiB
JavaScript

.pragma library
const Weights = {
exactMatch: 10000,
prefixMatch: 5000,
wordBoundary: 3000,
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 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 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 (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) 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
})
}
}
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,
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
}