Merge branch 'dev'
This commit is contained in:
commit
b97d018292
|
|
@ -241,6 +241,8 @@ spaces.get("/", async (c) => {
|
|||
seenSlugs.add(slug);
|
||||
|
||||
let vis = data.meta.visibility || "private";
|
||||
// Demo space is always public
|
||||
if (slug === "demo") vis = "public";
|
||||
// Check both claims.sub (raw userId) and did:key: format for
|
||||
// compatibility — auto-provisioned spaces store ownerDID as did:key:
|
||||
const callerDid = claims ? ((claims.did as string) || `did:key:${(claims.sub as string).slice(0, 32)}`) : "";
|
||||
|
|
@ -261,9 +263,7 @@ spaces.get("/", async (c) => {
|
|||
// For unauthenticated: only show demo
|
||||
if (!claims && slug !== "demo") continue;
|
||||
|
||||
// For authenticated: skip public spaces the user has no role in
|
||||
// (demo is shown separately, other public spaces are noise)
|
||||
if (claims && isPublicSpace && !isOwner && !isMember && slug !== "demo") continue;
|
||||
// Public spaces are shown in the PUBLIC section for all authenticated users
|
||||
|
||||
// Determine relationship
|
||||
const relationship = isOwner
|
||||
|
|
@ -290,6 +290,7 @@ spaces.get("/", async (c) => {
|
|||
accessible,
|
||||
relationship,
|
||||
pendingRequest: pendingRequest || undefined,
|
||||
isPersonal: !!(username && slug === username),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ interface SpaceInfo {
|
|||
accessible?: boolean;
|
||||
relationship?: "owner" | "member" | "demo" | "other";
|
||||
pendingRequest?: boolean;
|
||||
isPersonal?: boolean;
|
||||
}
|
||||
|
||||
export class RStackSpaceSwitcher extends HTMLElement {
|
||||
|
|
@ -141,10 +142,9 @@ export class RStackSpaceSwitcher extends HTMLElement {
|
|||
return { cls: "vis-public", label: "👁" };
|
||||
}
|
||||
|
||||
/** Format display name based on visibility type */
|
||||
/** Format display name — only rename the user's personal space */
|
||||
#displayName(s: SpaceInfo): string {
|
||||
const v = s.visibility || "public";
|
||||
if (v === "private") {
|
||||
if (s.isPersonal || s.slug === getUsername()?.toLowerCase()) {
|
||||
const username = getUsername();
|
||||
return username ? `${username}'s Space` : "My Space";
|
||||
}
|
||||
|
|
@ -168,130 +168,60 @@ export class RStackSpaceSwitcher extends HTMLElement {
|
|||
.join("");
|
||||
}
|
||||
|
||||
#demoSpaceHTML(current: string, moduleId: string): string {
|
||||
const isActive = current === "demo";
|
||||
return `
|
||||
<a class="item vis-public ${isActive ? "active" : ""}"
|
||||
href="${rspaceNavUrl("demo", moduleId)}">
|
||||
<span class="item-icon">🎮</span>
|
||||
<span class="item-name">Demo Space</span>
|
||||
<span class="item-vis vis-public">👁</span>
|
||||
</a>`;
|
||||
}
|
||||
|
||||
#renderMenu(menu: HTMLElement, current: string) {
|
||||
const auth = isAuthenticated();
|
||||
const moduleId = this.#getCurrentModule();
|
||||
|
||||
if (this.#spaces.length === 0) {
|
||||
let cta = "";
|
||||
if (!auth) {
|
||||
cta = this.#yourSpaceCTAhtml("Sign in to create →");
|
||||
} else {
|
||||
const username = getUsername();
|
||||
const label = username ? `Create ${username}'s Space →` : "Create My Space →";
|
||||
cta = this.#yourSpaceCTAhtml(label);
|
||||
}
|
||||
menu.innerHTML = `
|
||||
${this.#demoSpaceHTML(current, moduleId)}
|
||||
<div class="divider"></div>
|
||||
${cta}
|
||||
<div class="divider"></div>
|
||||
<div class="menu-empty">
|
||||
${auth ? "No spaces yet" : "Sign in to see your spaces"}
|
||||
</div>
|
||||
<button class="item item--create" id="create-toggle">+ Create new space</button>
|
||||
${this.#createFormHTML()}
|
||||
`;
|
||||
this.#attachYourSpaceCTA(menu);
|
||||
this.#attachCreatePopout(menu);
|
||||
return;
|
||||
}
|
||||
// Split spaces by visibility
|
||||
const privateSpaces = this.#spaces.filter((s) => s.visibility === "private");
|
||||
const permissionedSpaces = this.#spaces.filter((s) => s.visibility === "permissioned");
|
||||
const publicSpaces = this.#spaces.filter((s) => s.visibility === "public");
|
||||
|
||||
// Split spaces by visibility and role
|
||||
const mySpaces = this.#spaces.filter((s) => s.role);
|
||||
const discoverSpaces = this.#spaces.filter((s) => s.accessible === false);
|
||||
|
||||
const privateSpaces = mySpaces.filter((s) => s.visibility === "private");
|
||||
const permissionedSpaces = mySpaces.filter((s) => s.visibility === "permissioned");
|
||||
const publicSpaces = mySpaces.filter((s) => s.visibility === "public");
|
||||
|
||||
const hasOwnedSpace = mySpaces.some((s) => s.relationship === "owner");
|
||||
const hasOwnedSpace = this.#spaces.some((s) => s.relationship === "owner");
|
||||
|
||||
let html = "";
|
||||
|
||||
// ── Demo Space — always first ──
|
||||
html += this.#demoSpaceHTML(current, moduleId);
|
||||
html += `<div class="divider"></div>`;
|
||||
|
||||
// ── Create personal space CTA — only if user has no owned spaces ──
|
||||
// ── PRIVATE section ──
|
||||
html += `<div class="section-label section-label--private">Private</div>`;
|
||||
if (privateSpaces.length > 0) {
|
||||
html += this.#renderSpaceGroup(privateSpaces, current, moduleId);
|
||||
}
|
||||
if (!auth) {
|
||||
html += this.#yourSpaceCTAhtml("Sign in to create →");
|
||||
html += `<div class="divider"></div>`;
|
||||
} else if (!hasOwnedSpace) {
|
||||
const username = getUsername();
|
||||
const label = username ? `Create ${username}'s Space →` : "Create My Space →";
|
||||
html += this.#yourSpaceCTAhtml(label);
|
||||
html += `<div class="divider"></div>`;
|
||||
}
|
||||
|
||||
// ── Private spaces (red) — top ──
|
||||
if (privateSpaces.length > 0) {
|
||||
html += `<div class="section-label section-label--private">Private</div>`;
|
||||
html += this.#renderSpaceGroup(privateSpaces, current, moduleId);
|
||||
}
|
||||
|
||||
// ── Permissioned spaces (yellow) — middle ──
|
||||
// ── PERMISSIONED section ──
|
||||
if (permissionedSpaces.length > 0) {
|
||||
if (privateSpaces.length > 0) html += `<div class="divider"></div>`;
|
||||
html += `<div class="divider"></div>`;
|
||||
html += `<div class="section-label section-label--permissioned">Permissioned</div>`;
|
||||
html += this.#renderSpaceGroup(permissionedSpaces, current, moduleId);
|
||||
}
|
||||
|
||||
// ── Public spaces (green) — bottom ──
|
||||
if (publicSpaces.length > 0) {
|
||||
if (privateSpaces.length > 0 || permissionedSpaces.length > 0) html += `<div class="divider"></div>`;
|
||||
html += `<div class="section-label section-label--public">Public</div>`;
|
||||
html += this.#renderSpaceGroup(publicSpaces, current, moduleId);
|
||||
}
|
||||
|
||||
// ── Discover (permissioned spaces the user can request access to) ──
|
||||
if (auth && discoverSpaces.length > 0) {
|
||||
// ── PUBLIC section ──
|
||||
html += `<div class="divider"></div>`;
|
||||
html += `<div class="section-label">Discover</div>`;
|
||||
html += discoverSpaces
|
||||
.map((s) => {
|
||||
const vis = this.#visibilityInfo(s);
|
||||
const pending = s.pendingRequest;
|
||||
return `
|
||||
<div class="item item--discover ${vis.cls}">
|
||||
<span class="item-icon">${s.icon || "🌐"}</span>
|
||||
<span class="item-name">${this.#displayName(s)}</span>
|
||||
<span class="item-vis ${vis.cls}">${vis.label}</span>
|
||||
${pending
|
||||
? `<span class="item-badge item-badge--pending">Requested</span>`
|
||||
: `<button class="item-request-btn" data-slug="${s.slug}" data-name="${s.name.replace(/"/g, """)}">Request Access</button>`
|
||||
}
|
||||
</div>`;
|
||||
})
|
||||
.join("");
|
||||
html += `<div class="section-label section-label--public">Public</div>`;
|
||||
if (publicSpaces.length > 0) {
|
||||
html += this.#renderSpaceGroup(publicSpaces, current, moduleId);
|
||||
} else {
|
||||
html += `<div class="menu-empty">No public spaces</div>`;
|
||||
}
|
||||
|
||||
// ── *Discover section ──
|
||||
html += `<div class="divider"></div>`;
|
||||
html += `<div class="section-label section-label--discover">✦ Discover</div>`;
|
||||
html += `<a class="item item--discover-link" href="/discover"><span class="item-name">Explore spaces →</span></a>`;
|
||||
|
||||
// ── Create new space ──
|
||||
html += `<div class="divider"></div>`;
|
||||
html += `<button class="item item--create" id="create-toggle">+ Create new space</button>`;
|
||||
html += this.#createFormHTML();
|
||||
|
||||
menu.innerHTML = html;
|
||||
|
||||
// Attach Request Access button listeners
|
||||
menu.querySelectorAll(".item-request-btn").forEach((btn) => {
|
||||
btn.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
const el = btn as HTMLElement;
|
||||
this.#showRequestAccessModal(el.dataset.slug!, el.dataset.name!);
|
||||
});
|
||||
});
|
||||
|
||||
// Intercept space link clicks — dispatch space-switch event for client-side switching
|
||||
menu.querySelectorAll("a.item[href]").forEach((link) => {
|
||||
link.addEventListener("click", (e) => {
|
||||
|
|
@ -1176,6 +1106,9 @@ const STYLES = `
|
|||
.section-label--private { color: #f87171; opacity: 0.85; }
|
||||
.section-label--permissioned { color: #fbbf24; opacity: 0.85; }
|
||||
.section-label--public { color: #34d399; opacity: 0.85; }
|
||||
.section-label--discover { color: #a78bfa; opacity: 0.85; }
|
||||
.item--discover-link { font-size: 0.85rem; color: #a78bfa; text-decoration: none; }
|
||||
.item--discover-link:hover { background: rgba(167,139,250,0.08); }
|
||||
|
||||
.item {
|
||||
display: flex; align-items: center; gap: 10px;
|
||||
|
|
|
|||
Loading…
Reference in New Issue