mirror of
https://github.com/luminous-devs/lume.git
synced 2024-10-02 09:50:47 +00:00
smal fixes and update article layout
This commit is contained in:
parent
d5647d7452
commit
fee4ad7b98
@ -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",
|
||||
|
@ -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
|
||||
|
120
src/app.css
120
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;
|
||||
}
|
||||
}
|
||||
|
@ -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 (
|
||||
<div
|
||||
@ -20,20 +15,11 @@ export function ChatMessage({ message, self }: { message: NDKEvent; self: boolea
|
||||
: 'rounded-r-xl bg-neutral-200 text-neutral-900 dark:bg-neutral-800 dark:text-neutral-100'
|
||||
)}
|
||||
>
|
||||
{!richContent ? (
|
||||
{!decryptedContent ? (
|
||||
<p>Decrypting...</p>
|
||||
) : (
|
||||
<div>
|
||||
<p className="select-text whitespace-pre-line">{richContent.parsed}</p>
|
||||
<div>
|
||||
{richContent.images.length > 0 && <ImagePreview urls={richContent.images} />}
|
||||
{richContent.videos.length > 0 && <VideoPreview urls={richContent.videos} />}
|
||||
{richContent.links.length > 0 && <LinkPreview urls={richContent.links} />}
|
||||
{richContent.notes.length > 0 &&
|
||||
richContent.notes.map((note: string) => (
|
||||
<MentionNote key={note} id={note} />
|
||||
))}
|
||||
</div>
|
||||
<p className="select-text whitespace-pre-line">{decryptedContent}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
@ -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() {
|
||||
<div className="flex h-16 w-full items-center justify-between border-t border-neutral-100 dark:border-neutral-900">
|
||||
<div className="inline-flex items-center gap-3">
|
||||
<span className="text-sm font-medium tabular-nums text-neutral-600 dark:text-neutral-400">
|
||||
{editor?.storage?.characterCount.characters()}
|
||||
{editor?.storage?.characterCount.characters()} characters
|
||||
</span>
|
||||
<span className="text-sm font-medium tabular-nums text-neutral-600 dark:text-neutral-400">
|
||||
-
|
||||
|
@ -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') && (
|
||||
<div className="relative max-w-lg">
|
||||
<MentionNote id={searchParams.get('id')} />
|
||||
<MentionNote id={searchParams.get('id')} editing />
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSearchParams({})}
|
||||
className="absolute right-3 top-3 inline-flex h-6 w-6 items-center justify-center rounded bg-neutral-300 px-2 dark:bg-neutral-700"
|
||||
className="absolute right-3 top-3 inline-flex h-6 w-6 items-center justify-center rounded bg-neutral-200 px-2 dark:bg-neutral-800"
|
||||
>
|
||||
<CancelIcon className="h-4 w-4" />
|
||||
<CancelIcon className="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex h-16 w-full items-center justify-between border-t border-neutral-100 dark:border-neutral-900">
|
||||
<span className="text-sm font-medium tabular-nums text-neutral-600 dark:text-neutral-400">
|
||||
{editor?.storage?.characterCount.characters()}
|
||||
{editor?.storage?.characterCount.characters()} characters
|
||||
</span>
|
||||
<div className="flex items-center">
|
||||
<div className="inline-flex items-center gap-2">
|
||||
|
@ -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 (
|
||||
<div className="container mx-auto grid grid-cols-8 scroll-smooth px-4">
|
||||
<div className="grid grid-cols-12 gap-4 scroll-smooth px-4">
|
||||
<div className="col-span-1">
|
||||
<div className="flex flex-col items-end gap-4">
|
||||
<button
|
||||
@ -48,7 +41,6 @@ export function ArticleNoteScreen() {
|
||||
>
|
||||
<ArrowLeftIcon className="h-5 w-5" />
|
||||
</button>
|
||||
<div className="flex flex-col divide-y divide-neutral-200 rounded-xl bg-neutral-100 dark:divide-neutral-800 dark:bg-neutral-900">
|
||||
<button
|
||||
type="button"
|
||||
onClick={share}
|
||||
@ -60,40 +52,35 @@ export function ArticleNoteScreen() {
|
||||
<ShareIcon className="h-5 w-5" />
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={scrollToReply}
|
||||
className="inline-flex h-12 w-12 items-center justify-center rounded-b-xl"
|
||||
>
|
||||
<ReplyIcon className="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative col-span-6 flex flex-col overflow-y-auto">
|
||||
<div className="mx-auto w-full max-w-2xl">
|
||||
<div className="col-span-7 overflow-y-auto px-3 xl:col-span-8">
|
||||
{status === 'pending' ? (
|
||||
<div className="px-3 py-1.5">Loading...</div>
|
||||
) : (
|
||||
<div className="flex h-min w-full flex-col px-3">
|
||||
<div className="mb-3 border-b border-neutral-100 pb-3 dark:border-neutral-900">
|
||||
<User pubkey={data.pubkey} time={data.created_at} variant="thread" />
|
||||
<div className="mt-3">{data.content}</div>
|
||||
<div className="mt-3">
|
||||
<NoteActions id={id} pubkey={data.pubkey} extraButtons={false} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Markdown
|
||||
options={{
|
||||
overrides: {
|
||||
a: {
|
||||
props: {
|
||||
className: 'text-blue-500 hover:text-blue-600',
|
||||
target: '_blank',
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
className="break-p prose-lg prose-neutral dark:prose-invert prose-ul:list-disc"
|
||||
>
|
||||
{data.content}
|
||||
</Markdown>
|
||||
)}
|
||||
<div ref={replyRef} className="px-3">
|
||||
</div>
|
||||
<div className="col-span-4 border-l border-neutral-100 px-3 dark:border-neutral-900 xl:col-span-3">
|
||||
<div className="mb-3 border-b border-neutral-100 pb-3 dark:border-neutral-900">
|
||||
<NoteReplyForm eventId={id} />
|
||||
</div>
|
||||
<ReplyList eventId={id} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-1" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -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 <MemoizedTextKind content={event.content} />;
|
||||
return (
|
||||
<>
|
||||
{thread ? (
|
||||
<div className="mb-2 w-full px-3">
|
||||
<div className="flex h-min w-full flex-col gap-3 rounded-lg bg-neutral-100 p-3 dark:bg-neutral-900">
|
||||
{thread.rootEventId ? (
|
||||
<ChildNote id={thread.rootEventId} isRoot />
|
||||
) : null}
|
||||
{thread.replyEventId ? <ChildNote id={thread.replyEventId} /> : null}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
<MemoizedTextKind content={event.content} />
|
||||
</>
|
||||
);
|
||||
default:
|
||||
return <UnknownNote event={event} />;
|
||||
}
|
||||
|
@ -30,20 +30,12 @@ export function MoreActions({ id, pubkey }: { id: string; pubkey: string }) {
|
||||
</button>
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Portal>
|
||||
<DropdownMenu.Content className="flex w-[200px] flex-col overflow-hidden rounded-xl border border-neutral-300 bg-neutral-200 focus:outline-none dark:border-neutral-700 dark:bg-neutral-800">
|
||||
<DropdownMenu.Item asChild>
|
||||
<Link
|
||||
to={`/notes/text/${id}`}
|
||||
className="inline-flex h-10 items-center px-4 text-sm text-neutral-900 hover:bg-neutral-300 focus:outline-none dark:text-neutral-100 dark:hover:bg-neutral-700"
|
||||
>
|
||||
Focus
|
||||
</Link>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Content className="flex w-[200px] flex-col overflow-hidden rounded-xl border border-neutral-200 bg-neutral-100 focus:outline-none dark:border-neutral-800 dark:bg-neutral-900">
|
||||
<DropdownMenu.Item asChild>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => copyLink()}
|
||||
className="inline-flex h-10 items-center px-4 text-sm text-neutral-900 hover:bg-neutral-300 focus:outline-none dark:text-neutral-100 dark:hover:bg-neutral-700"
|
||||
className="inline-flex h-10 items-center px-4 text-sm text-neutral-900 hover:bg-neutral-200 focus:outline-none dark:text-neutral-100 dark:hover:bg-neutral-800"
|
||||
>
|
||||
Copy shareable link
|
||||
</button>
|
||||
@ -52,7 +44,7 @@ export function MoreActions({ id, pubkey }: { id: string; pubkey: string }) {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => copyID()}
|
||||
className="inline-flex h-10 items-center px-4 text-sm text-neutral-900 hover:bg-neutral-300 focus:outline-none dark:text-neutral-100 dark:hover:bg-neutral-700"
|
||||
className="inline-flex h-10 items-center px-4 text-sm text-neutral-900 hover:bg-neutral-200 focus:outline-none dark:text-neutral-100 dark:hover:bg-neutral-800"
|
||||
>
|
||||
Copy ID
|
||||
</button>
|
||||
@ -60,7 +52,7 @@ export function MoreActions({ id, pubkey }: { id: string; pubkey: string }) {
|
||||
<DropdownMenu.Item asChild>
|
||||
<Link
|
||||
to={`/users/${pubkey}`}
|
||||
className="inline-flex h-10 items-center px-4 text-sm text-neutral-900 hover:bg-neutral-300 focus:outline-none dark:text-neutral-100 dark:hover:bg-neutral-700"
|
||||
className="inline-flex h-10 items-center px-4 text-sm text-neutral-900 hover:bg-neutral-200 focus:outline-none dark:text-neutral-100 dark:hover:bg-neutral-800"
|
||||
>
|
||||
View profile
|
||||
</Link>
|
||||
|
@ -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,6 +52,7 @@ export const MentionNote = memo(function MentionNote({ id }: { id: string }) {
|
||||
</div>
|
||||
<div className="mt-1 px-3 pb-3">
|
||||
{renderKind(data)}
|
||||
{!editing ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
@ -59,6 +66,7 @@ export const MentionNote = memo(function MentionNote({ id }: { id: string }) {
|
||||
>
|
||||
Show more
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -12,11 +12,11 @@ export function LinkPreview({ url }: { url: string }) {
|
||||
|
||||
if (status === 'pending') {
|
||||
return (
|
||||
<div className="mt-2 flex w-full flex-col rounded-lg border border-neutral-300 bg-neutral-200 dark:border-neutral-700 dark:bg-neutral-800">
|
||||
<div className="h-44 w-full animate-pulse bg-neutral-400 dark:bg-neutral-600" />
|
||||
<div className="my-2 flex w-full flex-col rounded-lg bg-neutral-100 dark:bg-neutral-900">
|
||||
<div className="h-48 w-full animate-pulse bg-neutral-300 dark:bg-neutral-700" />
|
||||
<div className="flex flex-col gap-2 px-3 py-3">
|
||||
<div className="h-3 w-2/3 animate-pulse rounded bg-neutral-400 dark:bg-neutral-600" />
|
||||
<div className="h-3 w-3/4 animate-pulse rounded bg-neutral-400 dark:bg-neutral-600" />
|
||||
<div className="h-3 w-2/3 animate-pulse rounded bg-neutral-300 dark:bg-neutral-700" />
|
||||
<div className="h-3 w-3/4 animate-pulse rounded bg-neutral-300 dark:bg-neutral-700" />
|
||||
<span className="mt-2.5 text-sm leading-none text-neutral-600 dark:text-neutral-400">
|
||||
{domain.hostname}
|
||||
</span>
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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 <div key={'n-' + i} className="h-2" />;
|
||||
parsedContent = reactStringReplace(parsedContent, '\n', () => {
|
||||
return <div key={nanoid()} className="h-3" />;
|
||||
});
|
||||
|
||||
if (typeof parsedContent[0] === 'string') {
|
||||
|
Loading…
Reference in New Issue
Block a user