Changeset 0.37.10 (#222)
Co-authored-by: gobha <jasafpro@gmail.com> Co-committed-by: gobha <jasafpro@gmail.com>
This commit is contained in:
@@ -4,9 +4,13 @@
|
||||
* RBAC-gated items based on sw.can() / sw.isAdmin / sw.isTeamAdmin().
|
||||
* Surfaces mount this wherever they want; the shell does not own placement.
|
||||
*
|
||||
* Surface links (Notes, extensions) are auto-included and the CURRENT
|
||||
* surface is filtered out so every surface gets the same menu minus itself.
|
||||
*
|
||||
* Props:
|
||||
* placement — Menu direction: 'down-right' (default) | 'down-left'
|
||||
* onAction — Override handler; if omitted, uses default routing
|
||||
* placement — Menu direction: 'down-right' (default) | 'up-right' etc.
|
||||
* onAction — Override handler; if omitted, navigates via location.href
|
||||
* extraItems — Additional menu items inserted after surface links
|
||||
*/
|
||||
const { html } = window;
|
||||
const { useState, useRef, useMemo } = hooks;
|
||||
@@ -14,7 +18,14 @@ const { useState, useRef, useMemo } = hooks;
|
||||
import { Avatar } from '../primitives/avatar.js';
|
||||
import { Menu } from '../primitives/menu.js';
|
||||
|
||||
export function UserMenu({ placement = 'down-right', onAction }) {
|
||||
/**
|
||||
* Detect current surface from body[data-surface].
|
||||
*/
|
||||
function _currentSurface() {
|
||||
return document.body?.dataset?.surface || '';
|
||||
}
|
||||
|
||||
export function UserMenu({ placement = 'down-right', onAction, extraItems }) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const btnRef = useRef(null);
|
||||
|
||||
@@ -23,18 +34,51 @@ export function UserMenu({ placement = 'down-right', onAction }) {
|
||||
const authenticated = sw?.auth?.isAuthenticated;
|
||||
|
||||
const items = useMemo(() => {
|
||||
const list = [
|
||||
{ label: 'Settings', action: 'settings', icon: '\u2699\ufe0f' },
|
||||
const current = _currentSurface();
|
||||
const list = [];
|
||||
|
||||
// ── Surface links (auto-filtered) ──────
|
||||
// Each surface that exists gets a menu item, except the current one.
|
||||
const surfaces = [
|
||||
{ key: 'chat', label: 'Chat', icon: '\ud83d\udcac' },
|
||||
{ key: 'notes', label: 'Notes', icon: '\ud83d\udcdd' },
|
||||
];
|
||||
|
||||
// Add extension surfaces from page data if available
|
||||
const extSurfaces = window.__EXTENSION_SURFACES__ || [];
|
||||
for (const ext of extSurfaces) {
|
||||
surfaces.push({ key: ext.id || ext.route, label: ext.title, icon: '\ud83e\udde9' });
|
||||
}
|
||||
|
||||
const surfaceItems = surfaces
|
||||
.filter(s => s.key !== current)
|
||||
.map(s => ({ label: s.label, action: s.key, icon: s.icon }));
|
||||
|
||||
if (surfaceItems.length) {
|
||||
list.push(...surfaceItems);
|
||||
list.push({ divider: true });
|
||||
}
|
||||
|
||||
// ── Extra items from caller ────────────
|
||||
if (extraItems && extraItems.length) {
|
||||
list.push(...extraItems);
|
||||
list.push({ divider: true });
|
||||
}
|
||||
|
||||
// ── Standard items ─────────────────────
|
||||
// Settings — skip if current surface IS settings
|
||||
if (current !== 'settings') {
|
||||
list.push({ label: 'Settings', action: 'settings', icon: '\u2699\ufe0f' });
|
||||
}
|
||||
|
||||
// Admin — requires admin role
|
||||
if (!authenticated || sw?.isAdmin) {
|
||||
if ((!authenticated || sw?.isAdmin) && current !== 'admin') {
|
||||
list.push({ label: 'Admin', action: 'admin', icon: '\ud83d\udee1\ufe0f' });
|
||||
}
|
||||
|
||||
// Team Admin — requires admin role on at least one team
|
||||
const hasTeamAdmin = sw?.auth?.teams?.some(t => t.my_role === 'admin');
|
||||
if (!authenticated || hasTeamAdmin) {
|
||||
if ((!authenticated || hasTeamAdmin) && current !== 'team-admin') {
|
||||
list.push({ label: 'Team Admin', action: 'team-admin', icon: '\ud83d\udc65' });
|
||||
}
|
||||
|
||||
@@ -47,15 +91,27 @@ export function UserMenu({ placement = 'down-right', onAction }) {
|
||||
list.push({ label: 'Sign Out', action: 'sign-out' });
|
||||
|
||||
return list;
|
||||
}, [user, authenticated]);
|
||||
}, [user, authenticated, extraItems]);
|
||||
|
||||
function handleSelect(action) {
|
||||
setOpen(false);
|
||||
if (onAction) return onAction(action);
|
||||
if (action === 'sign-out') {
|
||||
sw?.auth?.logout();
|
||||
} else {
|
||||
sw?.emit('shell.navigate', { target: action });
|
||||
|
||||
// Default routing
|
||||
const BASE = window.__BASE__ || '';
|
||||
switch (action) {
|
||||
case 'sign-out':
|
||||
window.sw?.auth?.logout();
|
||||
break;
|
||||
case 'debug':
|
||||
const dbg = document.getElementById('debugModal');
|
||||
if (dbg) dbg.classList.toggle('active');
|
||||
break;
|
||||
case 'chat':
|
||||
location.href = BASE + '/';
|
||||
break;
|
||||
default:
|
||||
location.href = BASE + '/' + action;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user