diff --git a/src/components/article/ArticleEditor.tsx b/src/components/article/ArticleEditor.tsx index 97df99b..0b492bb 100644 --- a/src/components/article/ArticleEditor.tsx +++ b/src/components/article/ArticleEditor.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useRef } from "react"; +import { useState, useEffect, useRef, useCallback } from "react"; import { marked } from "marked"; import { publishArticle } from "../../lib/nostr"; import { useUIStore } from "../../stores/ui"; @@ -7,6 +7,7 @@ import { useDraftStore, type ArticleDraft } from "../../stores/drafts"; import { open } from "@tauri-apps/plugin-dialog"; import { readFile } from "@tauri-apps/plugin-fs"; import { uploadBytes } from "../../lib/upload"; +import { getCurrentWindow } from "@tauri-apps/api/window"; export function ArticleEditor() { const { goBack } = useUIStore(); @@ -27,6 +28,46 @@ export function ArticleEditor() { const [uploading, setUploading] = useState(false); const [error, setError] = useState(null); const [published, setPublished] = useState(false); + const [publishedRelays, setPublishedRelays] = useState(0); + const [lastSaved, setLastSaved] = useState(null); + const [zenMode, setZenMode] = useState(false); + const [zenHint, setZenHint] = useState(false); + const zenTextareaRef = useRef(null); + + const toggleZen = useCallback(async () => { + const win = getCurrentWindow(); + if (zenMode) { + await win.setFullscreen(false); + setZenMode(false); + } else { + await win.setFullscreen(true); + setZenMode(true); + setZenHint(true); + setTimeout(() => setZenHint(false), 2500); + } + }, [zenMode]); + + // F11 to toggle zen mode, Esc to exit + useEffect(() => { + const handler = (e: KeyboardEvent) => { + if (e.key === "F11") { + e.preventDefault(); + toggleZen(); + } + if (e.key === "Escape" && zenMode) { + toggleZen(); + } + }; + window.addEventListener("keydown", handler); + return () => window.removeEventListener("keydown", handler); + }, [toggleZen, zenMode]); + + // Exit fullscreen on unmount + useEffect(() => { + return () => { + getCurrentWindow().setFullscreen(false).catch(() => {}); + }; + }, []); // Sync state when active draft changes useEffect(() => { @@ -46,10 +87,19 @@ export function ArticleEditor() { if (!activeDraftId) return; const t = setTimeout(() => { updateDraft(activeDraftId, { title, content, summary, image, tags }); + setLastSaved(Date.now()); }, 1000); return () => clearTimeout(t); }, [title, content, summary, image, tags, activeDraftId]); + // Update "saved Xs ago" display every 10s + const [, setTick] = useState(0); + useEffect(() => { + if (!lastSaved) return; + const iv = setInterval(() => setTick((t) => t + 1), 10000); + return () => clearInterval(iv); + }, [lastSaved]); + const renderedHtml = marked(content || "*Nothing to preview yet.*") as string; const wordCount = content.trim() ? content.trim().split(/\s+/).length : 0; const canPublish = title.trim().length > 0 && content.trim().length > 0; @@ -59,7 +109,7 @@ export function ArticleEditor() { setPublishing(true); setError(null); try { - await publishArticle({ + const result = await publishArticle({ title: title.trim(), content: content.trim(), summary: summary.trim() || undefined, @@ -68,7 +118,11 @@ export function ArticleEditor() { }); if (activeDraftId) deleteDraft(activeDraftId); setPublished(true); - setTimeout(goBack, 1500); + setPublishedRelays(result.relayCount); + if (result.relayCount === 0) { + setError("Warning: no relays confirmed — your article may not have been published."); + } + setTimeout(goBack, 2000); } catch (err) { setError(`Failed to publish: ${err}`); } finally { @@ -113,6 +167,45 @@ export function ArticleEditor() { return ; } + // Zen mode — fullscreen distraction-free writing + if (zenMode) { + return ( +
+ {/* Exit hint — fades after 2.5s */} +
+ Esc or F11 to exit +
+ +
+ setTitle(e.target.value)} + placeholder="Title" + className="w-full bg-transparent text-text text-3xl font-bold placeholder:text-text-dim focus:outline-none mb-6" + style={{ fontFamily: "var(--font-reading)" }} + /> +