mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-11 18:01:14 +00:00
Show English context for Speed Dial search matches
Display English action names and breadcrumbs for English-only matches Show English tab titles when tabs match only by their English names
This commit is contained in:
@@ -112,7 +112,7 @@ var activeCompletion = null;
|
||||
// - *_en: English counterparts of the translated fields, sent by C++ only when they differ from the
|
||||
// displayed text (a non-English UI), so the page can still be searched in English. Each carries the
|
||||
// weight of the localized field it mirrors, so a match ranks the same in either language. Search-only,
|
||||
// except title_en, whose ranges drive the row's English hint.
|
||||
// except their ranges, which drive the row's English hint (see ENGLISH_HINT_FIELDS).
|
||||
var SEARCH_FIELDS = [
|
||||
{ text: function (a) { return a.title; }, weight: SCORE_TITLE, range: "title" },
|
||||
{ text: function (a) { return a.group; }, weight: SCORE_GROUP, range: "group" },
|
||||
@@ -121,12 +121,48 @@ var SEARCH_FIELDS = [
|
||||
{ text: function (a) { return a.kind === "plugin" ? T("sd_plugins", "Plugins") : ""; }, weight: SCORE_GROUP, range: "plugin" },
|
||||
{ text: function (a) { return a.kind === "plugin" ? "plugin plugins" : ""; }, weight: SCORE_GROUP, range: null },
|
||||
{ text: function (a) { return a.title_en; }, weight: SCORE_TITLE, range: "title_en" },
|
||||
{ text: function (a) { return a.group_en; }, weight: SCORE_GROUP, range: null },
|
||||
{ text: function (a) { return a.source_en; }, weight: 0, range: null },
|
||||
{ text: function (a) { return a.full_label_en; }, weight: 0, range: null }
|
||||
{ text: function (a) { return a.group_en; }, weight: SCORE_GROUP, range: "group_en" },
|
||||
{ text: function (a) { return a.source_en; }, weight: 0, range: "source_en" },
|
||||
{ text: function (a) { return a.full_label_en; }, weight: 0, range: "full_label_en" }
|
||||
];
|
||||
var MATCH_RANGE_KEYS = SEARCH_FIELDS.map(function (f) { return f.range; }).filter(Boolean);
|
||||
|
||||
// English aliases a row can match through. `range` names the matchIndex key matchFields fills for that
|
||||
// field; the hint shows the action's English name (title_en), falling back to whichever alias matched.
|
||||
var ENGLISH_HINT_FIELDS = [
|
||||
{ range: "title_en", text: function (a) { return a.title_en; } },
|
||||
{ range: "group_en", text: function (a) { return a.group_en; } },
|
||||
{ range: "source_en", text: function (a) { return a.source_en; } },
|
||||
{ range: "full_label_en", text: function (a) { return a.full_label_en; } }
|
||||
];
|
||||
|
||||
// The English alias to show as a muted hint on an action row, or null. Only a row whose match came
|
||||
// through an English alias gets one (a visible translated highlight, or a local-only alias, needs
|
||||
// none). The hint is the action's English NAME - highlighted when the name itself matched, otherwise
|
||||
// shown plain - so the row identifies the action instead of repeating its translated eyebrow: a
|
||||
// "Process : Speed" breadcrumb match still names the setting ("Inner wall"), not the breadcrumb again.
|
||||
function englishHint(a, mi, visibleMatch) {
|
||||
if (!mi || visibleMatch) return null;
|
||||
var matched = null;
|
||||
for (var i = 0; i < ENGLISH_HINT_FIELDS.length; i++) {
|
||||
if (mi[ENGLISH_HINT_FIELDS[i].range]) { matched = ENGLISH_HINT_FIELDS[i]; break; }
|
||||
}
|
||||
if (!matched) return null;
|
||||
if (a.title_en) return { text: a.title_en, ranges: mi.title_en || null };
|
||||
var text = matched.text(a);
|
||||
return text ? { text: text, ranges: mi[matched.range] } : null;
|
||||
}
|
||||
|
||||
// The English breadcrumb a row matched through (a command's group, a setting's source path), with its
|
||||
// highlight ranges, or null when neither matched. Shown beside the translated eyebrow so an English
|
||||
// hit is visible where it landed, alongside the English name hint on the row-name line.
|
||||
function englishBreadcrumb(a, mi) {
|
||||
if (!mi) return null;
|
||||
if (mi.group_en && a.group_en) return { text: a.group_en, ranges: mi.group_en };
|
||||
if (mi.source_en && a.source_en) return { text: a.source_en, ranges: mi.source_en };
|
||||
return null;
|
||||
}
|
||||
|
||||
// The action's non-empty SEARCH_FIELDS as pre-normalized haystacks ({norm, weight, range}), cached on
|
||||
// the action object. The action objects arrive from C++ and are stable for the dialog's lifetime, so
|
||||
// this runs once per action; skipping empty fields keeps the per-keystroke scan to the text an action
|
||||
@@ -417,6 +453,18 @@ function filterTabs(tabs, query) {
|
||||
});
|
||||
}
|
||||
|
||||
// The English title of a tab matched only through it, with its highlight ranges, or null when the tab
|
||||
// matched its translated title or id (so it already shows why) or has no English title.
|
||||
function tabAliasHint(t, query) {
|
||||
var q = (query || "").trim();
|
||||
if (!q || !t || !t.title_en)
|
||||
return null;
|
||||
if (FuzzyRanges(tabTitle(t), q, false) || FuzzyRanges(t.id, q, false))
|
||||
return null;
|
||||
var ranges = FuzzyRanges(t.title_en, q, false);
|
||||
return ranges ? { text: t.title_en, ranges: ranges } : null;
|
||||
}
|
||||
|
||||
function shouldRenderActionList(query) {
|
||||
return !!((query || "").trim());
|
||||
}
|
||||
@@ -951,12 +999,21 @@ function renderActionRow(a, i) {
|
||||
var showPluginCategory = a.kind === "plugin" && (typedQuery || isRecent);
|
||||
var eyebrow = actionEyebrow(a, query, isRecent);
|
||||
var eyebrowMatch = mi ? (showPluginCategory ? mi.plugin : (mi.useEyebrowGroup ? mi.group : mi.source)) : null;
|
||||
shell.left.insertBefore(markedText("row-eyebrow", eyebrow, eyebrowMatch), shell.line);
|
||||
// The eyebrow line: the translated breadcrumb, and beside it the English breadcrumb when the query
|
||||
// matched that (non-English UI), so an English hit on a breadcrumb is visible where it landed.
|
||||
var eyebrowLine = document.createElement("div");
|
||||
eyebrowLine.className = "row-eyebrow-line";
|
||||
eyebrowLine.appendChild(markedText("row-eyebrow", eyebrow, eyebrowMatch));
|
||||
var enBreadcrumb = englishBreadcrumb(a, mi);
|
||||
if (enBreadcrumb)
|
||||
eyebrowLine.appendChild(markedText("row-eyebrow-en", enBreadcrumb.text, enBreadcrumb.ranges));
|
||||
shell.left.insertBefore(eyebrowLine, shell.line);
|
||||
shell.line.appendChild(markedText("row-name", a.title, mi ? mi.title : null));
|
||||
// A row found only through its English title shows that title, highlighted, so the match is
|
||||
// explained even when nothing in the translated name resembles the query.
|
||||
if (mi && !mi.title && mi.title_en)
|
||||
shell.line.appendChild(markedText("row-alias", a.title_en, mi.title_en));
|
||||
// A row found only through an English alias shows the action's English name, highlighted when the
|
||||
// name itself matched, so the match makes sense even when the translated row resembles nothing.
|
||||
var hint = englishHint(a, mi, mi ? (mi.title || eyebrowMatch) : null);
|
||||
if (hint)
|
||||
shell.line.appendChild(markedText("row-alias", hint.text, hint.ranges));
|
||||
var badge = modeBadge(a, USER_MODE);
|
||||
if (badge) {
|
||||
var tag = document.createElement("span");
|
||||
@@ -1134,6 +1191,11 @@ function renderTabRow(t, i) {
|
||||
name.className = "row-name";
|
||||
name.textContent = label;
|
||||
shell.line.appendChild(name);
|
||||
// A tab found only through its English title shows that title, highlighted, so the match is
|
||||
// explained even when the translated label looks nothing like the query.
|
||||
var hint = tabAliasHint(t, query);
|
||||
if (hint)
|
||||
shell.line.appendChild(markedText("row-alias", hint.text, hint.ranges));
|
||||
shell.row.onclick = function () { sel = { zone: "list", i: i }; render({ resize: true }); };
|
||||
shell.row.ondblclick = function () { sel = { zone: "list", i: i }; jumpToTab(t); };
|
||||
return shell.row;
|
||||
|
||||
@@ -59,6 +59,16 @@ assert.deepEqual(ctx.filterTabs(localizedTabs, "device").map(function (t) { retu
|
||||
"a localized tab matches its English title");
|
||||
assert.deepEqual(ctx.filterTabs(localizedTabs, "设备").map(function (t) { return t.id; }), ["monitor"],
|
||||
"a localized tab still matches its translated title");
|
||||
// A tab found only through its English title shows it as a hint; one that matched its translated
|
||||
// title or id already shows why, and one without an English title has nothing to show.
|
||||
assert.deepEqual(ctx.tabAliasHint(localizedTabs[0], "device"), { text: "Device", ranges: [[0, 6]] },
|
||||
"an English-only tab match yields the English title and its highlight ranges");
|
||||
assert.equal(ctx.tabAliasHint(localizedTabs[0], "设备"), null,
|
||||
"a translated-title tab match draws no English hint");
|
||||
assert.equal(ctx.tabAliasHint(localizedTabs[0], "monitor"), null,
|
||||
"a tab matched by its id draws no English hint");
|
||||
assert.equal(ctx.tabAliasHint(localizedTabs[1], "Device"), null,
|
||||
"a tab with no English title has no hint");
|
||||
|
||||
// tabTitle: pages added with an empty title (e.g. MainFrame's Home tab) fall back to the id.
|
||||
assert.equal(ctx.tabTitle({ id: "home", title: "" }), "Home",
|
||||
@@ -242,6 +252,47 @@ assert.equal(ctx.completionFor("reverse o", ctx.searchActions(zhPool, "reverse o
|
||||
"completion extends an English word from title_en");
|
||||
assert.equal(ctx.completionFor("quality overh", ctx.searchActions(zhPool, "quality overh")).suffix, "angs",
|
||||
"completion also reaches the English breadcrumb");
|
||||
// A row matched only through an English group/source/full_label alias still names the action in
|
||||
// English: the hint is title_en (the English name), not the alias that matched, so a breadcrumb hit
|
||||
// does not just repeat the translated eyebrow.
|
||||
ctx.searchActions(zhPool, "export");
|
||||
assert.deepEqual(ctx.matchIndex.slice.group_en, [[8, 14]], "the English group keeps its ranges");
|
||||
assert.deepEqual(ctx.englishHint(zhPool[1], ctx.matchIndex.slice, null), { text: "Slice and Preview", ranges: null },
|
||||
"an English group match names the action, unhighlighted");
|
||||
assert.deepEqual(ctx.englishBreadcrumb(zhPool[1], ctx.matchIndex.slice), { text: "Slice & Export", ranges: [[8, 14]] },
|
||||
"an English group match shows the English breadcrumb beside the eyebrow");
|
||||
ctx.searchActions(zhPool, "overhang reversal");
|
||||
assert.deepEqual(ctx.matchIndex.rev.full_label_en, [[0, 17]], "the English full_label keeps its ranges");
|
||||
assert.deepEqual(ctx.englishHint(zhPool[0], ctx.matchIndex.rev, null), { text: "Reverse on even", ranges: null },
|
||||
"an English full_label match names the action, unhighlighted");
|
||||
ctx.searchActions(zhPool, "quality");
|
||||
assert.deepEqual(ctx.matchIndex.rev.source_en, [[10, 17]], "the English breadcrumb keeps its ranges");
|
||||
assert.deepEqual(ctx.englishHint(zhPool[0], ctx.matchIndex.rev, null), { text: "Reverse on even", ranges: null },
|
||||
"an English breadcrumb match names the action, not the breadcrumb");
|
||||
assert.deepEqual(ctx.englishBreadcrumb(zhPool[0], ctx.matchIndex.rev), { text: "Process : Quality : Overhangs", ranges: [[10, 17]] },
|
||||
"an English breadcrumb match shows the English breadcrumb beside the eyebrow");
|
||||
// The name itself matching is the one case that highlights the hint.
|
||||
ctx.searchActions(zhPool, "reverse on even");
|
||||
assert.deepEqual(ctx.englishHint(zhPool[0], ctx.matchIndex.rev, null), { text: "Reverse on even", ranges: [[0, 15]] },
|
||||
"an English title match names and highlights the action");
|
||||
assert.equal(ctx.englishBreadcrumb(zhPool[0], ctx.matchIndex.rev), null,
|
||||
"an English title match shows no breadcrumb hint");
|
||||
// A breadcrumb match with no English name falls back to the matched alias.
|
||||
assert.deepEqual(ctx.englishHint({ title_en: "", source_en: "Process : Speed" }, { source_en: [[10, 15]] }, null),
|
||||
{ text: "Process : Speed", ranges: [[10, 15]] }, "without an English name the matched alias is shown");
|
||||
// A visible translated highlight suppresses the hint even when an English alias also matched.
|
||||
ctx.searchActions(zhPool, "brim");
|
||||
assert.equal(ctx.englishHint(zhPool[2], ctx.matchIndex.brim, ctx.matchIndex.brim.title), null,
|
||||
"a row that highlights its translated title draws no English hint");
|
||||
// The reported case: "process speed" matches only the English breadcrumb of "Inner wall", so the hint
|
||||
// names the setting rather than echoing the breadcrumb.
|
||||
const zhInner = [{ id: "inner", title: "内壁", title_en: "Inner wall", source: "工艺 : 速度",
|
||||
source_en: "Process : Speed", group: "", input: "" }];
|
||||
ctx.searchActions(zhInner, "process speed");
|
||||
assert.deepEqual(ctx.englishHint(zhInner[0], ctx.matchIndex.inner, null), { text: "Inner wall", ranges: null },
|
||||
"a breadcrumb-only match names the setting in English");
|
||||
assert.deepEqual(ctx.englishBreadcrumb(zhInner[0], ctx.matchIndex.inner), { text: "Process : Speed", ranges: [[0, 8], [10, 15]] },
|
||||
"a breadcrumb-only match shows the English breadcrumb beside the eyebrow");
|
||||
|
||||
// actionCategory: a command/dynamic action's group is its category; a setting uses the top-level
|
||||
// source segment; every plugin shares one header; a category-less action falls back to "Other".
|
||||
|
||||
@@ -390,7 +390,31 @@ body {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Eyebrow line: the translated breadcrumb, plus the English breadcrumb beside it when it matched. */
|
||||
.row-eyebrow-line {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.row-eyebrow {
|
||||
font-size: 10px;
|
||||
line-height: 1.3;
|
||||
color: var(--muted, var(--orca-muted, #6b7280));
|
||||
opacity: .85;
|
||||
flex: 0 1 auto;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* English breadcrumb of a row found only through it (non-English UI); gives way to the translation. */
|
||||
.row-eyebrow-en {
|
||||
flex: 0 100 auto;
|
||||
min-width: 0;
|
||||
font-size: 10px;
|
||||
line-height: 1.3;
|
||||
color: var(--muted, var(--orca-muted, #6b7280));
|
||||
@@ -427,6 +451,7 @@ body {
|
||||
|
||||
.row-name mark,
|
||||
.row-eyebrow mark,
|
||||
.row-eyebrow-en mark,
|
||||
.row-alias mark {
|
||||
background: var(--plugin-status-warn-bg);
|
||||
color: var(--plugin-status-warn);
|
||||
|
||||
Reference in New Issue
Block a user