import { NDKEvent, NDKKind, NDKTag } from '@nostr-dev-kit/ndk'; import CharacterCount from '@tiptap/extension-character-count'; import Image from '@tiptap/extension-image'; import Placeholder from '@tiptap/extension-placeholder'; import { EditorContent, FloatingMenu, useEditor } from '@tiptap/react'; import StarterKit from '@tiptap/starter-kit'; import { useLayoutEffect, useMemo, useRef, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { toast } from 'sonner'; import { twMerge } from 'tailwind-merge'; import { Markdown } from 'tiptap-markdown'; import { ArticleCoverUploader, MediaUploader, MentionPopup } from '@app/new/components'; import { useNDK } from '@libs/ndk/provider'; import { BoldIcon, Heading1Icon, Heading2Icon, Heading3Icon, ItalicIcon, LoaderIcon, ThreadsIcon, } from '@shared/icons'; export function NewArticleScreen() { const { ndk } = useNDK(); const [height, setHeight] = useState(0); const [loading, setLoading] = useState(false); const [title, setTitle] = useState(''); const [summary, setSummary] = useState({ open: false, content: '' }); const [cover, setCover] = useState(''); const navigate = useNavigate(); const containerRef = useRef(null); const ident = useMemo(() => String(Date.now()), []); const editor = useEditor({ extensions: [ StarterKit.configure(), Placeholder.configure({ placeholder: 'Type something...' }), Image.configure({ HTMLAttributes: { class: 'rounded-lg w-full object-cover h-auto max-h-[400px] border border-neutral-200 dark:border-neutral-800 outline outline-1 outline-offset-0 outline-neutral-300 dark:outline-neutral-700', }, }), CharacterCount.configure(), Markdown.configure({ html: false, tightLists: true, linkify: true, transformPastedText: true, }), ], content: JSON.parse(localStorage.getItem('editor-post') || '{}'), editorProps: { attributes: { class: 'outline-none prose prose-lg prose-neutral max-w-none select-text whitespace-pre-line break-words dark:prose-invert hover:prose-a:text-blue-500', }, }, onUpdate: ({ editor }) => { const jsonContent = JSON.stringify(editor.getJSON()); localStorage.setItem('editor-article', jsonContent); }, }); const submit = async () => { try { if (!ndk.signer) return navigate('/new/privkey'); setLoading(true); // get markdown content const content = editor.storage.markdown.getMarkdown(); // define tags const tags: NDKTag[] = [ ['d', ident], ['title', title], ['image', cover], ['summary', summary.content], ['published_at', String(Math.floor(Date.now() / 1000))], ]; // add hashtag to tags if present const hashtags = content.split(/\s/gm).filter((s: string) => s.startsWith('#')); hashtags?.forEach((tag: string) => { tags.push(['t', tag.replace('#', '')]); }); const event = new NDKEvent(ndk); event.content = content; event.kind = NDKKind.Article; event.tags = tags; // publish const publishedRelays = await event.publish(); if (publishedRelays) { toast.success(`Broadcasted to ${publishedRelays.size} relays successfully.`); // update state setLoading(false); // reset editor editor.commands.clearContent(); localStorage.setItem('editor-article', '{}'); } } catch (e) { setLoading(false); toast.error(e); } }; useLayoutEffect(() => { setHeight(containerRef.current.clientHeight); }, []); return (
Article editor is still in beta. If you need a stable and more reliable feature, you can use Habla (habla.news) instead.