feat(rsocials): hub navigation page, route renames, and campaigns listing
Restructure rSocials landing to a hub with navigation cards for Campaigns, Threads, and Thread Editor. Rename /thread routes to /thread-editor for clarity. Render campaigns listing inline instead of redirecting. Also improve rNotes notebook creation to auto-open first note for editing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
b8f32a863e
commit
6173e17bb9
|
|
@ -360,17 +360,31 @@ Gear: EUR 400 (10%)</code></pre><p><em>Maya is tracking expenses in rF
|
||||||
}
|
}
|
||||||
|
|
||||||
private demoCreateNotebook() {
|
private demoCreateNotebook() {
|
||||||
const title = prompt("Notebook name:");
|
|
||||||
if (!title?.trim()) return;
|
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
const nbId = `demo-nb-${now}`;
|
||||||
|
const noteId = `demo-note-${now}`;
|
||||||
|
const newNote: Note = {
|
||||||
|
id: noteId, title: "Untitled Note", content: "", content_plain: "",
|
||||||
|
content_format: 'tiptap-json',
|
||||||
|
type: "NOTE", tags: null, is_pinned: false,
|
||||||
|
created_at: new Date(now).toISOString(), updated_at: new Date(now).toISOString(),
|
||||||
|
};
|
||||||
const nb = {
|
const nb = {
|
||||||
id: `demo-nb-${now}`, title, description: "",
|
id: nbId, title: "Untitled Notebook", description: "",
|
||||||
cover_color: "#8b5cf6", note_count: "0",
|
cover_color: "#8b5cf6", note_count: "1",
|
||||||
updated_at: new Date(now).toISOString(), notes: [] as Note[],
|
updated_at: new Date(now).toISOString(), notes: [newNote],
|
||||||
} as any;
|
} as any;
|
||||||
this.demoNotebooks.push(nb);
|
this.demoNotebooks.push(nb);
|
||||||
this.notebooks = this.demoNotebooks.map(({ notes, ...rest }) => rest as Notebook);
|
this.notebooks = this.demoNotebooks.map(({ notes, ...rest }) => rest as Notebook);
|
||||||
|
this.selectedNotebook = { ...nb };
|
||||||
|
this.view = "notebook";
|
||||||
this.render();
|
this.render();
|
||||||
|
// Auto-open the note for editing
|
||||||
|
this.selectedNote = newNote;
|
||||||
|
this.view = "note";
|
||||||
|
this.renderNav();
|
||||||
|
this.renderMeta();
|
||||||
|
this.mountEditor(newNote);
|
||||||
}
|
}
|
||||||
|
|
||||||
private demoCreateNote() {
|
private demoCreateNote() {
|
||||||
|
|
|
||||||
|
|
@ -131,7 +131,7 @@ export class FolkCampaignManager extends HTMLElement {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<a href="/${this.esc(this._space)}/rsocials/thread" class="btn btn--outline">Open Thread Builder</a>
|
<a href="/${this.esc(this._space)}/rsocials/thread-editor" class="btn btn--outline">Open Thread Builder</a>
|
||||||
<button class="btn btn--primary" id="import-md-btn">Import from Markdown</button>
|
<button class="btn btn--primary" id="import-md-btn">Import from Markdown</button>
|
||||||
</div>
|
</div>
|
||||||
${phaseHTML}
|
${phaseHTML}
|
||||||
|
|
|
||||||
|
|
@ -820,7 +820,7 @@ class FolkCampaignPlanner extends HTMLElement {
|
||||||
} else if (action === 'open-thread') {
|
} else if (action === 'open-thread') {
|
||||||
const d = node.data as ThreadNodeData;
|
const d = node.data as ThreadNodeData;
|
||||||
if (d.threadId) {
|
if (d.threadId) {
|
||||||
window.location.href = `/${this.space}/rsocials/thread/${d.threadId}/edit`;
|
window.location.href = `/${this.space}/rsocials/thread-editor/${d.threadId}/edit`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -1457,7 +1457,7 @@ class FolkCampaignPlanner extends HTMLElement {
|
||||||
if (node?.type === 'thread') {
|
if (node?.type === 'thread') {
|
||||||
const d = node.data as ThreadNodeData;
|
const d = node.data as ThreadNodeData;
|
||||||
if (d.threadId) {
|
if (d.threadId) {
|
||||||
window.location.href = `/${this.space}/rsocials/thread/${d.threadId}/edit`;
|
window.location.href = `/${this.space}/rsocials/thread-editor/${d.threadId}/edit`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -274,7 +274,7 @@ export class FolkThreadBuilder extends HTMLElement {
|
||||||
</div>
|
</div>
|
||||||
<div class="preview ro-cards">${tweetCards}</div>
|
<div class="preview ro-cards">${tweetCards}</div>
|
||||||
<div class="ro-actions">
|
<div class="ro-actions">
|
||||||
<a href="/${this.esc(this._space)}/rsocials/thread/${this.esc(t.id)}/edit" class="btn btn--primary">Edit Thread</a>
|
<a href="/${this.esc(this._space)}/rsocials/thread-editor/${this.esc(t.id)}/edit" class="btn btn--primary">Edit Thread</a>
|
||||||
<button class="btn btn--outline" id="ro-copy-thread">Copy Thread</button>
|
<button class="btn btn--outline" id="ro-copy-thread">Copy Thread</button>
|
||||||
<button class="btn btn--outline" id="ro-copy-link">Copy Link</button>
|
<button class="btn btn--outline" id="ro-copy-link">Copy Link</button>
|
||||||
<div class="export-dropdown">
|
<div class="export-dropdown">
|
||||||
|
|
@ -289,7 +289,7 @@ export class FolkThreadBuilder extends HTMLElement {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="ro-cta">
|
<div class="ro-cta">
|
||||||
<a href="/${this.esc(this._space)}/rsocials/thread" class="btn btn--success">Create Your Own Thread</a>
|
<a href="/${this.esc(this._space)}/rsocials/thread-editor" class="btn btn--success">Create Your Own Thread</a>
|
||||||
<a href="/${this.esc(this._space)}/rsocials/threads" class="btn btn--outline">Browse All Threads</a>
|
<a href="/${this.esc(this._space)}/rsocials/threads" class="btn btn--outline">Browse All Threads</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ export class FolkThreadGallery extends HTMLElement {
|
||||||
const cardsHTML = threads.length === 0
|
const cardsHTML = threads.length === 0
|
||||||
? `<div class="empty">
|
? `<div class="empty">
|
||||||
<p>No threads yet. Create your first thread!</p>
|
<p>No threads yet. Create your first thread!</p>
|
||||||
<a href="/${this.esc(space)}/rsocials/thread" class="btn btn--success">Create Thread</a>
|
<a href="/${this.esc(space)}/rsocials/thread-editor" class="btn btn--success">Create Thread</a>
|
||||||
</div>`
|
</div>`
|
||||||
: `<div class="grid">
|
: `<div class="grid">
|
||||||
${threads.map(t => {
|
${threads.map(t => {
|
||||||
|
|
@ -164,7 +164,7 @@ export class FolkThreadGallery extends HTMLElement {
|
||||||
<div class="gallery">
|
<div class="gallery">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<h1>Threads</h1>
|
<h1>Threads</h1>
|
||||||
<a href="/${this.esc(space)}/rsocials/thread" class="btn btn--primary">New Thread</a>
|
<a href="/${this.esc(space)}/rsocials/thread-editor" class="btn btn--primary">New Thread</a>
|
||||||
</div>
|
</div>
|
||||||
${cardsHTML}
|
${cardsHTML}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -459,7 +459,7 @@ routes.get("/thread/:id", async (c) => {
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
routes.get("/thread/:id/edit", async (c) => {
|
routes.get("/thread-editor/:id/edit", async (c) => {
|
||||||
const space = c.req.param("space") || "demo";
|
const space = c.req.param("space") || "demo";
|
||||||
const id = c.req.param("id");
|
const id = c.req.param("id");
|
||||||
if (!id || id.includes("..") || id.includes("/")) return c.text("Not found", 404);
|
if (!id || id.includes("..") || id.includes("/")) return c.text("Not found", 404);
|
||||||
|
|
@ -481,10 +481,10 @@ routes.get("/thread/:id/edit", async (c) => {
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
routes.get("/thread", (c) => {
|
routes.get("/thread-editor", (c) => {
|
||||||
const space = c.req.param("space") || "demo";
|
const space = c.req.param("space") || "demo";
|
||||||
return c.html(renderShell({
|
return c.html(renderShell({
|
||||||
title: `Thread Builder — rSocials | rSpace`,
|
title: `Thread Editor — rSocials | rSpace`,
|
||||||
moduleId: "rsocials",
|
moduleId: "rsocials",
|
||||||
spaceSlug: space,
|
spaceSlug: space,
|
||||||
modules: getModuleInfoList(),
|
modules: getModuleInfoList(),
|
||||||
|
|
@ -511,7 +511,16 @@ routes.get("/threads", (c) => {
|
||||||
|
|
||||||
routes.get("/campaigns", (c) => {
|
routes.get("/campaigns", (c) => {
|
||||||
const space = c.req.param("space") || "demo";
|
const space = c.req.param("space") || "demo";
|
||||||
return c.redirect(`/${space}/rsocials/campaign`);
|
return c.html(renderShell({
|
||||||
|
title: `Campaigns — rSocials | rSpace`,
|
||||||
|
moduleId: "rsocials",
|
||||||
|
spaceSlug: space,
|
||||||
|
modules: getModuleInfoList(),
|
||||||
|
theme: "dark",
|
||||||
|
body: `<folk-campaign-manager space="${escapeHtml(space)}"></folk-campaign-manager>`,
|
||||||
|
styles: `<link rel="stylesheet" href="/modules/rsocials/socials.css">`,
|
||||||
|
scripts: `<script type="module" src="/modules/rsocials/folk-campaign-manager.js"></script>`,
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── Demo feed rendering (server-rendered, no web component needed) ──
|
// ── Demo feed rendering (server-rendered, no web component needed) ──
|
||||||
|
|
@ -600,19 +609,55 @@ routes.get("/landing", (c) => {
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── Default: campaign planner canvas ──
|
// ── Default: rSocials hub with navigation ──
|
||||||
|
|
||||||
routes.get("/", (c) => {
|
routes.get("/", (c) => {
|
||||||
const space = c.req.param("space") || "demo";
|
const space = c.req.param("space") || "demo";
|
||||||
|
const base = `/${escapeHtml(space)}/rsocials`;
|
||||||
return c.html(renderShell({
|
return c.html(renderShell({
|
||||||
title: `${space} — rSocials | rSpace`,
|
title: `rSocials — ${space} | rSpace`,
|
||||||
moduleId: "rsocials",
|
moduleId: "rsocials",
|
||||||
spaceSlug: space,
|
spaceSlug: space,
|
||||||
modules: getModuleInfoList(),
|
modules: getModuleInfoList(),
|
||||||
body: `<folk-campaign-planner space="${escapeHtml(space)}"></folk-campaign-planner>`,
|
|
||||||
scripts: `<script type="module" src="/modules/rsocials/folk-campaign-planner.js"></script>`,
|
|
||||||
styles: `<link rel="stylesheet" href="/modules/rsocials/campaign-planner.css">`,
|
|
||||||
theme: "dark",
|
theme: "dark",
|
||||||
|
styles: `<style>
|
||||||
|
.rs-hub{max-width:720px;margin:3rem auto;padding:0 1.5rem}
|
||||||
|
.rs-hub h1{font-size:1.8rem;margin-bottom:.5rem}
|
||||||
|
.rs-hub p{color:var(--rs-text-secondary,#aaa);margin-bottom:2rem}
|
||||||
|
.rs-nav{display:flex;flex-direction:column;gap:1rem}
|
||||||
|
.rs-nav a{display:flex;align-items:center;gap:1rem;padding:1.25rem 1.5rem;border-radius:12px;background:var(--rs-surface,#1e1e2e);border:1px solid var(--rs-border,#333);text-decoration:none;color:inherit;transition:border-color .15s,background .15s}
|
||||||
|
.rs-nav a:hover{border-color:var(--rs-accent,#14b8a6);background:var(--rs-surface-hover,#252538)}
|
||||||
|
.rs-nav .nav-icon{font-size:2rem;flex-shrink:0}
|
||||||
|
.rs-nav .nav-body h3{margin:0 0 .25rem;font-size:1.1rem}
|
||||||
|
.rs-nav .nav-body p{margin:0;font-size:.85rem;color:var(--rs-text-secondary,#aaa)}
|
||||||
|
</style>`,
|
||||||
|
body: `<div class="rs-hub">
|
||||||
|
<h1>rSocials</h1>
|
||||||
|
<p>Social media tools for your community</p>
|
||||||
|
<nav class="rs-nav">
|
||||||
|
<a href="${base}/campaigns">
|
||||||
|
<span class="nav-icon">📢</span>
|
||||||
|
<div class="nav-body">
|
||||||
|
<h3>Campaigns</h3>
|
||||||
|
<p>Plan and manage multi-platform social media campaigns</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<a href="${base}/threads">
|
||||||
|
<span class="nav-icon">📋</span>
|
||||||
|
<div class="nav-body">
|
||||||
|
<h3>Threads</h3>
|
||||||
|
<p>Browse saved thread drafts and published threads</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<a href="${base}/thread-editor">
|
||||||
|
<span class="nav-icon">🧵</span>
|
||||||
|
<div class="nav-body">
|
||||||
|
<h3>Thread Editor</h3>
|
||||||
|
<p>Compose and preview tweet threads with live card preview</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</nav>
|
||||||
|
</div>`,
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -651,19 +696,7 @@ export const socialsModule: RSpaceModule = {
|
||||||
],
|
],
|
||||||
subPageInfos: [
|
subPageInfos: [
|
||||||
{
|
{
|
||||||
path: "thread",
|
path: "campaigns",
|
||||||
title: "Thread Builder",
|
|
||||||
icon: "🧵",
|
|
||||||
tagline: "rSocials Tool",
|
|
||||||
description: "Compose, preview, and schedule tweet threads with a live card-by-card preview. Save drafts, generate share images, and publish when ready.",
|
|
||||||
features: [
|
|
||||||
{ icon: "✍️", title: "Live Preview", text: "See your thread as tweet cards in real time as you type, with character counts and thread numbering." },
|
|
||||||
{ icon: "💾", title: "Save & Edit Drafts", text: "Save thread drafts to your space, revisit and refine them before publishing." },
|
|
||||||
{ icon: "🖼️", title: "Share Images", text: "Auto-generate a branded share image of your thread for cross-posting." },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "campaign",
|
|
||||||
title: "Campaign Manager",
|
title: "Campaign Manager",
|
||||||
icon: "📢",
|
icon: "📢",
|
||||||
tagline: "rSocials Tool",
|
tagline: "rSocials Tool",
|
||||||
|
|
@ -681,5 +714,17 @@ export const socialsModule: RSpaceModule = {
|
||||||
tagline: "rSocials Tool",
|
tagline: "rSocials Tool",
|
||||||
description: "Browse all saved thread drafts in your community. Find inspiration, remix threads, or pick up where you left off.",
|
description: "Browse all saved thread drafts in your community. Find inspiration, remix threads, or pick up where you left off.",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "thread-editor",
|
||||||
|
title: "Thread Editor",
|
||||||
|
icon: "🧵",
|
||||||
|
tagline: "rSocials Tool",
|
||||||
|
description: "Compose, preview, and schedule tweet threads with a live card-by-card preview. Save drafts, generate share images, and publish when ready.",
|
||||||
|
features: [
|
||||||
|
{ icon: "✍️", title: "Live Preview", text: "See your thread as tweet cards in real time as you type, with character counts and thread numbering." },
|
||||||
|
{ icon: "💾", title: "Save & Edit Drafts", text: "Save thread drafts to your space, revisit and refine them before publishing." },
|
||||||
|
{ icon: "🖼️", title: "Share Images", text: "Auto-generate a branded share image of your thread for cross-posting." },
|
||||||
|
],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue