diff --git a/bincio/serve/server.py b/bincio/serve/server.py index d9a8e7d..4dbb948 100644 --- a/bincio/serve/server.py +++ b/bincio/serve/server.py @@ -2828,6 +2828,34 @@ async def toggle_idea_status( return JSONResponse({"status": idea["status"]}) +@app.patch("/api/ideas/{idea_id}") +async def edit_idea( + idea_id: str, + data: IdeaBody, + bincio_session: Optional[str] = Cookie(default=None), +) -> JSONResponse: + user = _require_user(bincio_session) + dd = _get_data_dir() + path = _ideas_dir(dd) / f"{idea_id}.json" + if not path.exists(): + raise HTTPException(404, "Not found") + title = data.title.strip()[:200] + body = data.body.strip()[:2000] + if not title: + raise HTTPException(400, "Title required") + with open(path, "r+", encoding="utf-8") as f: + _fcntl.flock(f, _fcntl.LOCK_EX) + idea = json.load(f) + if not user.is_admin and idea.get("author") != user.handle: + raise HTTPException(403, "Forbidden") + idea["title"] = title + idea["body"] = body + f.seek(0) + f.truncate() + json.dump(idea, f, ensure_ascii=False, indent=2) + return JSONResponse({"ok": True, "title": title, "body": body}) + + @app.delete("/api/ideas/{idea_id}") async def delete_idea( idea_id: str, diff --git a/site/src/components/IdeasPage.svelte b/site/src/components/IdeasPage.svelte index b33583b..0a5ae2f 100644 --- a/site/src/components/IdeasPage.svelte +++ b/site/src/components/IdeasPage.svelte @@ -24,6 +24,42 @@ let submitting = false; let formError = ''; + let editingId: string | null = null; + let editTitle = ''; + let editBody = ''; + let editSaving = false; + + function startEdit(idea: Idea) { + editingId = idea.id; + editTitle = idea.title; + editBody = idea.body; + } + + function cancelEdit() { + editingId = null; + } + + async function saveEdit(idea: Idea) { + if (!editTitle.trim()) return; + editSaving = true; + try { + const r = await fetch(`/api/ideas/${idea.id}`, { + method: 'PATCH', + credentials: 'include', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ title: editTitle.trim(), body: editBody.trim() }), + }); + if (r.ok) { + idea.title = editTitle.trim(); + idea.body = editBody.trim(); + ideas = ideas; + editingId = null; + } + } finally { + editSaving = false; + } + } + function relativeTime(ts: number): string { const diff = Math.floor(Date.now() / 1000) - ts; if (diff < 60) return 'just now'; @@ -216,35 +252,68 @@
-
-

- {#if done}{/if}{idea.title} -

-
- {#if isAdmin} - - {/if} - {#if isAdmin || idea.author === myHandle} - - {/if} + {#if editingId === idea.id} + + +
+ +
-
- {#if idea.body} -

{idea.body}

+ {:else} +
+

+ {#if done}{/if}{idea.title} +

+
+ {#if isAdmin} + + {/if} + {#if isAdmin || idea.author === myHandle} + + + {/if} +
+
+ {#if idea.body} +

{idea.body}

+ {/if} +

+ @{idea.author} · {relativeTime(idea.created_at)} +

{/if} -

- @{idea.author} · {relativeTime(idea.created_at)} -

{/each}