merge: resolve rsocials conflict, keep dev campaigns page

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-03-09 13:32:45 -07:00
commit b553acd756
6 changed files with 102 additions and 41 deletions

View File

@ -360,17 +360,31 @@ Gear: EUR 400 (10%)</code></pre><p><em>Maya is tracking expenses in rF
}
private demoCreateNotebook() {
const title = prompt("Notebook name:");
if (!title?.trim()) return;
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 = {
id: `demo-nb-${now}`, title, description: "",
cover_color: "#8b5cf6", note_count: "0",
updated_at: new Date(now).toISOString(), notes: [] as Note[],
id: nbId, title: "Untitled Notebook", description: "",
cover_color: "#8b5cf6", note_count: "1",
updated_at: new Date(now).toISOString(), notes: [newNote],
} as any;
this.demoNotebooks.push(nb);
this.notebooks = this.demoNotebooks.map(({ notes, ...rest }) => rest as Notebook);
this.selectedNotebook = { ...nb };
this.view = "notebook";
this.render();
// Auto-open the note for editing
this.selectedNote = newNote;
this.view = "note";
this.renderNav();
this.renderMeta();
this.mountEditor(newNote);
}
private demoCreateNote() {
@ -726,16 +740,20 @@ Gear: EUR 400 (10%)</code></pre><p><em>Maya is tracking expenses in rF
}
private async createNotebook() {
const title = prompt("Notebook name:");
if (!title?.trim()) return;
try {
const base = this.getApiBase();
await fetch(`${base}/api/notebooks`, {
const res = await fetch(`${base}/api/notebooks`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ title }),
body: JSON.stringify({ title: "Untitled Notebook" }),
});
await this.loadNotebooks();
const nb = await res.json();
if (nb?.id) {
this.view = "notebook";
await this.loadNotebook(nb.id);
} else {
await this.loadNotebooks();
}
} catch {
this.error = "Failed to create notebook";
this.render();

View File

@ -131,7 +131,7 @@ export class FolkCampaignManager extends HTMLElement {
</div>
</div>
<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>
</div>
${phaseHTML}

View File

@ -820,7 +820,7 @@ class FolkCampaignPlanner extends HTMLElement {
} else if (action === 'open-thread') {
const d = node.data as ThreadNodeData;
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') {
const d = node.data as ThreadNodeData;
if (d.threadId) {
window.location.href = `/${this.space}/rsocials/thread/${d.threadId}/edit`;
window.location.href = `/${this.space}/rsocials/thread-editor/${d.threadId}/edit`;
}
}
});

View File

@ -274,7 +274,7 @@ export class FolkThreadBuilder extends HTMLElement {
</div>
<div class="preview ro-cards">${tweetCards}</div>
<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-link">Copy Link</button>
<div class="export-dropdown">
@ -289,7 +289,7 @@ export class FolkThreadBuilder extends HTMLElement {
</div>
</div>
<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>
</div>
</div>

View File

@ -96,7 +96,7 @@ export class FolkThreadGallery extends HTMLElement {
const cardsHTML = threads.length === 0
? `<div class="empty">
<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 class="grid">
${threads.map(t => {
@ -164,7 +164,7 @@ export class FolkThreadGallery extends HTMLElement {
<div class="gallery">
<div class="header">
<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>
${cardsHTML}
</div>

View File

@ -467,7 +467,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 dataSpace = (c.get("effectiveSpace" as any) as string) || space;
const id = c.req.param("id");
@ -490,11 +490,11 @@ routes.get("/thread/:id/edit", async (c) => {
}));
});
routes.get("/thread", (c) => {
routes.get("/thread-editor", (c) => {
const space = c.req.param("space") || "demo";
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
return c.html(renderShell({
title: `Thread Builder — rSocials | rSpace`,
title: `Thread Editor — rSocials | rSpace`,
moduleId: "rsocials",
spaceSlug: space,
modules: getModuleInfoList(),
@ -522,8 +522,16 @@ routes.get("/threads", (c) => {
routes.get("/campaigns", (c) => {
const space = c.req.param("space") || "demo";
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
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) ──
@ -615,20 +623,55 @@ routes.get("/landing", (c) => {
}));
});
// ── Default: campaign planner canvas ──
// ── Default: rSocials hub with navigation ──
routes.get("/", (c) => {
const space = c.req.param("space") || "demo";
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
const base = `/${escapeHtml(space)}/rsocials`;
return c.html(renderShell({
title: `${space} — rSocials | rSpace`,
title: `rSocials — ${space} | rSpace`,
moduleId: "rsocials",
spaceSlug: space,
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",
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>`,
}));
});
@ -667,19 +710,7 @@ export const socialsModule: RSpaceModule = {
],
subPageInfos: [
{
path: "thread",
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",
path: "campaigns",
title: "Campaign Manager",
icon: "📢",
tagline: "rSocials Tool",
@ -697,5 +728,17 @@ export const socialsModule: RSpaceModule = {
tagline: "rSocials Tool",
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." },
],
},
],
};