Name the chrome's language menu and guard anchor positioning in the fallback
CI / full-gate (push) Successful in 2m44s

This commit is contained in:
2026-08-05 02:23:50 +02:00
parent cfeee10fa8
commit 9cf6c05325
10 changed files with 29 additions and 19 deletions
+1
View File
@@ -71,6 +71,7 @@
<% if ((row.actions || []).length) { -%>
<td class="col-actions"><%- include("menu", {
id: `${actionsId}-${i + 1}`,
align: null, up: false, width: null, // explicit: EJS would otherwise inherit the page's own
kebab: true,
trigger: { class: "", icon: "i-kebab", label: t("table.rowActions", { name: row.name || t("table.row") }) },
// Mapped field by field, not spread: an Action and a menu Item are separate shapes.
+4 -2
View File
@@ -3,12 +3,14 @@
so switching is a plain navigation — zero-JS, and the address bar always says which language the
page is in. Renders nothing for a single-language deployment.
Locals: localeSwitch (host-supplied: { current, href, label, tag }[]) · up? (open upward, default true)
id? (the menu's id; name a second picker on the same page)
localeMenuId? (the menu's id; name a second picker on the same page. Not `id` — EJS
merges page locals into every include, and `id` is the likeliest key in a plugin's data)
-%>
<% const choices = locals.localeSwitch || []; -%>
<% if (choices.length > 1) { -%>
<%- include("menu", {
id: locals.id || "locale-menu",
id: locals.localeMenuId || "locale-menu",
align: null, kebab: false, width: null, // explicit: EJS would otherwise inherit the page's own
up: locals.up !== false,
trigger: { class: "btn icon-btn", icon: "i-globe", label: t("locale.label") },
items: [
+2 -2
View File
@@ -26,7 +26,7 @@
const popCls = "menu-pop" + (locals.align === "left" ? " left" : "") + (locals.up ? " up" : "");
const width = locals.width;
-%>
<span class="menu"><button<% if (btnCls) { %> class="<%= btnCls %>"<% } %> type="button" popovertarget="<%= locals.id %>"<% if (trigger.label) { %> aria-label="<%= trigger.label %>"<% } %>><% if (trigger.html != null) { %><%- trigger.html %><% } else { if (trigger.icon) { %><svg class="ico ico-sm"><use href="#<%= trigger.icon %>"/></svg><% } if (trigger.text) { %><%= trigger.text %><% } } %></button><div id="<%= locals.id %>" class="<%= popCls %>" popover<% if (width != null) { %> style="min-width:<%= typeof width === "number" ? width + "px" : width %>"<% } %>>
<div class="menu"><button<% if (btnCls) { %> class="<%= btnCls %>"<% } %> type="button" popovertarget="<%= locals.id %>"<% if (trigger.label) { %> aria-label="<%= trigger.label %>"<% } %>><% if (trigger.html != null) { %><%- trigger.html %><% } else { if (trigger.icon) { %><svg class="ico ico-sm"><use href="#<%= trigger.icon %>"/></svg><% } if (trigger.text) { %><%= trigger.text %><% } } %></button><div id="<%= locals.id %>" class="<%= popCls %>" popover<% if (width != null) { %> style="min-width:<%= typeof width === "number" ? width + "px" : width %>"<% } %>>
<% items.forEach((it) => { -%>
<% if (it.head != null) { -%>
<div class="menu-head"><%= it.head %></div>
@@ -40,4 +40,4 @@
<button class="menu-item<%= it.danger ? " danger" : "" %>" type="button"><% if (it.icon) { %><svg class="ico"><use href="#<%= it.icon %>"/></svg><% } %><%= it.label %></button>
<% } -%>
<% }) -%>
</div></span>
</div></div>
+3 -3
View File
@@ -60,7 +60,7 @@
<%# Signed in: the same popover block as the menu partial, hand-rolled because this one's
trigger composes escaped user values and its item is a CSRF POST form, neither of which
the partial's Item shapes cover (AGENTS.md). Keep the two in step. %>
<span class="menu" style="flex:1 1 auto">
<div class="menu" style="flex:1 1 auto">
<button class="profile" type="button" popovertarget="profile-menu">
<span class="avatar" aria-hidden="true"><%= user.initials %></span>
<span class="profile-meta">
@@ -76,7 +76,7 @@
<button class="menu-item danger" type="submit"><svg class="ico"><use href="#i-logout" /></svg><%= t("shell.signOut") %></button>
</form>
</div>
</span>
</div>
<% } else if (!hideSignIn) { %>
<%# anonymous (a public page in the shell): no session to end — offer a way in instead.
signInHref carries this page as return_to (chrome.signInHref); falls back to bare /login.
@@ -84,7 +84,7 @@
<a class="btn btn-primary" href="<%= locals.signInHref || '/login' %>" style="flex:1 1 auto"><svg class="ico ico-sm" aria-hidden="true"><use href="#i-user" /></svg><%= t("shell.signIn") %></a>
<% } %>
<%- include("locale-switch") %>
<%- include("locale-switch", { localeMenuId: "chrome-locale-menu" }) %>
</div>
</div>
</aside>