polish
This commit is contained in:
@@ -80,7 +80,6 @@
|
|||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-hook-form": "^7.48.2",
|
"react-hook-form": "^7.48.2",
|
||||||
"react-hotkeys-hook": "^4.4.1",
|
"react-hotkeys-hook": "^4.4.1",
|
||||||
"react-medium-image-zoom": "^5.1.8",
|
|
||||||
"react-router-dom": "^6.18.0",
|
"react-router-dom": "^6.18.0",
|
||||||
"react-string-replace": "^1.1.1",
|
"react-string-replace": "^1.1.1",
|
||||||
"reactflow": "^11.10.1",
|
"reactflow": "^11.10.1",
|
||||||
|
|||||||
13
pnpm-lock.yaml
generated
13
pnpm-lock.yaml
generated
@@ -191,9 +191,6 @@ dependencies:
|
|||||||
react-hotkeys-hook:
|
react-hotkeys-hook:
|
||||||
specifier: ^4.4.1
|
specifier: ^4.4.1
|
||||||
version: 4.4.1(react-dom@18.2.0)(react@18.2.0)
|
version: 4.4.1(react-dom@18.2.0)(react@18.2.0)
|
||||||
react-medium-image-zoom:
|
|
||||||
specifier: ^5.1.8
|
|
||||||
version: 5.1.8(react-dom@18.2.0)(react@18.2.0)
|
|
||||||
react-router-dom:
|
react-router-dom:
|
||||||
specifier: ^6.18.0
|
specifier: ^6.18.0
|
||||||
version: 6.18.0(react-dom@18.2.0)(react@18.2.0)
|
version: 6.18.0(react-dom@18.2.0)(react@18.2.0)
|
||||||
@@ -5405,16 +5402,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
|
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/react-medium-image-zoom@5.1.8(react-dom@18.2.0)(react@18.2.0):
|
|
||||||
resolution: {integrity: sha512-2X4oLlEopIWg7qalR1Qpy4gPrU9CTF0DvJ7HNu5u/NwdyQWupEsje2vuMbjBz7+np8MmQ4DKJ6zGr1ofCuzB3g==}
|
|
||||||
peerDependencies:
|
|
||||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
|
||||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
|
||||||
dependencies:
|
|
||||||
react: 18.2.0
|
|
||||||
react-dom: 18.2.0(react@18.2.0)
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/react-remove-scroll-bar@2.3.4(@types/react@18.2.37)(react@18.2.0):
|
/react-remove-scroll-bar@2.3.4(@types/react@18.2.37)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-63C4YQBUt0m6ALadE9XV56hV8BgJWDmmTPY758iIJjfQKt2nYwoUrPk0LXRXcB/yIj82T1/Ixfdpdk68LwIB0A==}
|
resolution: {integrity: sha512-63C4YQBUt0m6ALadE9XV56hV8BgJWDmmTPY758iIJjfQKt2nYwoUrPk0LXRXcB/yIj82T1/Ixfdpdk68LwIB0A==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|||||||
@@ -87,9 +87,9 @@ export function ArticleNoteScreen() {
|
|||||||
)}
|
)}
|
||||||
<div ref={replyRef} className="px-3">
|
<div ref={replyRef} className="px-3">
|
||||||
<div className="mb-3 border-b border-neutral-100 pb-3 dark:border-neutral-900">
|
<div className="mb-3 border-b border-neutral-100 pb-3 dark:border-neutral-900">
|
||||||
<NoteReplyForm id={id} />
|
<NoteReplyForm eventId={id} />
|
||||||
</div>
|
</div>
|
||||||
<ReplyList id={id} />
|
<ReplyList eventId={id} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,14 +6,7 @@ import { useRef, useState } from 'react';
|
|||||||
import { useNavigate, useParams } from 'react-router-dom';
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
|
|
||||||
import { ArrowLeftIcon, CheckCircleIcon, ReplyIcon, ShareIcon } from '@shared/icons';
|
import { ArrowLeftIcon, CheckCircleIcon, ReplyIcon, ShareIcon } from '@shared/icons';
|
||||||
import {
|
import { MemoizedTextKind, NoteActions, NoteReplyForm, UnknownNote } from '@shared/notes';
|
||||||
ArticleNote,
|
|
||||||
FileNote,
|
|
||||||
NoteActions,
|
|
||||||
NoteReplyForm,
|
|
||||||
TextNote,
|
|
||||||
UnknownNote,
|
|
||||||
} from '@shared/notes';
|
|
||||||
import { ReplyList } from '@shared/notes/replies/list';
|
import { ReplyList } from '@shared/notes/replies/list';
|
||||||
import { User } from '@shared/user';
|
import { User } from '@shared/user';
|
||||||
|
|
||||||
@@ -46,11 +39,7 @@ export function TextNoteScreen() {
|
|||||||
const renderKind = (event: NDKEvent) => {
|
const renderKind = (event: NDKEvent) => {
|
||||||
switch (event.kind) {
|
switch (event.kind) {
|
||||||
case NDKKind.Text:
|
case NDKKind.Text:
|
||||||
return <TextNote content={event.content} />;
|
return <MemoizedTextKind content={event.content} />;
|
||||||
case NDKKind.Article:
|
|
||||||
return <ArticleNote event={event} />;
|
|
||||||
case 1063:
|
|
||||||
return <FileNote event={event} />;
|
|
||||||
default:
|
default:
|
||||||
return <UnknownNote event={event} />;
|
return <UnknownNote event={event} />;
|
||||||
}
|
}
|
||||||
@@ -106,9 +95,9 @@ export function TextNoteScreen() {
|
|||||||
)}
|
)}
|
||||||
<div ref={replyRef} className="px-3">
|
<div ref={replyRef} className="px-3">
|
||||||
<div className="mb-3 border-b border-neutral-100 pb-3 dark:border-neutral-900">
|
<div className="mb-3 border-b border-neutral-100 pb-3 dark:border-neutral-900">
|
||||||
<NoteReplyForm id={id} />
|
<NoteReplyForm eventId={id} />
|
||||||
</div>
|
</div>
|
||||||
<ReplyList id={id} />
|
<ReplyList eventId={id} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ export function FileNote({ event }: { event: NDKEvent }) {
|
|||||||
<div className="mb-3 h-min w-full px-3">
|
<div className="mb-3 h-min w-full px-3">
|
||||||
<div className="relative flex flex-col gap-2 overflow-hidden rounded-xl bg-neutral-50 pt-3 dark:bg-neutral-950">
|
<div className="relative flex flex-col gap-2 overflow-hidden rounded-xl bg-neutral-50 pt-3 dark:bg-neutral-950">
|
||||||
<User pubkey={event.pubkey} time={event.created_at} eventId={event.id} />
|
<User pubkey={event.pubkey} time={event.created_at} eventId={event.id} />
|
||||||
<div>{renderFileType()}</div>
|
<div className="relative mt-2">{renderFileType()}</div>
|
||||||
<NoteActions id={event.id} pubkey={event.pubkey} />
|
<NoteActions id={event.id} pubkey={event.pubkey} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,15 +1,29 @@
|
|||||||
import { downloadDir } from '@tauri-apps/api/path';
|
import { downloadDir } from '@tauri-apps/api/path';
|
||||||
|
import { Window } from '@tauri-apps/api/window';
|
||||||
import { download } from '@tauri-apps/plugin-upload';
|
import { download } from '@tauri-apps/plugin-upload';
|
||||||
import { SyntheticEvent } from 'react';
|
import { SyntheticEvent, useState } from 'react';
|
||||||
import Zoom from 'react-medium-image-zoom';
|
|
||||||
|
|
||||||
import { CancelIcon, DownloadIcon } from '@shared/icons';
|
import { CheckCircleIcon, DownloadIcon } from '@shared/icons';
|
||||||
|
|
||||||
export function ImagePreview({ url }: { url: string }) {
|
export function ImagePreview({ url }: { url: string }) {
|
||||||
const downloadImage = async (url: string) => {
|
const [downloaded, setDownloaded] = useState(false);
|
||||||
|
|
||||||
|
const downloadImage = async (e: { stopPropagation: () => void }) => {
|
||||||
|
try {
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
const downloadDirPath = await downloadDir();
|
const downloadDirPath = await downloadDir();
|
||||||
const filename = url.substring(url.lastIndexOf('/') + 1);
|
const filename = url.substring(url.lastIndexOf('/') + 1);
|
||||||
return await download(url, downloadDirPath + `/${filename}`);
|
await download(url, downloadDirPath + `/${filename}`);
|
||||||
|
|
||||||
|
setDownloaded(true);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const open = () => {
|
||||||
|
return new Window('image-viewer', { url, title: 'Image Viewer' });
|
||||||
};
|
};
|
||||||
|
|
||||||
const fallback = (event: SyntheticEvent<HTMLImageElement, Event>) => {
|
const fallback = (event: SyntheticEvent<HTMLImageElement, Event>) => {
|
||||||
@@ -17,8 +31,8 @@ export function ImagePreview({ url }: { url: string }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Zoom key={url} zoomMargin={50} IconUnzoom={() => <CancelIcon className="h-4 w-4" />}>
|
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
|
||||||
<div className="group relative my-2">
|
<div onClick={open} className="group relative my-2">
|
||||||
<img
|
<img
|
||||||
src={url}
|
src={url}
|
||||||
alt={url}
|
alt={url}
|
||||||
@@ -30,12 +44,15 @@ export function ImagePreview({ url }: { url: string }) {
|
|||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => downloadImage(url)}
|
onClick={(e) => downloadImage(e)}
|
||||||
className="absolute right-2 top-2 hidden h-10 w-10 items-center justify-center rounded-lg bg-black/50 backdrop-blur-xl group-hover:inline-flex hover:bg-blue-500"
|
className="absolute right-2 top-2 z-10 hidden h-10 w-10 items-center justify-center rounded-lg bg-blue-500 group-hover:inline-flex hover:bg-blue-600"
|
||||||
>
|
>
|
||||||
<DownloadIcon className="h-4 w-4 text-white" />
|
{downloaded ? (
|
||||||
|
<CheckCircleIcon className="h-5 w-5 text-white" />
|
||||||
|
) : (
|
||||||
|
<DownloadIcon className="h-5 w-5 text-white" />
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</Zoom>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export function TextNote({ event }: { event: NDKEvent }) {
|
|||||||
const { addWidget } = useWidget();
|
const { addWidget } = useWidget();
|
||||||
const { getEventThread } = useNostr();
|
const { getEventThread } = useNostr();
|
||||||
|
|
||||||
const thread = getEventThread(event);
|
const thread = getEventThread(event.tags);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mb-3 h-min w-full px-3">
|
<div className="mb-3 h-min w-full px-3">
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { WVList } from 'virtua';
|
|||||||
|
|
||||||
import { LoaderIcon } from '@shared/icons';
|
import { LoaderIcon } from '@shared/icons';
|
||||||
import {
|
import {
|
||||||
|
ChildNote,
|
||||||
MemoizedArticleKind,
|
MemoizedArticleKind,
|
||||||
MemoizedFileKind,
|
MemoizedFileKind,
|
||||||
MemoizedTextKind,
|
MemoizedTextKind,
|
||||||
@@ -16,16 +17,33 @@ import { User } from '@shared/user';
|
|||||||
import { WidgetWrapper } from '@shared/widgets';
|
import { WidgetWrapper } from '@shared/widgets';
|
||||||
|
|
||||||
import { useEvent } from '@utils/hooks/useEvent';
|
import { useEvent } from '@utils/hooks/useEvent';
|
||||||
|
import { useNostr } from '@utils/hooks/useNostr';
|
||||||
import { Widget } from '@utils/types';
|
import { Widget } from '@utils/types';
|
||||||
|
|
||||||
export function ThreadWidget({ widget }: { widget: Widget }) {
|
export function ThreadWidget({ widget }: { widget: Widget }) {
|
||||||
const { status, data } = useEvent(widget.content);
|
const { status, data } = useEvent(widget.content);
|
||||||
|
const { getEventThread } = useNostr();
|
||||||
|
|
||||||
const renderKind = useCallback(
|
const renderKind = useCallback(
|
||||||
(event: NDKEvent) => {
|
(event: NDKEvent) => {
|
||||||
|
const thread = getEventThread(event.tags);
|
||||||
switch (event.kind) {
|
switch (event.kind) {
|
||||||
case NDKKind.Text:
|
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} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
case NDKKind.Article:
|
case NDKKind.Article:
|
||||||
return <MemoizedArticleKind id={event.id} tags={event.tags} />;
|
return <MemoizedArticleKind id={event.id} tags={event.tags} />;
|
||||||
case 1063:
|
case 1063:
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
import { NDKEvent, NDKFilter, NDKKind, NDKSubscription } from '@nostr-dev-kit/ndk';
|
import {
|
||||||
|
NDKEvent,
|
||||||
|
NDKFilter,
|
||||||
|
NDKKind,
|
||||||
|
NDKSubscription,
|
||||||
|
NDKTag,
|
||||||
|
} from '@nostr-dev-kit/ndk';
|
||||||
import { open } from '@tauri-apps/plugin-dialog';
|
import { open } from '@tauri-apps/plugin-dialog';
|
||||||
import { readBinaryFile } from '@tauri-apps/plugin-fs';
|
import { readBinaryFile } from '@tauri-apps/plugin-fs';
|
||||||
import { fetch } from '@tauri-apps/plugin-http';
|
import { fetch } from '@tauri-apps/plugin-http';
|
||||||
@@ -50,19 +56,29 @@ export function useNostr() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getEventThread = (event: NDKEvent) => {
|
const getEventThread = (tags: NDKTag[]) => {
|
||||||
let rootEventId: string;
|
let rootEventId: string = null;
|
||||||
let replyEventId: string;
|
let replyEventId: string = null;
|
||||||
|
|
||||||
if (event.tags?.[0]?.[0] === 'e' && !event.tags?.[0]?.[3]) {
|
const events = tags.filter((el) => el[0] === 'e');
|
||||||
rootEventId = event.tags[0][1];
|
|
||||||
|
if (!events.length) return null;
|
||||||
|
|
||||||
|
if (events.length === 1)
|
||||||
|
return {
|
||||||
|
rootEventId: events[0][1],
|
||||||
|
replyEventId: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (events.length > 1) {
|
||||||
|
rootEventId = events.find((el) => el[3] === 'root')?.[1];
|
||||||
|
replyEventId = events.find((el) => el[3] === 'reply')?.[1];
|
||||||
|
|
||||||
|
if (!rootEventId && !replyEventId) {
|
||||||
|
rootEventId = events[0][1];
|
||||||
|
replyEventId = events[1][1];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rootEventId = event.tags.find((el) => el[3] === 'root')?.[1] || null;
|
|
||||||
// eslint-disable-next-line prefer-const
|
|
||||||
replyEventId = event.tags.find((el) => el[3] === 'reply')?.[1] || null;
|
|
||||||
|
|
||||||
if (!rootEventId && !replyEventId) return null;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
rootEventId,
|
rootEventId,
|
||||||
|
|||||||
Reference in New Issue
Block a user