From 854a47f26627cd89378e09526e4f5009af0d71f6 Mon Sep 17 00:00:00 2001 From: reya Date: Tue, 24 Oct 2023 13:11:10 +0700 Subject: [PATCH] wip --- package.json | 3 + pnpm-lock.yaml | 9 + src/app.css | 8 + src/app.tsx | 36 ++- src/app/chats/components/chatForm.tsx | 15 +- src/app/new/article.tsx | 270 ++++++++++++++++++ .../new/components/articleCoverUploader.tsx | 84 ++++++ src/app/new/components/index.ts | 4 + .../new/components}/mediaUploader.tsx | 0 .../new/components/mentionPopup.tsx} | 5 +- .../new/components/mentionPopupItem.tsx} | 2 +- .../{notes/components/editor => new}/file.tsx | 68 +++-- src/app/new/index.tsx | 77 +++++ .../{notes/components/editor => new}/post.tsx | 73 ++++- src/app/notes/components/editor/article.tsx | 124 -------- src/app/notes/components/index.ts | 3 - src/app/notes/new.tsx | 85 ------ src/app/nwc/components/alby.tsx | 2 +- src/app/nwc/components/other.tsx | 2 +- src/app/relays/components/relayList.tsx | 3 +- src/libs/storage/instance.ts | 5 +- src/shared/composer/composer.tsx | 164 ----------- src/shared/composer/index.ts | 8 - src/shared/composer/mention/inlineList.tsx | 83 ------ src/shared/composer/mention/suggestion.tsx | 68 ----- src/shared/composer/modal.tsx | 52 ---- src/shared/composer/user.tsx | 19 -- src/shared/icons/bold.tsx | 22 ++ src/shared/icons/heading1.tsx | 22 ++ src/shared/icons/heading2.tsx | 22 ++ src/shared/icons/heading3.tsx | 22 ++ src/shared/icons/index.ts | 5 + src/shared/icons/italic.tsx | 22 ++ src/shared/navigation.tsx | 2 +- src/shared/notes/actions/reaction.tsx | 28 +- src/shared/notes/actions/reply.tsx | 16 +- src/shared/notes/actions/repost.tsx | 38 +-- src/shared/notes/child.tsx | 22 +- src/shared/notes/kinds/file.tsx | 2 +- src/shared/notes/kinds/repost.tsx | 24 +- src/shared/notes/kinds/text.tsx | 4 +- src/shared/notes/preview/link.tsx | 2 +- src/shared/notes/preview/video.tsx | 2 +- src/shared/notes/skeleton.tsx | 6 +- src/shared/notes/wrapper.tsx | 2 +- src/shared/user.tsx | 8 +- src/shared/widgets/local/network.tsx | 4 +- src/stores/composer.ts | 30 -- src/stores/constants.ts | 3 +- src/utils/hooks/useEvent.ts | 7 +- src/utils/rawEvent.ts | 12 - src/utils/transform.ts | 14 +- 52 files changed, 815 insertions(+), 798 deletions(-) create mode 100644 src/app/new/article.tsx create mode 100644 src/app/new/components/articleCoverUploader.tsx create mode 100644 src/app/new/components/index.ts rename src/{shared/composer => app/new/components}/mediaUploader.tsx (100%) rename src/{shared/composer/mention/popup.tsx => app/new/components/mentionPopup.tsx} (93%) rename src/{shared/composer/mention/item.tsx => app/new/components/mentionPopupItem.tsx} (94%) rename src/app/{notes/components/editor => new}/file.tsx (62%) create mode 100644 src/app/new/index.tsx rename src/app/{notes/components/editor => new}/post.tsx (66%) delete mode 100644 src/app/notes/components/editor/article.tsx delete mode 100644 src/app/notes/components/index.ts delete mode 100644 src/app/notes/new.tsx delete mode 100644 src/shared/composer/composer.tsx delete mode 100644 src/shared/composer/index.ts delete mode 100644 src/shared/composer/mention/inlineList.tsx delete mode 100644 src/shared/composer/mention/suggestion.tsx delete mode 100644 src/shared/composer/modal.tsx delete mode 100644 src/shared/composer/user.tsx create mode 100644 src/shared/icons/bold.tsx create mode 100644 src/shared/icons/heading1.tsx create mode 100644 src/shared/icons/heading2.tsx create mode 100644 src/shared/icons/heading3.tsx create mode 100644 src/shared/icons/italic.tsx delete mode 100644 src/stores/composer.ts delete mode 100644 src/utils/rawEvent.ts diff --git a/package.json b/package.json index 1bfbab73..7d2c74ab 100644 --- a/package.json +++ b/package.json @@ -50,9 +50,12 @@ "@tauri-apps/plugin-upload": "2.0.0-alpha.1", "@tauri-apps/plugin-window": "2.0.0-alpha.1", "@tiptap/extension-character-count": "^2.1.12", + "@tiptap/extension-document": "^2.1.12", "@tiptap/extension-image": "^2.1.12", "@tiptap/extension-mention": "^2.1.12", + "@tiptap/extension-paragraph": "^2.1.12", "@tiptap/extension-placeholder": "^2.1.12", + "@tiptap/extension-text": "^2.1.12", "@tiptap/pm": "^2.1.12", "@tiptap/react": "^2.1.12", "@tiptap/starter-kit": "^2.1.12", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 052cff96..335964cc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -101,15 +101,24 @@ dependencies: '@tiptap/extension-character-count': specifier: ^2.1.12 version: 2.1.12(@tiptap/core@2.1.12)(@tiptap/pm@2.1.12) + '@tiptap/extension-document': + specifier: ^2.1.12 + version: 2.1.12(@tiptap/core@2.1.12) '@tiptap/extension-image': specifier: ^2.1.12 version: 2.1.12(@tiptap/core@2.1.12) '@tiptap/extension-mention': specifier: ^2.1.12 version: 2.1.12(@tiptap/core@2.1.12)(@tiptap/pm@2.1.12)(@tiptap/suggestion@2.1.12) + '@tiptap/extension-paragraph': + specifier: ^2.1.12 + version: 2.1.12(@tiptap/core@2.1.12) '@tiptap/extension-placeholder': specifier: ^2.1.12 version: 2.1.12(@tiptap/core@2.1.12)(@tiptap/pm@2.1.12) + '@tiptap/extension-text': + specifier: ^2.1.12 + version: 2.1.12(@tiptap/core@2.1.12) '@tiptap/pm': specifier: ^2.1.12 version: 2.1.12 diff --git a/src/app.css b/src/app.css index b425d849..a22f5396 100644 --- a/src/app.css +++ b/src/app.css @@ -4,6 +4,14 @@ @tailwind components; @tailwind utilities; +@layer utilities { + .break-p { + word-break: break-word; + word-wrap: break-word; + overflow-wrap: break-word; + } +} + html { font-size: 14px; } diff --git a/src/app.tsx b/src/app.tsx index 009d7157..1ec6544a 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -6,6 +6,7 @@ import { OnboardingScreen } from '@app/auth/onboarding'; import { ChatsScreen } from '@app/chats'; import { ErrorScreen } from '@app/error'; import { ExploreScreen } from '@app/explore'; +import { NewScreen } from '@app/new'; import { useStorage } from '@libs/storage/provider'; @@ -118,18 +119,39 @@ export default function App() { }, ], }, + { + path: '/new', + element: , + errorElement: , + children: [ + { + path: '', + async lazy() { + const { NewPostScreen } = await import('@app/new/post'); + return { Component: NewPostScreen }; + }, + }, + { + path: 'article', + async lazy() { + const { NewArticleScreen } = await import('@app/new/article'); + return { Component: NewArticleScreen }; + }, + }, + { + path: 'file', + async lazy() { + const { NewFileScreen } = await import('@app/new/file'); + return { Component: NewFileScreen }; + }, + }, + ], + }, { path: '/notes', element: , errorElement: , children: [ - { - path: 'new', - async lazy() { - const { NewNoteScreen } = await import('@app/notes/new'); - return { Component: NewNoteScreen }; - }, - }, { path: 'text/:id', async lazy() { diff --git a/src/app/chats/components/chatForm.tsx b/src/app/chats/components/chatForm.tsx index 4936b791..ace68a08 100644 --- a/src/app/chats/components/chatForm.tsx +++ b/src/app/chats/components/chatForm.tsx @@ -1,11 +1,12 @@ +import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk'; import { nip04 } from 'nostr-tools'; import { useCallback, useState } from 'react'; import { MediaUploader } from '@app/chats/components/mediaUploader'; -import { EnterIcon } from '@shared/icons'; +import { useNDK } from '@libs/ndk/provider'; -import { useNostr } from '@utils/hooks/useNostr'; +import { EnterIcon } from '@shared/icons'; export function ChatForm({ receiverPubkey, @@ -15,7 +16,7 @@ export function ChatForm({ userPubkey: string; userPrivkey: string; }) { - const { publish } = useNostr(); + const { ndk } = useNDK(); const [value, setValue] = useState(''); const encryptMessage = useCallback(async () => { @@ -26,8 +27,12 @@ export function ChatForm({ const message = await encryptMessage(); const tags = [['p', receiverPubkey]]; - // publish message - await publish({ content: message, kind: 4, tags }); + const event = new NDKEvent(ndk); + event.content = message; + event.kind = NDKKind.EncryptedDirectMessage; + event.tags = tags; + + await event.publish(); // reset state setValue(''); diff --git a/src/app/new/article.tsx b/src/app/new/article.tsx new file mode 100644 index 00000000..38998e35 --- /dev/null +++ b/src/app/new/article.tsx @@ -0,0 +1,270 @@ +import { NDKEvent, NDKKind } 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 { useMemo, useState } from 'react'; +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 [loading, setLoading] = useState(false); + const [title, setTitle] = useState(''); + const [summary, setSummary] = useState({ open: false, content: '' }); + const [cover, setCover] = useState(''); + + 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 { + setLoading(true); + + // get markdown content + const content = editor.storage.markdown.getMarkdown(); + + // define tags + const tags: string[][] = [ + ['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('#', '')]); + }); + + // publish message + const event = new NDKEvent(ndk); + event.content = content; + event.kind = NDKKind.Article; + event.tags = tags; + + 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); + } + }; + + return ( +
+
+ {cover ? ( + post cover + ) : null} +
+ setTitle(e.target.value)} + /> +
0 ? '' : 'hidden' + )} + > + + +
+
+ {summary.open ? ( +
+
+
+