From fee4ad7b98a1a3a762b20eb6b0f94978c7501408 Mon Sep 17 00:00:00 2001 From: reya Date: Mon, 13 Nov 2023 15:44:58 +0700 Subject: [PATCH] smal fixes and update article layout --- package.json | 1 + pnpm-lock.yaml | 9 ++ src/app.css | 120 +-------------------------- src/app/chats/components/message.tsx | 18 +--- src/app/new/article.tsx | 11 ++- src/app/new/post.tsx | 36 +++++--- src/app/notes/article.tsx | 103 ++++++++++------------- src/app/notes/text.tsx | 33 ++++++-- src/shared/notes/actions/more.tsx | 16 +--- src/shared/notes/mentions/note.tsx | 36 ++++---- src/shared/notes/preview/link.tsx | 8 +- src/utils/hooks/useEvent.ts | 13 +-- src/utils/hooks/useRichContent.tsx | 9 +- 13 files changed, 165 insertions(+), 248 deletions(-) diff --git a/package.json b/package.json index 4fbc7a85..00997e7a 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,7 @@ "markdown-to-jsx": "^7.3.2", "media-chrome": "^1.5.2", "minidenticons": "^4.2.0", + "nanoid": "^5.0.3", "nostr-fetch": "^0.13.1", "nostr-tools": "^1.17.0", "qrcode.react": "^3.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fd55430b..6ec56e9a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -146,6 +146,9 @@ dependencies: minidenticons: specifier: ^4.2.0 version: 4.2.0 + nanoid: + specifier: ^5.0.3 + version: 5.0.3 nostr-fetch: specifier: ^0.13.1 version: 0.13.1 @@ -4696,6 +4699,12 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + /nanoid@5.0.3: + resolution: {integrity: sha512-I7X2b22cxA4LIHXPSqbBCEQSL+1wv8TuoefejsX4HFWyC6jc5JG7CEaxOltiKjc1M+YCS2YkrZZcj4+dytw9GA==} + engines: {node: ^18 || >=20} + hasBin: true + dev: false + /natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} dev: true diff --git a/src/app.css b/src/app.css index d7f5d15b..cba4eba0 100644 --- a/src/app.css +++ b/src/app.css @@ -10,6 +10,10 @@ word-wrap: break-word; overflow-wrap: break-word; } + + .prose :where(iframe):not(:where([class~="not-prose"] *)) { + @apply aspect-video w-full h-auto mx-auto; + } } html { @@ -64,119 +68,3 @@ input::-ms-clear { .ProseMirror img.ProseMirror-selectednode { @apply outline-blue-500; } - -[data-rmiz] { - position: relative; -} - -[data-rmiz-ghost] { - position: absolute; - pointer-events: none; -} - -[data-rmiz-btn-zoom], -[data-rmiz-btn-unzoom] { - background-color: rgba(0, 0, 0, 0.7); - border-radius: 50%; - border: none; - box-shadow: 0 0 1px rgba(255, 255, 255, 0.5); - color: #fff; - height: 40px; - margin: 0; - outline-offset: 2px; - padding: 9px; - touch-action: manipulation; - width: 40px; - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; -} - -[data-rmiz-btn-zoom]:not(:focus):not(:active) { - position: absolute; - clip: rect(0 0 0 0); - clip-path: inset(50%); - height: 1px; - overflow: hidden; - pointer-events: none; - white-space: nowrap; - width: 1px; -} - -[data-rmiz-btn-zoom] { - position: absolute; - inset: 10px 10px auto auto; - cursor: zoom-in; -} - -[data-rmiz-btn-unzoom] { - position: absolute; - inset: 20px 20px auto auto; - cursor: zoom-out; - z-index: 1; - display: flex; - align-items: center; - justify-content: center; -} - -[data-rmiz-content="found"] img, -[data-rmiz-content="found"] svg, -[data-rmiz-content="found"] [role="img"], -[data-rmiz-content="found"] [data-zoom] { - cursor: zoom-in; -} - -[data-rmiz-modal]::backdrop { - display: none; -} - -[data-rmiz-modal][open] { - position: fixed; - width: 100vw; - width: 100dvw; - height: 100vh; - height: 100dvh; - max-width: none; - max-height: none; - margin: 0; - padding: 0; - border: 0; - background: transparent; - overflow: hidden; -} - -[data-rmiz-modal-overlay] { - position: absolute; - inset: 0; - transition: background-color 0.3s; -} - -[data-rmiz-modal-overlay="hidden"] { - background-color: rgba(255, 255, 255, 0); -} - -[data-rmiz-modal-overlay="visible"] { - background-color: rgba(255, 255, 255, 0.5); - backdrop-filter: blur(4px); -} - -[data-rmiz-modal-content] { - position: relative; - width: 100%; - height: 100%; -} - -[data-rmiz-modal-img] { - position: absolute; - cursor: zoom-out; - image-rendering: high-quality; - transform-origin: top left; - transition: transform 0.3s; -} - -@media (prefers-reduced-motion: reduce) { - [data-rmiz-modal-overlay], - [data-rmiz-modal-img] { - transition-duration: 0.01ms !important; - } -} diff --git a/src/app/chats/components/message.tsx b/src/app/chats/components/message.tsx index 3dedeac2..f12843c9 100644 --- a/src/app/chats/components/message.tsx +++ b/src/app/chats/components/message.tsx @@ -3,13 +3,8 @@ import { twMerge } from 'tailwind-merge'; import { useDecryptMessage } from '@app/chats/hooks/useDecryptMessage'; -import { ImagePreview, LinkPreview, MentionNote, VideoPreview } from '@shared/notes'; - -import { parser } from '@utils/parser'; - export function ChatMessage({ message, self }: { message: NDKEvent; self: boolean }) { const decryptedContent = useDecryptMessage(message); - const richContent = parser(decryptedContent) ?? null; return (
- {!richContent ? ( + {!decryptedContent ? (

Decrypting...

) : (
-

{richContent.parsed}

-
- {richContent.images.length > 0 && } - {richContent.videos.length > 0 && } - {richContent.links.length > 0 && } - {richContent.notes.length > 0 && - richContent.notes.map((note: string) => ( - - ))} -
+

{decryptedContent}

)}
diff --git a/src/app/new/article.tsx b/src/app/new/article.tsx index 38998e35..16b20bf4 100644 --- a/src/app/new/article.tsx +++ b/src/app/new/article.tsx @@ -1,4 +1,4 @@ -import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk'; +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'; @@ -71,7 +71,7 @@ export function NewArticleScreen() { const content = editor.storage.markdown.getMarkdown(); // define tags - const tags: string[][] = [ + const tags: NDKTag[] = [ ['d', ident], ['title', title], ['image', cover], @@ -85,17 +85,20 @@ export function NewArticleScreen() { tags.push(['t', tag.replace('#', '')]); }); - // publish message 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', '{}'); @@ -235,7 +238,7 @@ export function NewArticleScreen() {
- {editor?.storage?.characterCount.characters()} + {editor?.storage?.characterCount.characters()} characters - diff --git a/src/app/new/post.tsx b/src/app/new/post.tsx index afb92439..a77e5f74 100644 --- a/src/app/new/post.tsx +++ b/src/app/new/post.tsx @@ -1,4 +1,4 @@ -import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk'; +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'; @@ -16,8 +16,13 @@ import { useNDK } from '@libs/ndk/provider'; import { CancelIcon, LoaderIcon } from '@shared/icons'; import { MentionNote } from '@shared/notes'; +import { WIDGET_KIND } from '@stores/constants'; + +import { useWidget } from '@utils/hooks/useWidget'; + export function NewPostScreen() { const { ndk, relayUrls } = useNDK(); + const { addWidget } = useWidget(); const [loading, setLoading] = useState(false); const [searchParams, setSearchParams] = useSearchParams(); @@ -67,11 +72,11 @@ export function NewPostScreen() { }); // define tags - let tags: string[][] = []; + let tags: NDKTag[] = []; // add reply to tags if present if (reply.id && reply.pubkey) { - if (reply.root && reply.root.length > 1) { + if (reply.root) { tags = [ ['e', reply.root, relayUrls[0], 'root'], ['e', reply.id, relayUrls[0], 'reply'], @@ -89,24 +94,35 @@ export function NewPostScreen() { const hashtags = serializedContent .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 = serializedContent; event.kind = NDKKind.Text; event.tags = tags; + // publish event const publishedRelays = await event.publish(); + if (publishedRelays) { toast.success(`Broadcasted to ${publishedRelays.size} relays successfully.`); + // update state setLoading(false); - // reset editor setSearchParams({}); + + // open new widget with this event id + if (!reply.id && !reply.pubkey) { + addWidget.mutate({ + title: 'Thread', + content: event.id, + kind: WIDGET_KIND.thread, + }); + } + + // reset editor editor.commands.clearContent(); localStorage.setItem('editor-post', '{}'); } @@ -132,20 +148,20 @@ export function NewPostScreen() { /> {searchParams.get('id') && (
- +
)}
- {editor?.storage?.characterCount.characters()} + {editor?.storage?.characterCount.characters()} characters
diff --git a/src/app/notes/article.tsx b/src/app/notes/article.tsx index 1df8bedf..4f746f55 100644 --- a/src/app/notes/article.tsx +++ b/src/app/notes/article.tsx @@ -1,24 +1,21 @@ import { writeText } from '@tauri-apps/plugin-clipboard-manager'; +import Markdown from 'markdown-to-jsx'; import { nip19 } from 'nostr-tools'; -import { AddressPointer, EventPointer } from 'nostr-tools/lib/types/nip19'; -import { useRef, useState } from 'react'; +import { EventPointer } from 'nostr-tools/lib/types/nip19'; +import { useState } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; -import { ArrowLeftIcon, CheckCircleIcon, ReplyIcon, ShareIcon } from '@shared/icons'; -import { NoteActions, NoteReplyForm } from '@shared/notes'; +import { ArrowLeftIcon, CheckCircleIcon, ShareIcon } from '@shared/icons'; +import { NoteReplyForm } from '@shared/notes'; import { ReplyList } from '@shared/notes/replies/list'; -import { User } from '@shared/user'; import { useEvent } from '@utils/hooks/useEvent'; export function ArticleNoteScreen() { - const { id } = useParams(); - const navigate = useNavigate(); - const replyRef = useRef(null); - const naddr = id.startsWith('naddr') ? (nip19.decode(id).data as AddressPointer) : null; - const { status, data } = useEvent(id, naddr); + const { id } = useParams(); + const { status, data } = useEvent(id); const [isCopy, setIsCopy] = useState(false); @@ -33,12 +30,8 @@ export function ArticleNoteScreen() { setTimeout(() => setIsCopy(false), 2000); }; - const scrollToReply = () => { - replyRef.current.scrollIntoView(); - }; - return ( -
+
-
- - -
+
-
-
- {status === 'pending' ? ( -
Loading...
- ) : ( -
-
- -
{data.content}
-
- -
-
-
- )} -
-
- -
- -
-
+
+ {status === 'pending' ? ( +
Loading...
+ ) : ( + + {data.content} + + )} +
+
+
+ +
+
-
); } diff --git a/src/app/notes/text.tsx b/src/app/notes/text.tsx index 85695b6e..861a0861 100644 --- a/src/app/notes/text.tsx +++ b/src/app/notes/text.tsx @@ -6,19 +6,27 @@ import { useRef, useState } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import { ArrowLeftIcon, CheckCircleIcon, ReplyIcon, ShareIcon } from '@shared/icons'; -import { MemoizedTextKind, NoteActions, NoteReplyForm, UnknownNote } from '@shared/notes'; +import { + ChildNote, + MemoizedTextKind, + NoteActions, + NoteReplyForm, + UnknownNote, +} from '@shared/notes'; import { ReplyList } from '@shared/notes/replies/list'; import { User } from '@shared/user'; import { useEvent } from '@utils/hooks/useEvent'; +import { useNostr } from '@utils/hooks/useNostr'; export function TextNoteScreen() { - const { id } = useParams(); - const { status, data } = useEvent(id); - const navigate = useNavigate(); const replyRef = useRef(null); + const { id } = useParams(); + const { status, data } = useEvent(id); + const { getEventThread } = useNostr(); + const [isCopy, setIsCopy] = useState(false); const share = async () => { @@ -37,9 +45,24 @@ export function TextNoteScreen() { }; const renderKind = (event: NDKEvent) => { + const thread = getEventThread(event.tags); switch (event.kind) { case NDKKind.Text: - return ; + return ( + <> + {thread ? ( +
+
+ {thread.rootEventId ? ( + + ) : null} + {thread.replyEventId ? : null} +
+
+ ) : null} + + + ); default: return ; } diff --git a/src/shared/notes/actions/more.tsx b/src/shared/notes/actions/more.tsx index cd588544..d2f4d9e9 100644 --- a/src/shared/notes/actions/more.tsx +++ b/src/shared/notes/actions/more.tsx @@ -30,20 +30,12 @@ export function MoreActions({ id, pubkey }: { id: string; pubkey: string }) { - - - - Focus - - + @@ -52,7 +44,7 @@ export function MoreActions({ id, pubkey }: { id: string; pubkey: string }) { @@ -60,7 +52,7 @@ export function MoreActions({ id, pubkey }: { id: string; pubkey: string }) { View profile diff --git a/src/shared/notes/mentions/note.tsx b/src/shared/notes/mentions/note.tsx index 8b624685..07024a0f 100644 --- a/src/shared/notes/mentions/note.tsx +++ b/src/shared/notes/mentions/note.tsx @@ -14,7 +14,13 @@ import { WIDGET_KIND } from '@stores/constants'; import { useEvent } from '@utils/hooks/useEvent'; import { useWidget } from '@utils/hooks/useWidget'; -export const MentionNote = memo(function MentionNote({ id }: { id: string }) { +export const MentionNote = memo(function MentionNote({ + id, + editing, +}: { + id: string; + editing?: boolean; +}) { const { status, data } = useEvent(id); const { addWidget } = useWidget(); @@ -46,19 +52,21 @@ export const MentionNote = memo(function MentionNote({ id }: { id: string }) {
{renderKind(data)} - + {!editing ? ( + + ) : null}
); diff --git a/src/shared/notes/preview/link.tsx b/src/shared/notes/preview/link.tsx index 2eb7aabf..1d7a5296 100644 --- a/src/shared/notes/preview/link.tsx +++ b/src/shared/notes/preview/link.tsx @@ -12,11 +12,11 @@ export function LinkPreview({ url }: { url: string }) { if (status === 'pending') { return ( -
-
+
+
-
-
+
+
{domain.hostname} diff --git a/src/utils/hooks/useEvent.ts b/src/utils/hooks/useEvent.ts index b4b00dab..8b27b993 100644 --- a/src/utils/hooks/useEvent.ts +++ b/src/utils/hooks/useEvent.ts @@ -1,18 +1,19 @@ import { NDKEvent } from '@nostr-dev-kit/ndk'; import { useQuery } from '@tanstack/react-query'; +import { nip19 } from 'nostr-tools'; import { AddressPointer } from 'nostr-tools/lib/types/nip19'; import { useNDK } from '@libs/ndk/provider'; -export function useEvent( - id: undefined | string, - naddr?: undefined | AddressPointer, - embed?: undefined | string -) { +export function useEvent(id: undefined | string, embed?: undefined | string) { const { ndk } = useNDK(); const { status, data } = useQuery({ queryKey: ['event', id], queryFn: async () => { + const naddr = id.startsWith('naddr') + ? (nip19.decode(id).data as AddressPointer) + : null; + // return event refer from naddr if (naddr) { const rEvents = await ndk.fetchEvents({ @@ -20,8 +21,10 @@ export function useEvent( '#d': [naddr.identifier], authors: [naddr.pubkey], }); + const rEvent = [...rEvents].slice(-1)[0]; if (!rEvent) return Promise.reject(new Error('event not found')); + return rEvent; } diff --git a/src/utils/hooks/useRichContent.tsx b/src/utils/hooks/useRichContent.tsx index 681cd039..1e188ad7 100644 --- a/src/utils/hooks/useRichContent.tsx +++ b/src/utils/hooks/useRichContent.tsx @@ -1,3 +1,4 @@ +import { nanoid } from 'nanoid'; import { nip19 } from 'nostr-tools'; import { ReactNode } from 'react'; import { Link } from 'react-router-dom'; @@ -43,13 +44,13 @@ const VIDEOS = [ ]; export function useRichContent(content: string, textmode: boolean = false) { - let parsedContent: string | ReactNode[] = content; + let parsedContent: string | ReactNode[] = content.replace(/\n+/g, '\n'); let linkPreview: string; let images: string[] = []; let videos: string[] = []; let events: string[] = []; - const text = content.replace(/\n+/g, '\n'); + const text = parsedContent; const words = text.split(/( |\n)/); if (!textmode) { @@ -151,8 +152,8 @@ export function useRichContent(content: string, textmode: boolean = false) { ); }); - parsedContent = reactStringReplace(parsedContent, '\n', (match, i) => { - return
; + parsedContent = reactStringReplace(parsedContent, '\n', () => { + return
; }); if (typeof parsedContent[0] === 'string') {