Add long-form article editor (NIP-23)

- ArticleEditor with title, markdown body, summary, cover image, tags
- write/preview toggle with markdown rendering via marked
- Auto-save draft to localStorage
- Publish as kind 30023 with NIP-23 tags (d, title, published_at, etc.)
- 'write article' button in sidebar when logged in
- Article preview prose styles in CSS

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Jure
2026-03-08 19:04:43 +01:00
parent 366731f9d7
commit bf1d68bb93
9 changed files with 291 additions and 3 deletions
+31
View File
@@ -61,6 +61,37 @@ export async function fetchGlobalFeed(limit: number = 50): Promise<NDKEvent[]> {
return Array.from(events).sort((a, b) => (b.created_at ?? 0) - (a.created_at ?? 0));
}
export async function publishArticle(opts: {
title: string;
content: string;
summary?: string;
image?: string;
tags?: string[];
}): Promise<void> {
const instance = getNDK();
if (!instance.signer) throw new Error("Not logged in");
const slug = opts.title
.toLowerCase()
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-|-$/g, "")
.slice(0, 60) + "-" + Date.now();
const event = new NDKEvent(instance);
event.kind = 30023;
event.content = opts.content;
event.tags = [
["d", slug],
["title", opts.title],
["published_at", String(Math.floor(Date.now() / 1000))],
];
if (opts.summary) event.tags.push(["summary", opts.summary]);
if (opts.image) event.tags.push(["image", opts.image]);
if (opts.tags) opts.tags.forEach((t) => event.tags.push(["t", t]));
await event.publish();
}
export async function publishReaction(eventId: string, eventPubkey: string, reaction = "+"): Promise<void> {
const instance = getNDK();
if (!instance.signer) throw new Error("Not logged in");
+1 -1
View File
@@ -1 +1 @@
export { getNDK, connectToRelays, fetchGlobalFeed, fetchFollowFeed, fetchReplies, publishNote, publishReaction, publishReply, fetchUserNotes, fetchProfile } from "./client";
export { getNDK, connectToRelays, fetchGlobalFeed, fetchFollowFeed, fetchReplies, publishNote, publishArticle, publishReaction, publishReply, fetchUserNotes, fetchProfile } from "./client";