Files
vega/src/stores/drafts.ts
Jure 092553ab9b Bump to v0.7.0 — writer tools, NIP-98 uploads, multi-draft, article bookmarks
- NIP-98 HTTP Auth for image uploads with fallback services (void.cat, nostrimg.com)
- Markdown toolbar (bold, italic, heading, link, image, quote, code, list) + Ctrl+B/I/K
- Multi-draft management with draft list, resume, delete, auto-migrate
- Cover image file picker in article meta panel
- Article bookmarks via NIP-51 'a' tags; Notes/Articles tabs in BookmarkView
- Removed Rust upload_file command; dropped reqwest/mime_guess deps
- Upload spinner, draft count badge, empty states
2026-03-18 18:36:08 +01:00

123 lines
3.1 KiB
TypeScript

import { create } from "zustand";
const STORAGE_KEY = "wrystr_article_drafts";
const ACTIVE_KEY = "wrystr_active_draft";
const OLD_DRAFT_KEY = "wrystr_article_draft";
export interface ArticleDraft {
id: string;
title: string;
content: string;
summary: string;
image: string;
tags: string;
createdAt: number;
updatedAt: number;
}
function generateId(): string {
return Date.now().toString(36) + Math.random().toString(36).slice(2, 8);
}
function loadDrafts(): ArticleDraft[] {
try {
const stored = localStorage.getItem(STORAGE_KEY);
if (stored) return JSON.parse(stored);
} catch { /* ignore */ }
// Auto-migrate old single-draft format
try {
const old = localStorage.getItem(OLD_DRAFT_KEY);
if (old) {
const data = JSON.parse(old);
if (data && (data.title || data.content)) {
const migrated: ArticleDraft = {
id: generateId(),
title: data.title || "",
content: data.content || "",
summary: data.summary || "",
image: data.image || "",
tags: data.tags || "",
createdAt: Date.now(),
updatedAt: Date.now(),
};
localStorage.setItem(STORAGE_KEY, JSON.stringify([migrated]));
localStorage.removeItem(OLD_DRAFT_KEY);
return [migrated];
}
}
} catch { /* ignore */ }
return [];
}
function saveDrafts(drafts: ArticleDraft[]) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(drafts));
}
function loadActiveDraftId(): string | null {
return localStorage.getItem(ACTIVE_KEY) || null;
}
function saveActiveDraftId(id: string | null) {
if (id) {
localStorage.setItem(ACTIVE_KEY, id);
} else {
localStorage.removeItem(ACTIVE_KEY);
}
}
interface DraftState {
drafts: ArticleDraft[];
activeDraftId: string | null;
createDraft: () => string;
updateDraft: (id: string, fields: Partial<Pick<ArticleDraft, "title" | "content" | "summary" | "image" | "tags">>) => void;
deleteDraft: (id: string) => void;
setActiveDraft: (id: string | null) => void;
}
export const useDraftStore = create<DraftState>((set, get) => ({
drafts: loadDrafts(),
activeDraftId: loadActiveDraftId(),
createDraft: () => {
const id = generateId();
const draft: ArticleDraft = {
id,
title: "",
content: "",
summary: "",
image: "",
tags: "",
createdAt: Date.now(),
updatedAt: Date.now(),
};
const updated = [draft, ...get().drafts];
set({ drafts: updated, activeDraftId: id });
saveDrafts(updated);
saveActiveDraftId(id);
return id;
},
updateDraft: (id, fields) => {
const updated = get().drafts.map((d) =>
d.id === id ? { ...d, ...fields, updatedAt: Date.now() } : d
);
set({ drafts: updated });
saveDrafts(updated);
},
deleteDraft: (id) => {
const updated = get().drafts.filter((d) => d.id !== id);
const activeId = get().activeDraftId === id ? null : get().activeDraftId;
set({ drafts: updated, activeDraftId: activeId });
saveDrafts(updated);
saveActiveDraftId(activeId);
},
setActiveDraft: (id) => {
set({ activeDraftId: id });
saveActiveDraftId(id);
},
}));