Fixes issue with showing hidden settings in speed dial.

This commit is contained in:
Lam Wei Lun
2026-09-16 16:02:39 +08:00
parent b01d18bba3
commit 075a84093f
10 changed files with 198 additions and 40 deletions
+21 -13
View File
@@ -114,6 +114,13 @@ function sourceNorm(a) {
a._sn = NormText(a.source || "", false);
return a._sn;
}
// Search-only alias for the descriptive name when the title differs (e.g. "Reverse on even" vs
// "Overhang reversal"). Never rendered, so no highlight ranges.
function fullNorm(a) {
if (a._fn === undefined)
a._fn = NormText(a.full_label || "", false);
return a._fn;
}
// Match one pre-normalized field vs one pre-normalized needle. Returns {score, ranges, contiguous}
// when the needle is present, else null. wwRe is a compiled whole-word (\b-bounded) regex for the
@@ -152,19 +159,19 @@ function tokenWordRe(token) {
return new RegExp("\\b" + EscapeRegExp(token) + "\\b");
}
// One token's best match across an action's three fields, keeping the per-field ranges so the caller
// can highlight each matched word. Returns {score, title, group, source} (ranges or null per field), or
// null when no field contains the token. Score mirrors scoreFields' tiers: contiguous > fuzzy, then
// title > group > source.
// One token's best match across an action's searchable fields, keeping per-field ranges for
// highlighting. Returns {score, title, group, source}, or null if no field matches.
function tokenMatch(a, token, wwRe) {
var t = fieldMatchScore(titleNorm(a), token, wwRe);
var g = fieldMatchScore(groupNorm(a), token, wwRe);
var s = fieldMatchScore(sourceNorm(a), token, wwRe);
if (!t && !g && !s) return null;
var f = fieldMatchScore(fullNorm(a), token, wwRe);
if (!t && !g && !s && !f) return null;
var score = Math.max(
t ? (t.contiguous ? SCORE_CONTIGUOUS : 0) + SCORE_TITLE + t.score : -Infinity,
g ? (g.contiguous ? SCORE_CONTIGUOUS : 0) + SCORE_GROUP + g.score : -Infinity,
s ? (s.contiguous ? SCORE_CONTIGUOUS : 0) + s.score : -Infinity
s ? (s.contiguous ? SCORE_CONTIGUOUS : 0) + s.score : -Infinity,
f ? (f.contiguous ? SCORE_CONTIGUOUS : 0) + f.score : -Infinity
);
return { score: score, title: t ? t.ranges : null, group: g ? g.ranges : null, source: s ? s.ranges : null };
}
@@ -189,11 +196,9 @@ function mergeRanges(ranges) {
return out;
}
// Combine the per-field match scores into one comparable value, or null when no field matched.
// Ranking tiers, strongest first:
// tier (contiguous/perfect vs fuzzy) > field (title > group > source) > start/gaps.
// The additive weights keep every contiguous match above every fuzzy one regardless of field.
function scoreFields(t, g, s) {
// Combine per-field scores into one value, or null when nothing matched.
// Ranking: contiguous > fuzzy, then title > group > source/full alias, then start/gaps.
function scoreFields(t, g, s, f) {
var best = null;
function consider(m, weight) {
if (!m) return;
@@ -203,6 +208,7 @@ function scoreFields(t, g, s) {
consider(t, SCORE_TITLE);
consider(g, SCORE_GROUP);
consider(s, 0);
consider(f, 0);
return best;
}
@@ -216,6 +222,7 @@ function scoreFields(t, g, s) {
// - tokens: every whitespace-separated word must match SOME field, but different words may match
// different fields. This is what lets "speed acceleration inner" find "Inner wall" whose path is
// "Process : Speed : Acceleration" (title + source breadcrumb together).
// full_label is searchable too but never highlighted, since it is not rendered.
// A phrase match always outranks a distributed token match.
function searchActions(actions, query) {
var q = (query || "").trim();
@@ -239,7 +246,8 @@ function searchActions(actions, query) {
var t = fieldMatchScore(titleNorm(a), searchNeedle, wwRe);
var g = fieldMatchScore(groupNorm(a), searchNeedle, wwRe);
var s = fieldMatchScore(sourceNorm(a), searchNeedle, wwRe);
var phrase = scoreFields(t, g, s);
var f = fieldMatchScore(fullNorm(a), searchNeedle, wwRe);
var phrase = scoreFields(t, g, s, f);
var score, ranges;
if (phrase !== null) {
score = phrase + SCORE_PHRASE;
@@ -312,7 +320,7 @@ function completionFor(query, list) {
var top = (list || []).slice(0, 10);
for (var i = 0; i < top.length; i++) {
var a = top[i];
var fields = [a.title, a.group, a.source];
var fields = [a.title, a.group, a.source, a.full_label];
for (var f = 0; f < fields.length; f++) {
var words = completionWords(fields[f]);
for (var w = 0; w < words.length; w++) {
@@ -117,6 +117,18 @@ const negativePool = [
assert.equal(ctx.searchActions(negativePool, "ornt").length, 1,
"a low-score fuzzy match is not mistaken for no match");
// A setting whose displayed title is the page row label keeps the descriptive ConfigOptionDef name as
// a search-only alias, so the old wording still finds it without being shown.
const aliasPool = [
{ id: "rev", title: "Reverse on even", full_label: "Overhang reversal", source: "Process : Quality : Overhangs", group: "", input: "" }
];
assert.deepEqual(ctx.searchActions(aliasPool, "overhang reversal").map(function (a) { return a.id; }), ["rev"],
"the descriptive full_label is searchable even though the title shows the row label");
assert.equal(ctx.matchIndex.rev.title, null,
"an alias-only match does not highlight the displayed title");
assert.deepEqual(ctx.searchActions(aliasPool, "reversal").map(function (a) { return a.id; }), ["rev"],
"a token that exists only in the full_label still matches");
// Multi-token cross-field search: each whitespace-separated word must match SOME searchable field,
// but different words may match different fields. "inner" is the title while "speed" and
// "acceleration" live in the source breadcrumb, so the query as a whole is never contiguous in one