@uniweb/kit 0.9.43 → 0.9.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -41,10 +41,43 @@ const DEFAULT_FUSE_OPTIONS = {
|
|
|
41
41
|
],
|
|
42
42
|
threshold: 0.35,
|
|
43
43
|
includeMatches: true,
|
|
44
|
+
// Exposed so ranking is inspectable, and so a foundation that wants its own
|
|
45
|
+
// ordering has something to order by. Fuse omits `score` entirely without it,
|
|
46
|
+
// which is why nothing could be ranked on relevance before.
|
|
47
|
+
includeScore: true,
|
|
44
48
|
ignoreLocation: true,
|
|
45
49
|
minMatchCharLength: 2
|
|
46
50
|
}
|
|
47
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Does this entry literally contain every word of the query?
|
|
54
|
+
*
|
|
55
|
+
* Fuse is approximate by design, and on fields the size of a whole page that
|
|
56
|
+
* turns into a real problem: searching "inset" on a documentation site
|
|
57
|
+
* returned 68 hits of which 4 contained the word, and all four ranked below
|
|
58
|
+
* the tenth result — so every result the reader saw was a near-miss on a page
|
|
59
|
+
* that never mentions the term.
|
|
60
|
+
*
|
|
61
|
+
* Fuzzy is the right FALLBACK — it is what tolerates a typo — but it must not
|
|
62
|
+
* outrank an exact match. Checking containment directly is the cheap, honest
|
|
63
|
+
* signal Fuse's score does not provide here.
|
|
64
|
+
*
|
|
65
|
+
* Every token must appear, so "Inset Components" does not match a page that
|
|
66
|
+
* merely says "components". Matching is substring-based rather than
|
|
67
|
+
* word-boundary so that "inset" still finds "insets".
|
|
68
|
+
*/
|
|
69
|
+
function literalTier(item, tokens) {
|
|
70
|
+
if (!tokens.length) return 2
|
|
71
|
+
|
|
72
|
+
const title = `${item.title || ''} ${item.pageTitle || ''}`.toLowerCase()
|
|
73
|
+
if (tokens.every((t) => title.includes(t))) return 0
|
|
74
|
+
|
|
75
|
+
const body = `${title} ${item.content || ''} ${item.excerpt || ''}`.toLowerCase()
|
|
76
|
+
if (tokens.every((t) => body.includes(t))) return 1
|
|
77
|
+
|
|
78
|
+
return 2
|
|
79
|
+
}
|
|
80
|
+
|
|
48
81
|
/**
|
|
49
82
|
* Get localStorage safely (handles SSR and access errors)
|
|
50
83
|
* @returns {Storage|null}
|
|
@@ -292,6 +325,23 @@ export function createIndexProvider(website, options = {}) {
|
|
|
292
325
|
results = results.filter(({ item }) => item.route?.startsWith(route))
|
|
293
326
|
}
|
|
294
327
|
|
|
328
|
+
// Rank pages that actually contain the words above Fuse's near-misses.
|
|
329
|
+
//
|
|
330
|
+
// Fuse sorts by its own score, which on page-sized content fields rates
|
|
331
|
+
// an approximate match as highly as an exact one — so a page containing
|
|
332
|
+
// the search term could sit below ten pages that never mention it, and
|
|
333
|
+
// the reader concludes the site has nothing on the subject.
|
|
334
|
+
//
|
|
335
|
+
// A stable sort by tier keeps Fuse's ordering *within* each tier, so
|
|
336
|
+
// relevance still decides among equals and the fuzzy tail is preserved
|
|
337
|
+
// rather than discarded. Applied before `limit`, because the cutoff is
|
|
338
|
+
// exactly where the problem showed up.
|
|
339
|
+
const tokens = String(text || '').toLowerCase().split(/\s+/).filter(Boolean)
|
|
340
|
+
results = results
|
|
341
|
+
.map((r, i) => ({ r, i, tier: literalTier(r.item, tokens) }))
|
|
342
|
+
.sort((a, b) => a.tier - b.tier || a.i - b.i)
|
|
343
|
+
.map(({ r }) => r)
|
|
344
|
+
|
|
295
345
|
return results.slice(0, limit).map(({ item, matches }) => {
|
|
296
346
|
const snippet = buildSnippet(item.content, matches, { key: 'content' })
|
|
297
347
|
|