feat(theseus): plug-in dock under the Theseus menu
Aegis sat among the extension buttons and drifted as add-ons were installed or removed. First-class Silent Mode components (manifest category "plugin") now get a labelled chip at the right end of the favorites row, directly below the Theseus menu — always visible, never collapsed into the extensions overflow. The add-on host tags each sidebar panel and toolbar menu with the category so the chrome can split the two docks; the extensions dock keeps its behaviour.
This commit is contained in:
parent
0da15818ad
commit
ed2d2e1d5a
2 changed files with 61 additions and 7 deletions
|
|
@ -687,9 +687,15 @@ class AddonHost {
|
||||||
toolbarMenus: this.getToolbarMenus(),
|
toolbarMenus: this.getToolbarMenus(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
// `plugin` marks surfaces of a first-class Silent Mode component (manifest
|
||||||
|
// category "plugin", e.g. Aegis): the chrome pins those to their own dock
|
||||||
|
// instead of the extensions row.
|
||||||
getSidebarPanels() {
|
getSidebarPanels() {
|
||||||
const out = [];
|
const out = [];
|
||||||
for (const active of this._active.values()) out.push(...active.sidebarPanels);
|
for (const active of this._active.values()) {
|
||||||
|
const plugin = active.manifest.category === "plugin";
|
||||||
|
for (const p of active.sidebarPanels) out.push({ ...p, plugin, addonName: active.manifest.name });
|
||||||
|
}
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
// Menu declarations from every active add-on that carries a toolbar-menu
|
// Menu declarations from every active add-on that carries a toolbar-menu
|
||||||
|
|
@ -704,6 +710,7 @@ class AddonHost {
|
||||||
addonId: active.manifest.id,
|
addonId: active.manifest.id,
|
||||||
title: tm.title,
|
title: tm.title,
|
||||||
icon: tm.icon,
|
icon: tm.icon,
|
||||||
|
plugin: active.manifest.category === "plugin",
|
||||||
items: tm.items.map((it) => ({ id: it.id, label: it.label, icon: it.icon })),
|
items: tm.items.map((it) => ({ id: it.id, label: it.label, icon: it.icon })),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
59
chrome.html
59
chrome.html
|
|
@ -345,6 +345,17 @@
|
||||||
uniform fixed width so the row reads as a neat grid regardless of the
|
uniform fixed width so the row reads as a neat grid regardless of the
|
||||||
underlying title length; the strip is 28px tall to fit the roomier chips. */
|
underlying title length; the strip is 28px tall to fit the roomier chips. */
|
||||||
.bookmarks { display: flex; align-items: center; gap: 6px; padding: 4px 10px 6px; height: 28px; overflow: hidden; }
|
.bookmarks { display: flex; align-items: center; gap: 6px; padding: 4px 10px 6px; height: 28px; overflow: hidden; }
|
||||||
|
#bmlist { display: flex; align-items: center; gap: 6px; flex: 1 1 auto; min-width: 0; overflow: hidden; }
|
||||||
|
/* Plug-in dock: first-class Silent Mode components (Aegis) get a labelled
|
||||||
|
chip at the right end of the favorites row, directly under the Theseus
|
||||||
|
menu — always visible, never collapsed with the extensions. */
|
||||||
|
.plugdock { margin-left: auto; flex: none; display: flex; align-items: center; gap: 4px; padding-left: 8px; }
|
||||||
|
.plugbtn { display: inline-flex; align-items: center; gap: 6px; height: 22px; padding: 0 10px 0 8px; border-radius: 6px;
|
||||||
|
font: inherit; font-size: 12px; font-weight: 600; color: var(--mut); cursor: pointer; white-space: nowrap;
|
||||||
|
border: 1px solid var(--line); background: transparent; }
|
||||||
|
.plugbtn:hover { background: var(--line2); color: var(--ink); }
|
||||||
|
.plugbtn.active { background: rgb(from var(--acid) r g b / .12); color: var(--acid); border-color: color-mix(in srgb, var(--acid) 35%, transparent); }
|
||||||
|
.plugbtn img { display: block; }
|
||||||
/* Ctrl+F find-in-page bar. Sits below the address bar (above tabs body),
|
/* Ctrl+F find-in-page bar. Sits below the address bar (above tabs body),
|
||||||
~32px tall so it's part of the chrome view height like .tordisc / .bcnrbar.
|
~32px tall so it's part of the chrome view height like .tordisc / .bcnrbar.
|
||||||
syncHeight in the tabs handler picks it up automatically. */
|
syncHeight in the tabs handler picks it up automatically. */
|
||||||
|
|
@ -477,7 +488,7 @@
|
||||||
<button class="logo" id="logo" title="Settings"><span>⛓ Theseus</span><span class="gear">⚙</span></button>
|
<button class="logo" id="logo" title="Settings"><span>⛓ Theseus</span><span class="gear">⚙</span></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="loadbar" id="loadbar"></div>
|
<div class="loadbar" id="loadbar"></div>
|
||||||
<div class="bookmarks" id="bookmarks" hidden></div>
|
<div class="bookmarks" id="bookmarks" hidden><div id="bmlist"></div><span class="plugdock" id="plugdock" hidden></span></div>
|
||||||
<div class="findbar" id="findbar" hidden>
|
<div class="findbar" id="findbar" hidden>
|
||||||
<input id="findInput" type="text" placeholder="Find in page" spellcheck="false" autocomplete="off">
|
<input id="findInput" type="text" placeholder="Find in page" spellcheck="false" autocomplete="off">
|
||||||
<span class="findcount" id="findCount">–</span>
|
<span class="findcount" id="findCount">–</span>
|
||||||
|
|
@ -666,15 +677,21 @@
|
||||||
// an addon that declares a toolbar-menu both get one entry here. panels
|
// an addon that declares a toolbar-menu both get one entry here. panels
|
||||||
// stay clickable-toggle; menus open a floating dropdown positioned under
|
// stay clickable-toggle; menus open a floating dropdown positioned under
|
||||||
// the icon and dispatch the picked item back to the add-on.
|
// the icon and dispatch the picked item back to the add-on.
|
||||||
let lastPanels = [], lastMenus = [], lastButtons = [];
|
let lastPanels = [], lastMenus = [], lastButtons = [], lastAllPanels = [], lastAllMenus = [];
|
||||||
let sidebarOpen = false, activePanelId = null;
|
let sidebarOpen = false, activePanelId = null;
|
||||||
let openMenuAddonId = null; // which addon's menu dropdown is currently open
|
let openMenuAddonId = null; // which addon's menu dropdown is currently open
|
||||||
function renderSidebarState(d) {
|
function renderSidebarState(d) {
|
||||||
if (!d) return;
|
if (!d) return;
|
||||||
lastPanels = Array.isArray(d.panels) ? d.panels : [];
|
const allPanels = Array.isArray(d.panels) ? d.panels : [];
|
||||||
lastMenus = Array.isArray(d.toolbarMenus) ? d.toolbarMenus : [];
|
const allMenus = Array.isArray(d.toolbarMenus) ? d.toolbarMenus : [];
|
||||||
sidebarOpen = !!d.visible;
|
sidebarOpen = !!d.visible;
|
||||||
activePanelId = d.active || null;
|
activePanelId = d.active || null;
|
||||||
|
// Plug-ins (Aegis) live in their own dock on the favorites row, under
|
||||||
|
// the Theseus menu; everything else is an extension in the toolbar dock.
|
||||||
|
lastAllPanels = allPanels; lastAllMenus = allMenus;
|
||||||
|
lastPanels = allPanels.filter((p) => !p.plugin);
|
||||||
|
lastMenus = allMenus.filter((m) => !m.plugin);
|
||||||
|
renderPlugDock(allPanels.filter((p) => p.plugin), allMenus.filter((m) => m.plugin));
|
||||||
// Build the flat button list, panels first (matches shipped order) then
|
// Build the flat button list, panels first (matches shipped order) then
|
||||||
// toolbar-menu buttons in registration order.
|
// toolbar-menu buttons in registration order.
|
||||||
lastButtons = [];
|
lastButtons = [];
|
||||||
|
|
@ -724,6 +741,36 @@
|
||||||
$("extmore").classList.toggle("active", (sidebarOpen && !!activePanelId) || !!openMenuAddonId);
|
$("extmore").classList.toggle("active", (sidebarOpen && !!activePanelId) || !!openMenuAddonId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Plug-in dock (favorites row, right end). A chip per plug-in surface:
|
||||||
|
// icon + name, tinted while its sidebar panel or menu is open. Same
|
||||||
|
// actions as the toolbar dock buttons, never collapsed.
|
||||||
|
function renderPlugDock(panels, menus) {
|
||||||
|
const dock = $("plugdock");
|
||||||
|
const chips = [];
|
||||||
|
// Chip label is the plug-in's product name (e.g. "Aegis Wallet"), the
|
||||||
|
// panel title goes in the tooltip.
|
||||||
|
for (const p of panels) chips.push({ kind: "panel", panelId: p.panelId, icon: p.icon, title: p.title || "Plug-in", name: p.addonName || p.title || "Plug-in", active: sidebarOpen && p.panelId === activePanelId });
|
||||||
|
for (const m of menus) chips.push({ kind: "menu", addonId: m.addonId, icon: m.icon, title: m.title || m.addonId, name: m.title || m.addonId, active: openMenuAddonId === m.addonId });
|
||||||
|
dock.hidden = chips.length === 0;
|
||||||
|
dock.innerHTML = chips.map((c) => {
|
||||||
|
const dataAttr = c.kind === "panel" ? `data-panel="${c.panelId.replace(/"/g,""")}"` : `data-menu="${c.addonId.replace(/"/g,""")}"`;
|
||||||
|
const label = String(c.name).split(/\s+[—–-]\s+/, 1)[0].replace(/</g,"<");
|
||||||
|
return `<button class="plugbtn${c.active ? " active" : ""}" ${dataAttr} title="${c.title.replace(/"/g,""")}">${addonIconHtml(c.icon, 14)}<span>${label}</span></button>`;
|
||||||
|
}).join("");
|
||||||
|
dock.querySelectorAll(".plugbtn").forEach((btn) => {
|
||||||
|
btn.onclick = () => {
|
||||||
|
if (btn.dataset.panel) {
|
||||||
|
const pid = btn.dataset.panel;
|
||||||
|
closeToolbarMenu();
|
||||||
|
if (sidebarOpen && pid === activePanelId) { T.closeSidebar && T.closeSidebar(); }
|
||||||
|
else { T.openSidebar && T.openSidebar(pid); }
|
||||||
|
} else if (btn.dataset.menu) {
|
||||||
|
openToolbarMenu(btn.dataset.menu, btn);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// ---- toolbar-menu dropdown (per-addon) ----
|
// ---- toolbar-menu dropdown (per-addon) ----
|
||||||
// Positions under the clicked icon, styled after .extpop. Clicking an
|
// Positions under the clicked icon, styled after .extpop. Clicking an
|
||||||
// item dispatches to main via addonMenuSelect(addonId, itemId) and
|
// item dispatches to main via addonMenuSelect(addonId, itemId) and
|
||||||
|
|
@ -734,7 +781,7 @@
|
||||||
if (openMenuAddonId) {
|
if (openMenuAddonId) {
|
||||||
openMenuAddonId = null;
|
openMenuAddonId = null;
|
||||||
// Re-render dock so the "active" tint drops off the icon.
|
// Re-render dock so the "active" tint drops off the icon.
|
||||||
renderSidebarState({ visible: sidebarOpen, active: activePanelId, panels: lastPanels, toolbarMenus: lastMenus });
|
renderSidebarState({ visible: sidebarOpen, active: activePanelId, panels: lastAllPanels, toolbarMenus: lastAllMenus });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function openToolbarMenu(addonId, anchorBtn) {
|
function openToolbarMenu(addonId, anchorBtn) {
|
||||||
|
|
@ -933,7 +980,7 @@
|
||||||
return bookmarkBrandCase(cut);
|
return bookmarkBrandCase(cut);
|
||||||
}
|
}
|
||||||
function renderBookmarks() {
|
function renderBookmarks() {
|
||||||
const box = $("bookmarks");
|
const box = $("bmlist");
|
||||||
box.innerHTML = bookmarks.length
|
box.innerHTML = bookmarks.length
|
||||||
? bookmarks.map((b, i) => {
|
? bookmarks.map((b, i) => {
|
||||||
const fav = b.favicon
|
const fav = b.favicon
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue