mirror of
https://github.com/luminous-devs/lume.git
synced 2024-09-19 19:46:34 +00:00
refactor note component
This commit is contained in:
parent
41460436df
commit
1f18d8bb44
16
package.json
16
package.json
@ -17,7 +17,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@headlessui/react": "^1.7.15",
|
"@headlessui/react": "^1.7.15",
|
||||||
"@nostr-dev-kit/ndk": "^0.7.5",
|
"@nostr-dev-kit/ndk": "^0.7.6",
|
||||||
"@nostr-fetch/adapter-ndk": "^0.11.0",
|
"@nostr-fetch/adapter-ndk": "^0.11.0",
|
||||||
"@radix-ui/react-popover": "^1.0.6",
|
"@radix-ui/react-popover": "^1.0.6",
|
||||||
"@radix-ui/react-tooltip": "^1.0.6",
|
"@radix-ui/react-tooltip": "^1.0.6",
|
||||||
@ -28,7 +28,7 @@
|
|||||||
"cheerio": "1.0.0-rc.12",
|
"cheerio": "1.0.0-rc.12",
|
||||||
"dayjs": "^1.11.9",
|
"dayjs": "^1.11.9",
|
||||||
"destr": "^1.2.2",
|
"destr": "^1.2.2",
|
||||||
"framer-motion": "^10.12.18",
|
"framer-motion": "^10.12.20",
|
||||||
"get-urls": "^11.0.0",
|
"get-urls": "^11.0.0",
|
||||||
"immer": "^10.0.2",
|
"immer": "^10.0.2",
|
||||||
"light-bolt11-decoder": "^3.0.0",
|
"light-bolt11-decoder": "^3.0.0",
|
||||||
@ -41,7 +41,7 @@
|
|||||||
"react-player": "^2.12.0",
|
"react-player": "^2.12.0",
|
||||||
"react-router-dom": "^6.14.1",
|
"react-router-dom": "^6.14.1",
|
||||||
"react-string-replace": "^1.1.1",
|
"react-string-replace": "^1.1.1",
|
||||||
"react-virtuoso": "^4.3.11",
|
"react-virtuoso": "^4.4.0",
|
||||||
"slate": "^0.94.1",
|
"slate": "^0.94.1",
|
||||||
"slate-history": "^0.93.0",
|
"slate-history": "^0.93.0",
|
||||||
"slate-react": "^0.94.2",
|
"slate-react": "^0.94.2",
|
||||||
@ -56,8 +56,8 @@
|
|||||||
"@tauri-apps/cli": "^1.4.0",
|
"@tauri-apps/cli": "^1.4.0",
|
||||||
"@trivago/prettier-plugin-sort-imports": "^4.1.1",
|
"@trivago/prettier-plugin-sort-imports": "^4.1.1",
|
||||||
"@types/node": "^18.16.19",
|
"@types/node": "^18.16.19",
|
||||||
"@types/react": "^18.2.14",
|
"@types/react": "^18.2.15",
|
||||||
"@types/react-dom": "^18.2.6",
|
"@types/react-dom": "^18.2.7",
|
||||||
"@types/youtube-player": "^5.5.7",
|
"@types/youtube-player": "^5.5.7",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.62.0",
|
"@typescript-eslint/eslint-plugin": "^5.62.0",
|
||||||
"@typescript-eslint/parser": "^5.62.0",
|
"@typescript-eslint/parser": "^5.62.0",
|
||||||
@ -73,13 +73,13 @@
|
|||||||
"eslint-plugin-simple-import-sort": "^10.0.0",
|
"eslint-plugin-simple-import-sort": "^10.0.0",
|
||||||
"husky": "^8.0.3",
|
"husky": "^8.0.3",
|
||||||
"lint-staged": "^13.2.3",
|
"lint-staged": "^13.2.3",
|
||||||
"postcss": "^8.4.25",
|
"postcss": "^8.4.26",
|
||||||
"prettier": "^2.8.8",
|
"prettier": "^2.8.8",
|
||||||
"prettier-plugin-tailwindcss": "^0.3.0",
|
"prettier-plugin-tailwindcss": "^0.3.0",
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"tailwindcss": "^3.3.2",
|
"tailwindcss": "^3.3.3",
|
||||||
"typescript": "^4.9.5",
|
"typescript": "^4.9.5",
|
||||||
"vite": "^4.4.2",
|
"vite": "^4.4.4",
|
||||||
"vite-plugin-top-level-await": "^1.3.1",
|
"vite-plugin-top-level-await": "^1.3.1",
|
||||||
"vite-tsconfig-paths": "^4.2.0"
|
"vite-tsconfig-paths": "^4.2.0"
|
||||||
}
|
}
|
||||||
|
648
pnpm-lock.yaml
648
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
@ -5,8 +5,6 @@ import { useLiveThread } from '@app/space/hooks/useLiveThread';
|
|||||||
|
|
||||||
import { getNoteByID } from '@libs/storage';
|
import { getNoteByID } from '@libs/storage';
|
||||||
|
|
||||||
import { Kind1 } from '@shared/notes/contents/kind1';
|
|
||||||
import { Kind1063 } from '@shared/notes/contents/kind1063';
|
|
||||||
import { NoteMetadata } from '@shared/notes/metadata';
|
import { NoteMetadata } from '@shared/notes/metadata';
|
||||||
import { NoteReplyForm } from '@shared/notes/replies/form';
|
import { NoteReplyForm } from '@shared/notes/replies/form';
|
||||||
import { RepliesList } from '@shared/notes/replies/list';
|
import { RepliesList } from '@shared/notes/replies/list';
|
||||||
@ -41,8 +39,6 @@ export function NoteScreen() {
|
|||||||
<div className="rounded-md bg-zinc-900 px-5 pt-5">
|
<div className="rounded-md bg-zinc-900 px-5 pt-5">
|
||||||
<User pubkey={data.pubkey} time={data.created_at} />
|
<User pubkey={data.pubkey} time={data.created_at} />
|
||||||
<div className="mt-3">
|
<div className="mt-3">
|
||||||
{data.kind === 1 && <Kind1 content={data.content} />}
|
|
||||||
{data.kind === 1063 && <Kind1063 metadata={data.tags} />}
|
|
||||||
<NoteMetadata id={data.event_id || id} eventPubkey={data.pubkey} />
|
<NoteMetadata id={data.event_id || id} eventPubkey={data.pubkey} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
import { useInfiniteQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useInfiniteQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
import { useVirtualizer } from '@tanstack/react-virtual';
|
||||||
import { useEffect, useRef } from 'react';
|
import { useCallback, useEffect, useRef } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { getNotesByAuthors, removeBlock } from '@libs/storage';
|
import { getNotesByAuthors, removeBlock } from '@libs/storage';
|
||||||
|
|
||||||
import { Note } from '@shared/notes/note';
|
import { NoteKind_1, NoteKind_1063, NoteThread, Repost } from '@shared/notes';
|
||||||
|
import { NoteKindUnsupport } from '@shared/notes/kinds/unsupport';
|
||||||
import { NoteSkeleton } from '@shared/notes/skeleton';
|
import { NoteSkeleton } from '@shared/notes/skeleton';
|
||||||
import { TitleBar } from '@shared/titleBar';
|
import { TitleBar } from '@shared/titleBar';
|
||||||
|
|
||||||
|
import { LumeEvent } from '@utils/types';
|
||||||
|
|
||||||
const ITEM_PER_PAGE = 10;
|
const ITEM_PER_PAGE = 10;
|
||||||
|
|
||||||
export function FeedBlock({ params }: { params: any }) {
|
export function FeedBlock({ params }: { params: any }) {
|
||||||
@ -55,16 +57,70 @@ export function FeedBlock({ params }: { params: any }) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const renderItem = (index: string | number) => {
|
const renderItem = useCallback(
|
||||||
const note = notes[index];
|
(index: string | number) => {
|
||||||
|
const note: LumeEvent = notes[index];
|
||||||
if (!note) return;
|
if (!note) return;
|
||||||
return (
|
switch (note.kind) {
|
||||||
<div key={index} data-index={index} ref={rowVirtualizer.measureElement}>
|
case 1: {
|
||||||
<Note event={note} />
|
const root = note.tags.find((el) => el[3] === 'root')?.[1];
|
||||||
</div>
|
const reply = note.tags.find((el) => el[3] === 'reply')?.[1];
|
||||||
);
|
if (root || reply) {
|
||||||
};
|
return (
|
||||||
|
<div
|
||||||
|
key={note.event_id || note.id}
|
||||||
|
data-index={index}
|
||||||
|
ref={rowVirtualizer.measureElement}
|
||||||
|
>
|
||||||
|
<NoteThread event={note} root={root} reply={reply} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={note.event_id || note.id}
|
||||||
|
data-index={index}
|
||||||
|
ref={rowVirtualizer.measureElement}
|
||||||
|
>
|
||||||
|
<NoteKind_1 event={note} skipMetadata={false} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 6:
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={note.event_id || note.id}
|
||||||
|
data-index={index}
|
||||||
|
ref={rowVirtualizer.measureElement}
|
||||||
|
>
|
||||||
|
<Repost key={note.event_id} event={note} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
case 1063:
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={note.event_id || note.id}
|
||||||
|
data-index={index}
|
||||||
|
ref={rowVirtualizer.measureElement}
|
||||||
|
>
|
||||||
|
<NoteKind_1063 key={note.event_id} event={note} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={note.event_id || note.id}
|
||||||
|
data-index={index}
|
||||||
|
ref={rowVirtualizer.measureElement}
|
||||||
|
>
|
||||||
|
<NoteKindUnsupport key={note.event_id} event={note} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[notes]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-[400px] shrink-0 border-r border-zinc-900">
|
<div className="w-[400px] shrink-0 border-r border-zinc-900">
|
||||||
|
@ -1,18 +1,21 @@
|
|||||||
import { useInfiniteQuery } from '@tanstack/react-query';
|
import { useInfiniteQuery } from '@tanstack/react-query';
|
||||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
import { useVirtualizer } from '@tanstack/react-virtual';
|
||||||
import { useEffect, useRef } from 'react';
|
import { useCallback, useEffect, useRef } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
import { useNewsfeed } from '@app/space/hooks/useNewsfeed';
|
import { useNewsfeed } from '@app/space/hooks/useNewsfeed';
|
||||||
|
|
||||||
import { getNotes } from '@libs/storage';
|
import { getNotes } from '@libs/storage';
|
||||||
|
|
||||||
import { Note } from '@shared/notes/note';
|
import { NoteKind_1, NoteKind_1063, NoteThread, Repost } from '@shared/notes';
|
||||||
|
import { NoteKindUnsupport } from '@shared/notes/kinds/unsupport';
|
||||||
import { NoteSkeleton } from '@shared/notes/skeleton';
|
import { NoteSkeleton } from '@shared/notes/skeleton';
|
||||||
import { TitleBar } from '@shared/titleBar';
|
import { TitleBar } from '@shared/titleBar';
|
||||||
|
|
||||||
import { useNote } from '@stores/note';
|
import { useNote } from '@stores/note';
|
||||||
|
|
||||||
|
import { LumeEvent } from '@utils/types';
|
||||||
|
|
||||||
const ITEM_PER_PAGE = 10;
|
const ITEM_PER_PAGE = 10;
|
||||||
|
|
||||||
export function FollowingBlock() {
|
export function FollowingBlock() {
|
||||||
@ -66,19 +69,76 @@ export function FollowingBlock() {
|
|||||||
toggleHasNewNote(false);
|
toggleHasNewNote(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderItem = (index: string | number) => {
|
const renderItem = useCallback(
|
||||||
const note = notes[index];
|
(index: string | number) => {
|
||||||
if (!note) return;
|
const note: LumeEvent = notes[index];
|
||||||
return (
|
if (!note) return;
|
||||||
<div
|
switch (note.kind) {
|
||||||
key={note.event_id || note.id}
|
case 1: {
|
||||||
data-index={index}
|
let root: string;
|
||||||
ref={rowVirtualizer.measureElement}
|
let reply: string;
|
||||||
>
|
if (note.tags?.[0]?.[0] === 'e' && !note.tags?.[0]?.[3]) {
|
||||||
<Note event={note} />
|
root = note.tags[0][1];
|
||||||
</div>
|
} else {
|
||||||
);
|
root = note.tags.find((el) => el[3] === 'root')?.[1];
|
||||||
};
|
reply = note.tags.find((el) => el[3] === 'reply')?.[1];
|
||||||
|
}
|
||||||
|
if (root || reply) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={note.event_id || note.id}
|
||||||
|
data-index={index}
|
||||||
|
ref={rowVirtualizer.measureElement}
|
||||||
|
>
|
||||||
|
<NoteThread event={note} root={root} reply={reply} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={note.event_id || note.id}
|
||||||
|
data-index={index}
|
||||||
|
ref={rowVirtualizer.measureElement}
|
||||||
|
>
|
||||||
|
<NoteKind_1 event={note} skipMetadata={false} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 6:
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={note.event_id || note.id}
|
||||||
|
data-index={index}
|
||||||
|
ref={rowVirtualizer.measureElement}
|
||||||
|
>
|
||||||
|
<Repost key={note.event_id} event={note} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
case 1063:
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={note.event_id || note.id}
|
||||||
|
data-index={index}
|
||||||
|
ref={rowVirtualizer.measureElement}
|
||||||
|
>
|
||||||
|
<NoteKind_1063 key={note.event_id} event={note} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={note.event_id || note.id}
|
||||||
|
data-index={index}
|
||||||
|
ref={rowVirtualizer.measureElement}
|
||||||
|
>
|
||||||
|
<NoteKindUnsupport key={note.event_id} event={note} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[notes]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative w-[400px] shrink-0 border-r border-zinc-900">
|
<div className="relative w-[400px] shrink-0 border-r border-zinc-900">
|
||||||
|
@ -5,8 +5,6 @@ import { useLiveThread } from '@app/space/hooks/useLiveThread';
|
|||||||
|
|
||||||
import { getNoteByID, removeBlock } from '@libs/storage';
|
import { getNoteByID, removeBlock } from '@libs/storage';
|
||||||
|
|
||||||
import { Kind1 } from '@shared/notes/contents/kind1';
|
|
||||||
import { Kind1063 } from '@shared/notes/contents/kind1063';
|
|
||||||
import { NoteMetadata } from '@shared/notes/metadata';
|
import { NoteMetadata } from '@shared/notes/metadata';
|
||||||
import { NoteReplyForm } from '@shared/notes/replies/form';
|
import { NoteReplyForm } from '@shared/notes/replies/form';
|
||||||
import { RepliesList } from '@shared/notes/replies/list';
|
import { RepliesList } from '@shared/notes/replies/list';
|
||||||
@ -53,12 +51,6 @@ export function ThreadBlock({ params }: { params: any }) {
|
|||||||
<div className="rounded-md bg-zinc-900 px-5 pt-5">
|
<div className="rounded-md bg-zinc-900 px-5 pt-5">
|
||||||
<User pubkey={data.pubkey} time={data.created_at} />
|
<User pubkey={data.pubkey} time={data.created_at} />
|
||||||
<div className="mt-3">
|
<div className="mt-3">
|
||||||
{data.kind === 1 && <Kind1 content={data.content} />}
|
|
||||||
{data.kind === 1063 && <Kind1063 metadata={data.tags} />}
|
|
||||||
<NoteMetadata
|
|
||||||
id={data.event_id || params.content}
|
|
||||||
eventPubkey={data.pubkey}
|
|
||||||
/>
|
|
||||||
<Link to={`/app/note/${params.content}`}>Focus</Link>
|
<Link to={`/app/note/${params.content}`}>Focus</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
import { Note } from '@shared/notes/note';
|
import { NoteKind_1 } from '@shared/notes';
|
||||||
import { NoteSkeleton } from '@shared/notes/skeleton';
|
import { NoteSkeleton } from '@shared/notes/skeleton';
|
||||||
import { TitleBar } from '@shared/titleBar';
|
import { TitleBar } from '@shared/titleBar';
|
||||||
|
|
||||||
@ -27,7 +27,7 @@ export function TrendingNotes() {
|
|||||||
) : (
|
) : (
|
||||||
<div className="relative flex w-full flex-col pt-1.5">
|
<div className="relative flex w-full flex-col pt-1.5">
|
||||||
{data.notes.map((item) => (
|
{data.notes.map((item) => (
|
||||||
<Note key={item.id} event={item.event} skipMetadata={true} />
|
<NoteKind_1 key={item.id} event={item.event} skipMetadata={true} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -2,8 +2,6 @@ import { useQuery } from '@tanstack/react-query';
|
|||||||
|
|
||||||
import { useNDK } from '@libs/ndk/provider';
|
import { useNDK } from '@libs/ndk/provider';
|
||||||
|
|
||||||
import { Note } from '@shared/notes/note';
|
|
||||||
|
|
||||||
import { nHoursAgo } from '@utils/date';
|
import { nHoursAgo } from '@utils/date';
|
||||||
import { LumeEvent } from '@utils/types';
|
import { LumeEvent } from '@utils/types';
|
||||||
|
|
||||||
@ -19,15 +17,5 @@ export function UserFeed({ pubkey }: { pubkey: string }) {
|
|||||||
return events as unknown as LumeEvent[];
|
return events as unknown as LumeEvent[];
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return <div className="w-full max-w-[400px] px-2 pb-10"></div>;
|
||||||
<div className="w-full max-w-[400px] px-2 pb-10">
|
|
||||||
{status === 'loading' ? (
|
|
||||||
<div className="px-3">
|
|
||||||
<p>Loading...</p>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
data.map((note: LumeEvent) => <Note key={note.id} event={note} />)
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import { NDKUserProfile } from '@nostr-dev-kit/ndk';
|
import { NDKTag, NDKUserProfile } from '@nostr-dev-kit/ndk';
|
||||||
|
import destr from 'destr';
|
||||||
import Database from 'tauri-plugin-sql-api';
|
import Database from 'tauri-plugin-sql-api';
|
||||||
|
|
||||||
import { getParentID } from '@utils/transform';
|
import { getParentID } from '@utils/transform';
|
||||||
|
import { LumeEvent } from '@utils/types';
|
||||||
|
|
||||||
let db: null | Database = null;
|
let db: null | Database = null;
|
||||||
|
|
||||||
@ -92,11 +94,19 @@ export async function getNotes(limit: number, offset: number) {
|
|||||||
const totalNotes = await countTotalNotes();
|
const totalNotes = await countTotalNotes();
|
||||||
const nextCursor = offset + limit;
|
const nextCursor = offset + limit;
|
||||||
|
|
||||||
const notes: any = { data: null, nextCursor: 0 };
|
const notes: { data: LumeEvent[] | null; nextCursor: number } = {
|
||||||
const query: any = await db.select(
|
data: null,
|
||||||
|
nextCursor: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
const query: LumeEvent[] = await db.select(
|
||||||
`SELECT * FROM notes WHERE kind IN (1, 6, 1063) GROUP BY parent_id ORDER BY created_at DESC LIMIT "${limit}" OFFSET "${offset}";`
|
`SELECT * FROM notes WHERE kind IN (1, 6, 1063) GROUP BY parent_id ORDER BY created_at DESC LIMIT "${limit}" OFFSET "${offset}";`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
query.forEach(
|
||||||
|
(el) => (el.tags = typeof el.tags === 'string' ? destr(el.tags) : el.tags)
|
||||||
|
);
|
||||||
|
|
||||||
notes['data'] = query;
|
notes['data'] = query;
|
||||||
notes['nextCursor'] = Math.round(totalNotes / nextCursor) > 1 ? nextCursor : undefined;
|
notes['nextCursor'] = Math.round(totalNotes / nextCursor) > 1 ? nextCursor : undefined;
|
||||||
|
|
||||||
@ -106,11 +116,16 @@ export async function getNotes(limit: number, offset: number) {
|
|||||||
// get all notes by pubkey
|
// get all notes by pubkey
|
||||||
export async function getNotesByPubkey(pubkey: string) {
|
export async function getNotesByPubkey(pubkey: string) {
|
||||||
const db = await connect();
|
const db = await connect();
|
||||||
const res: any = await db.select(
|
|
||||||
|
const query: LumeEvent[] = await db.select(
|
||||||
`SELECT * FROM notes WHERE pubkey == "${pubkey}" AND kind IN (1, 6, 1063) GROUP BY parent_id ORDER BY created_at DESC;`
|
`SELECT * FROM notes WHERE pubkey == "${pubkey}" AND kind IN (1, 6, 1063) GROUP BY parent_id ORDER BY created_at DESC;`
|
||||||
);
|
);
|
||||||
|
|
||||||
return res;
|
query.forEach(
|
||||||
|
(el) => (el.tags = typeof el.tags === 'string' ? destr(el.tags) : el.tags)
|
||||||
|
);
|
||||||
|
|
||||||
|
return query;
|
||||||
}
|
}
|
||||||
|
|
||||||
// get all notes by authors
|
// get all notes by authors
|
||||||
@ -121,11 +136,19 @@ export async function getNotesByAuthors(authors: string, limit: number, offset:
|
|||||||
const array = JSON.parse(authors);
|
const array = JSON.parse(authors);
|
||||||
const finalArray = `'${array.join("','")}'`;
|
const finalArray = `'${array.join("','")}'`;
|
||||||
|
|
||||||
const notes: any = { data: null, nextCursor: 0 };
|
const notes: { data: LumeEvent[] | null; nextCursor: number } = {
|
||||||
const query: any = await db.select(
|
data: null,
|
||||||
|
nextCursor: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
const query: LumeEvent[] = await db.select(
|
||||||
`SELECT * FROM notes WHERE pubkey IN (${finalArray}) AND kind IN (1, 6, 1063) GROUP BY parent_id ORDER BY created_at DESC LIMIT "${limit}" OFFSET "${offset}";`
|
`SELECT * FROM notes WHERE pubkey IN (${finalArray}) AND kind IN (1, 6, 1063) GROUP BY parent_id ORDER BY created_at DESC LIMIT "${limit}" OFFSET "${offset}";`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
query.forEach(
|
||||||
|
(el) => (el.tags = typeof el.tags === 'string' ? destr(el.tags) : el.tags)
|
||||||
|
);
|
||||||
|
|
||||||
notes['data'] = query;
|
notes['data'] = query;
|
||||||
notes['nextCursor'] = Math.round(totalNotes / nextCursor) > 1 ? nextCursor : undefined;
|
notes['nextCursor'] = Math.round(totalNotes / nextCursor) > 1 ? nextCursor : undefined;
|
||||||
|
|
||||||
@ -144,7 +167,7 @@ export async function createNote(
|
|||||||
event_id: string,
|
event_id: string,
|
||||||
pubkey: string,
|
pubkey: string,
|
||||||
kind: number,
|
kind: number,
|
||||||
tags: any,
|
tags: string[],
|
||||||
content: string,
|
content: string,
|
||||||
created_at: number
|
created_at: number
|
||||||
) {
|
) {
|
||||||
|
@ -43,4 +43,5 @@ export * from './settings';
|
|||||||
export * from './logout';
|
export * from './logout';
|
||||||
export * from './follow';
|
export * from './follow';
|
||||||
export * from './unfollow';
|
export * from './unfollow';
|
||||||
|
export * from './reaction';
|
||||||
// @endindex
|
// @endindex
|
||||||
|
37
src/shared/icons/reaction.tsx
Normal file
37
src/shared/icons/reaction.tsx
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { SVGProps } from 'react';
|
||||||
|
|
||||||
|
export function ReactionIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M19 1a.75.75 0 01.75.75v2.5h2.5a.75.75 0 110 1.5h-2.5v2.5a.75.75 0 11-1.5 0v-2.5h-2.5a.75.75 0 110-1.5h2.5v-2.5A.75.75 0 0119 1z"
|
||||||
|
clipRule="evenodd"
|
||||||
|
></path>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M10.5 9.5c0 .828-.56 1.5-1.25 1.5S8 10.328 8 9.5 8.56 8 9.25 8s1.25.672 1.25 1.5zM16 9.5c0 .828-.56 1.5-1.25 1.5s-1.25-.672-1.25-1.5.56-1.5 1.25-1.5S16 8.672 16 9.5z"
|
||||||
|
></path>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M8.642 14.298a.75.75 0 011.06 0 3.25 3.25 0 004.597 0 .75.75 0 011.06 1.06 4.75 4.75 0 01-6.717 0 .75.75 0 010-1.06z"
|
||||||
|
clipRule="evenodd"
|
||||||
|
></path>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M12 3.5a8.5 8.5 0 108.5 8.5.75.75 0 011.5 0c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2a.75.75 0 010 1.5z"
|
||||||
|
clipRule="evenodd"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
@ -2,14 +2,20 @@ import { SVGProps } from 'react';
|
|||||||
|
|
||||||
export function RepostIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
|
export function RepostIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
|
||||||
return (
|
return (
|
||||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
<path
|
<path
|
||||||
d="M17.25 21.25L20.25 18.25L17.25 15.25M6.75 2.75L3.75 5.75L6.75 8.75M5.25 5.75H20.25V10.75M3.75 13.75V18.25H18.75"
|
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
strokeWidth={1.5}
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
/>
|
strokeWidth="1.5"
|
||||||
|
d="M12 21.25c4.28 0 7.75-3.75 7.75-8.25 0-5.167-4.613-8.829-6.471-10.094-.426-.29-.988-.165-1.285.257L9.582 6.59a1.002 1.002 0 01-1.525.131c-.39-.387-1.026-.391-1.376.033C5.06 8.718 4.25 11.16 4.25 13c0 4.5 3.47 8.25 7.75 8.25zm0 0c1.657 0 3-1.533 3-3.424 0-2.084-1.663-3.601-2.513-4.24a.802.802 0 00-.974 0c-.85.639-2.513 2.156-2.513 4.24 0 1.89 1.343 3.424 3 3.424z"
|
||||||
|
></path>
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
29
src/shared/notes/actions.tsx
Normal file
29
src/shared/notes/actions.tsx
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import * as Tooltip from '@radix-ui/react-tooltip';
|
||||||
|
|
||||||
|
import { NoteReaction } from '@shared/notes/actions/reaction';
|
||||||
|
import { NoteReply } from '@shared/notes/actions/reply';
|
||||||
|
import { NoteRepost } from '@shared/notes/actions/repost';
|
||||||
|
import { NoteZap } from '@shared/notes/actions/zap';
|
||||||
|
|
||||||
|
export function NoteActions({
|
||||||
|
id,
|
||||||
|
rootID,
|
||||||
|
eventPubkey,
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
rootID?: string;
|
||||||
|
eventPubkey: string;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Tooltip.Provider>
|
||||||
|
<div className="-ml-1 mt-4 inline-flex w-full items-center justify-between">
|
||||||
|
<div className="inline-flex items-center gap-2">
|
||||||
|
<NoteReaction />
|
||||||
|
<NoteZap />
|
||||||
|
<NoteRepost id={id} pubkey={eventPubkey} />
|
||||||
|
<NoteReply id={id} rootID={rootID} pubkey={eventPubkey} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Tooltip.Provider>
|
||||||
|
);
|
||||||
|
}
|
32
src/shared/notes/actions/reaction.tsx
Normal file
32
src/shared/notes/actions/reaction.tsx
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import * as Tooltip from '@radix-ui/react-tooltip';
|
||||||
|
|
||||||
|
import { ReactionIcon } from '@shared/icons';
|
||||||
|
|
||||||
|
export function NoteReaction() {
|
||||||
|
const submit = async () => {
|
||||||
|
console.log('todo');
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip.Root delayDuration={150}>
|
||||||
|
<Tooltip.Trigger asChild>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => submit()}
|
||||||
|
className="group inline-flex h-7 w-7 items-center justify-center"
|
||||||
|
>
|
||||||
|
<ReactionIcon className="h-5 w-5 text-zinc-300 group-hover:text-red-400" />
|
||||||
|
</button>
|
||||||
|
</Tooltip.Trigger>
|
||||||
|
<Tooltip.Portal>
|
||||||
|
<Tooltip.Content
|
||||||
|
className="-left-10 select-none rounded-md bg-zinc-800/80 px-3.5 py-1.5 text-sm leading-none text-zinc-100 backdrop-blur-lg will-change-[transform,opacity] data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade"
|
||||||
|
sideOffset={5}
|
||||||
|
>
|
||||||
|
Reaction
|
||||||
|
<Tooltip.Arrow className="fill-zinc-800/80 backdrop-blur-lg" />
|
||||||
|
</Tooltip.Content>
|
||||||
|
</Tooltip.Portal>
|
||||||
|
</Tooltip.Root>
|
||||||
|
);
|
||||||
|
}
|
@ -4,37 +4,28 @@ import { ReplyIcon } from '@shared/icons';
|
|||||||
|
|
||||||
import { useComposer } from '@stores/composer';
|
import { useComposer } from '@stores/composer';
|
||||||
|
|
||||||
import { compactNumber } from '@utils/number';
|
|
||||||
|
|
||||||
export function NoteReply({
|
export function NoteReply({
|
||||||
id,
|
id,
|
||||||
rootID,
|
rootID,
|
||||||
pubkey,
|
pubkey,
|
||||||
replies,
|
|
||||||
}: {
|
}: {
|
||||||
id: string;
|
id: string;
|
||||||
rootID?: string;
|
rootID?: string;
|
||||||
pubkey: string;
|
pubkey: string;
|
||||||
replies: number;
|
|
||||||
}) {
|
}) {
|
||||||
const setReply = useComposer((state) => state.setReply);
|
const setReply = useComposer((state) => state.setReply);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip.Root delayDuration={150}>
|
<Tooltip.Root delayDuration={150}>
|
||||||
<button
|
<Tooltip.Trigger asChild>
|
||||||
type="button"
|
<button
|
||||||
onClick={() => setReply(id, rootID, pubkey)}
|
type="button"
|
||||||
className="group group inline-flex h-6 w-20 items-center gap-1.5"
|
onClick={() => setReply(id, rootID, pubkey)}
|
||||||
>
|
className="group inline-flex h-7 w-7 items-center justify-center"
|
||||||
<Tooltip.Trigger asChild>
|
>
|
||||||
<span className="inline-flex h-6 w-6 items-center justify-center rounded group-hover:bg-zinc-800">
|
<ReplyIcon className="h-5 w-5 text-zinc-300 group-hover:text-green-500" />
|
||||||
<ReplyIcon className="h-4 w-4 text-zinc-400 group-hover:text-green-500" />
|
</button>
|
||||||
</span>
|
</Tooltip.Trigger>
|
||||||
</Tooltip.Trigger>
|
|
||||||
<span className="text-base leading-none text-zinc-400 group-hover:text-zinc-100">
|
|
||||||
{compactNumber.format(replies)}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
<Tooltip.Portal>
|
<Tooltip.Portal>
|
||||||
<Tooltip.Content
|
<Tooltip.Content
|
||||||
className="-left-10 select-none rounded-md bg-zinc-800/80 px-3.5 py-1.5 text-sm leading-none text-zinc-100 backdrop-blur-lg will-change-[transform,opacity] data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade"
|
className="-left-10 select-none rounded-md bg-zinc-800/80 px-3.5 py-1.5 text-sm leading-none text-zinc-100 backdrop-blur-lg will-change-[transform,opacity] data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade"
|
@ -5,17 +5,8 @@ import { RepostIcon } from '@shared/icons';
|
|||||||
import { FULL_RELAYS } from '@stores/constants';
|
import { FULL_RELAYS } from '@stores/constants';
|
||||||
|
|
||||||
import { usePublish } from '@utils/hooks/usePublish';
|
import { usePublish } from '@utils/hooks/usePublish';
|
||||||
import { compactNumber } from '@utils/number';
|
|
||||||
|
|
||||||
export function NoteRepost({
|
export function NoteRepost({ id, pubkey }: { id: string; pubkey: string }) {
|
||||||
id,
|
|
||||||
pubkey,
|
|
||||||
reposts,
|
|
||||||
}: {
|
|
||||||
id: string;
|
|
||||||
pubkey: string;
|
|
||||||
reposts: number;
|
|
||||||
}) {
|
|
||||||
const publish = usePublish();
|
const publish = usePublish();
|
||||||
|
|
||||||
const submit = async () => {
|
const submit = async () => {
|
||||||
@ -28,20 +19,15 @@ export function NoteRepost({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip.Root delayDuration={150}>
|
<Tooltip.Root delayDuration={150}>
|
||||||
<button
|
<Tooltip.Trigger asChild>
|
||||||
type="button"
|
<button
|
||||||
onClick={() => submit()}
|
type="button"
|
||||||
className="group group inline-flex h-6 w-20 items-center gap-1.5"
|
onClick={() => submit()}
|
||||||
>
|
className="group inline-flex h-7 w-7 items-center justify-center"
|
||||||
<Tooltip.Trigger asChild>
|
>
|
||||||
<span className="inline-flex h-6 w-6 items-center justify-center rounded group-hover:bg-zinc-800">
|
<RepostIcon className="h-5 w-5 text-zinc-300 group-hover:text-blue-400" />
|
||||||
<RepostIcon className="h-4 w-4 text-zinc-400 group-hover:text-blue-400" />
|
</button>
|
||||||
</span>
|
</Tooltip.Trigger>
|
||||||
</Tooltip.Trigger>
|
|
||||||
<span className="text-base leading-none text-zinc-400 group-hover:text-zinc-100">
|
|
||||||
{compactNumber.format(reposts)}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
<Tooltip.Portal>
|
<Tooltip.Portal>
|
||||||
<Tooltip.Content
|
<Tooltip.Content
|
||||||
className="-left-10 select-none rounded-md bg-zinc-800/80 px-3.5 py-1.5 text-sm leading-none text-zinc-100 backdrop-blur-lg will-change-[transform,opacity] data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade"
|
className="-left-10 select-none rounded-md bg-zinc-800/80 px-3.5 py-1.5 text-sm leading-none text-zinc-100 backdrop-blur-lg will-change-[transform,opacity] data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade"
|
@ -2,30 +2,23 @@ import * as Tooltip from '@radix-ui/react-tooltip';
|
|||||||
|
|
||||||
import { ZapIcon } from '@shared/icons';
|
import { ZapIcon } from '@shared/icons';
|
||||||
|
|
||||||
import { compactNumber } from '@utils/number';
|
export function NoteZap() {
|
||||||
|
|
||||||
export function NoteZap({ zaps }: { zaps: number }) {
|
|
||||||
return (
|
return (
|
||||||
<Tooltip.Root delayDuration={150}>
|
<Tooltip.Root delayDuration={150}>
|
||||||
<button
|
<Tooltip.Trigger asChild>
|
||||||
type="button"
|
<button
|
||||||
className="group group inline-flex h-6 w-20 items-center gap-1.5"
|
type="button"
|
||||||
>
|
className="group inline-flex h-7 w-7 items-center justify-center"
|
||||||
<Tooltip.Trigger asChild>
|
>
|
||||||
<span className="inline-flex h-6 w-6 items-center justify-center rounded group-hover:bg-zinc-800">
|
<ZapIcon className="h-5 w-5 text-zinc-300 group-hover:text-orange-400" />
|
||||||
<ZapIcon className="h-4 w-4 text-zinc-400 group-hover:text-orange-400" />
|
</button>
|
||||||
</span>
|
</Tooltip.Trigger>
|
||||||
</Tooltip.Trigger>
|
|
||||||
<span className="text-base leading-none text-zinc-400 group-hover:text-zinc-100">
|
|
||||||
{compactNumber.format(zaps)}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
<Tooltip.Portal>
|
<Tooltip.Portal>
|
||||||
<Tooltip.Content
|
<Tooltip.Content
|
||||||
className="-left-10 select-none rounded-md bg-zinc-800/80 px-3.5 py-1.5 text-sm leading-none text-zinc-100 backdrop-blur-lg will-change-[transform,opacity] data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade"
|
className="-left-10 select-none rounded-md bg-zinc-800/80 px-3.5 py-1.5 text-sm leading-none text-zinc-100 backdrop-blur-lg will-change-[transform,opacity] data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade"
|
||||||
sideOffset={5}
|
sideOffset={5}
|
||||||
>
|
>
|
||||||
Coming Soon
|
Tip
|
||||||
<Tooltip.Arrow className="fill-zinc-800/80 backdrop-blur-lg" />
|
<Tooltip.Arrow className="fill-zinc-800/80 backdrop-blur-lg" />
|
||||||
</Tooltip.Content>
|
</Tooltip.Content>
|
||||||
</Tooltip.Portal>
|
</Tooltip.Portal>
|
@ -1,40 +0,0 @@
|
|||||||
import { ReactNode } from 'react';
|
|
||||||
|
|
||||||
import { MentionNote } from '@shared/notes/mentions/note';
|
|
||||||
import { ImagePreview } from '@shared/notes/preview/image';
|
|
||||||
import { LinkPreview } from '@shared/notes/preview/link';
|
|
||||||
import { VideoPreview } from '@shared/notes/preview/video';
|
|
||||||
|
|
||||||
export function Kind1({
|
|
||||||
content,
|
|
||||||
truncate = false,
|
|
||||||
}: {
|
|
||||||
content: {
|
|
||||||
original: string;
|
|
||||||
parsed: ReactNode[];
|
|
||||||
notes: string[];
|
|
||||||
images: string[];
|
|
||||||
videos: string[];
|
|
||||||
links: string[];
|
|
||||||
};
|
|
||||||
truncate?: boolean;
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div
|
|
||||||
className={`select-text whitespace-pre-line break-words text-base text-zinc-100 ${
|
|
||||||
truncate ? 'line-clamp-3' : ''
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{content.parsed}
|
|
||||||
</div>
|
|
||||||
{content.images.length > 0 && (
|
|
||||||
<ImagePreview urls={content.images} truncate={truncate} />
|
|
||||||
)}
|
|
||||||
{content.videos.length > 0 && <VideoPreview urls={content.videos} />}
|
|
||||||
{content.links.length > 0 && <LinkPreview urls={content.links} />}
|
|
||||||
{content.notes.length > 0 &&
|
|
||||||
content.notes.map((note: string) => <MentionNote key={note} id={note} />)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
import { NDKTag } from '@nostr-dev-kit/ndk';
|
|
||||||
|
|
||||||
import { Image } from '@shared/image';
|
|
||||||
|
|
||||||
function isImage(url: string) {
|
|
||||||
return /\.(jpg|jpeg|gif|png|webp|avif)$/.test(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Kind1063({ metadata }: { metadata: NDKTag[] }) {
|
|
||||||
const url = metadata[0][1];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="mt-3">
|
|
||||||
{isImage(url) && (
|
|
||||||
<Image
|
|
||||||
src={url}
|
|
||||||
fallback="https://void.cat/d/XTmrMkpid8DGLjv1AzdvcW"
|
|
||||||
alt="image"
|
|
||||||
className="h-auto w-full rounded-lg object-cover"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
22
src/shared/notes/index.tsx
Normal file
22
src/shared/notes/index.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
export * from './actions/reaction';
|
||||||
|
export * from './actions/reply';
|
||||||
|
export * from './actions/repost';
|
||||||
|
export * from './actions/zap';
|
||||||
|
export * from './mentions/note';
|
||||||
|
export * from './mentions/user';
|
||||||
|
export * from './preview/image';
|
||||||
|
export * from './preview/link';
|
||||||
|
export * from './preview/video';
|
||||||
|
export * from './replies/form';
|
||||||
|
export * from './replies/item';
|
||||||
|
export * from './replies/list';
|
||||||
|
export * from './kinds/kind1';
|
||||||
|
export * from './kinds/kind1063';
|
||||||
|
export * from './metadata';
|
||||||
|
export * from './users/mini';
|
||||||
|
export * from './users/repost';
|
||||||
|
export * from './kinds/thread';
|
||||||
|
export * from './kinds/repost';
|
||||||
|
export * from './kinds/sub';
|
||||||
|
export * from './skeleton';
|
||||||
|
export * from './actions';
|
52
src/shared/notes/kinds/kind1.tsx
Normal file
52
src/shared/notes/kinds/kind1.tsx
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
import { LinkPreview, NoteActions, NoteMetadata, VideoPreview } from '@shared/notes';
|
||||||
|
import { MentionNote } from '@shared/notes/mentions/note';
|
||||||
|
import { ImagePreview } from '@shared/notes/preview/image';
|
||||||
|
import { User } from '@shared/user';
|
||||||
|
|
||||||
|
import { parser } from '@utils/parser';
|
||||||
|
import { LumeEvent } from '@utils/types';
|
||||||
|
|
||||||
|
export function NoteKind_1({
|
||||||
|
event,
|
||||||
|
skipMetadata = false,
|
||||||
|
}: {
|
||||||
|
event: LumeEvent;
|
||||||
|
skipMetadata?: boolean;
|
||||||
|
}) {
|
||||||
|
const content = useMemo(() => parser(event), [event.id]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="h-min w-full px-3 py-1.5">
|
||||||
|
<div className="relative overflow-hidden rounded-xl border-t border-zinc-800/50 bg-zinc-900 px-3 pt-3">
|
||||||
|
<div className="relative flex flex-col">
|
||||||
|
<User pubkey={event.pubkey} time={event.created_at} />
|
||||||
|
<div className="relative z-20 -mt-5 flex items-start gap-3">
|
||||||
|
<div className="w-11 shrink-0" />
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="relative z-10 select-text whitespace-pre-line break-words text-base text-zinc-100">
|
||||||
|
{content.parsed}
|
||||||
|
</div>
|
||||||
|
{content.images.length > 0 && <ImagePreview urls={content.images} />}
|
||||||
|
{content.videos.length > 0 && <VideoPreview urls={content.videos} />}
|
||||||
|
{content.links.length > 0 && <LinkPreview urls={content.links} />}
|
||||||
|
{content.notes.length > 0 &&
|
||||||
|
content.notes.map((note: string) => <MentionNote key={note} id={note} />)}
|
||||||
|
<NoteActions
|
||||||
|
id={event.event_id}
|
||||||
|
rootID={event.parent_id}
|
||||||
|
eventPubkey={event.pubkey}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{!skipMetadata ? (
|
||||||
|
<NoteMetadata id={event.event_id} />
|
||||||
|
) : (
|
||||||
|
<div className="pb-3" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
42
src/shared/notes/kinds/kind1063.tsx
Normal file
42
src/shared/notes/kinds/kind1063.tsx
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import { Image } from '@shared/image';
|
||||||
|
import { NoteActions, NoteMetadata } from '@shared/notes';
|
||||||
|
import { User } from '@shared/user';
|
||||||
|
|
||||||
|
import { LumeEvent } from '@utils/types';
|
||||||
|
|
||||||
|
function isImage(url: string) {
|
||||||
|
return /\.(jpg|jpeg|gif|png|webp|avif)$/.test(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function NoteKind_1063({ event }: { event: LumeEvent }) {
|
||||||
|
const url = event.tags[0][1];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="h-min w-full px-3 py-1.5">
|
||||||
|
<div className="relative overflow-hidden rounded-xl border-t border-zinc-800/50 bg-zinc-900 px-3 pt-3">
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<User pubkey={event.pubkey} time={event.created_at} />
|
||||||
|
<div className="relative z-20 -mt-5 flex items-start gap-3">
|
||||||
|
<div className="w-11 shrink-0" />
|
||||||
|
<div className="flex-1">
|
||||||
|
{isImage(url) && (
|
||||||
|
<Image
|
||||||
|
src={url}
|
||||||
|
fallback="https://void.cat/d/XTmrMkpid8DGLjv1AzdvcW"
|
||||||
|
alt="image"
|
||||||
|
className="h-auto w-full rounded-lg object-cover"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<NoteActions
|
||||||
|
id={event.event_id}
|
||||||
|
rootID={event.parent_id}
|
||||||
|
eventPubkey={event.pubkey}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<NoteMetadata id={event.event_id} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
74
src/shared/notes/kinds/repost.tsx
Normal file
74
src/shared/notes/kinds/repost.tsx
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import {
|
||||||
|
ImagePreview,
|
||||||
|
LinkPreview,
|
||||||
|
MentionNote,
|
||||||
|
NoteActions,
|
||||||
|
NoteMetadata,
|
||||||
|
NoteSkeleton,
|
||||||
|
RepostUser,
|
||||||
|
VideoPreview,
|
||||||
|
} from '@shared/notes';
|
||||||
|
import { User } from '@shared/user';
|
||||||
|
|
||||||
|
import { useEvent } from '@utils/hooks/useEvent';
|
||||||
|
import { getRepostID } from '@utils/transform';
|
||||||
|
import { LumeEvent } from '@utils/types';
|
||||||
|
|
||||||
|
export function Repost({ event }: { event: LumeEvent }) {
|
||||||
|
const repostID = getRepostID(event.tags);
|
||||||
|
const { status, data } = useEvent(repostID, event.content);
|
||||||
|
|
||||||
|
if (status === 'loading') {
|
||||||
|
return (
|
||||||
|
<div className="relative overflow-hidden rounded-xl border-t border-zinc-800/50 bg-zinc-900 px-3 pt-3">
|
||||||
|
<NoteSkeleton />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status === 'error') {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-center overflow-hidden rounded-xl border-t border-zinc-800/50 bg-zinc-900 px-3 py-3">
|
||||||
|
<p className="text-zinc-400">Failed to fetch</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="h-min w-full px-3 py-1.5">
|
||||||
|
<div className="relative overflow-hidden rounded-xl border-t border-zinc-800/50 bg-zinc-900 px-3 pt-3">
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<div className="isolate flex flex-col -space-y-4 overflow-hidden">
|
||||||
|
<RepostUser pubkey={event.pubkey} />
|
||||||
|
<User pubkey={data.pubkey} time={event.created_at} isRepost={true} />
|
||||||
|
</div>
|
||||||
|
<div className="relative z-20 flex items-start gap-3">
|
||||||
|
<div className="w-11 shrink-0" />
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="relative z-10 select-text whitespace-pre-line break-all text-base text-zinc-100">
|
||||||
|
{data.content.parsed}
|
||||||
|
</div>
|
||||||
|
{data.content.images.length > 0 && (
|
||||||
|
<ImagePreview urls={data.content.images} />
|
||||||
|
)}
|
||||||
|
{data.content.videos.length > 0 && (
|
||||||
|
<VideoPreview urls={data.content.videos} />
|
||||||
|
)}
|
||||||
|
{data.content.links.length > 0 && <LinkPreview urls={data.content.links} />}
|
||||||
|
{data.content.notes.length > 0 &&
|
||||||
|
data.content.notes.map((note: string) => (
|
||||||
|
<MentionNote key={note} id={note} />
|
||||||
|
))}
|
||||||
|
<NoteActions
|
||||||
|
id={event.event_id}
|
||||||
|
rootID={event.parent_id}
|
||||||
|
eventPubkey={event.pubkey}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<NoteMetadata id={event.event_id} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
60
src/shared/notes/kinds/sub.tsx
Normal file
60
src/shared/notes/kinds/sub.tsx
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import {
|
||||||
|
ImagePreview,
|
||||||
|
LinkPreview,
|
||||||
|
MentionNote,
|
||||||
|
NoteActions,
|
||||||
|
NoteSkeleton,
|
||||||
|
VideoPreview,
|
||||||
|
} from '@shared/notes';
|
||||||
|
import { User } from '@shared/user';
|
||||||
|
|
||||||
|
import { useEvent } from '@utils/hooks/useEvent';
|
||||||
|
|
||||||
|
export function SubNote({ id }: { id: string }) {
|
||||||
|
const { status, data } = useEvent(id);
|
||||||
|
|
||||||
|
if (status === 'loading') {
|
||||||
|
return (
|
||||||
|
<div className="relative overflow-hidden rounded-xl border-t border-zinc-800/50 bg-zinc-900 px-3 pt-3">
|
||||||
|
<NoteSkeleton />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status === 'error') {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-center overflow-hidden rounded-xl border-t border-zinc-800/50 bg-zinc-900 px-3 py-3">
|
||||||
|
<p className="text-zinc-400">Failed to fetch</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="absolute bottom-0 left-[18px] h-[calc(100%-3.4rem)] w-0.5 bg-gradient-to-t from-zinc-800 to-zinc-600" />
|
||||||
|
<div className="mb-5 flex flex-col">
|
||||||
|
<User pubkey={data.pubkey} time={data.created_at} />
|
||||||
|
<div className="relative z-20 -mt-5 flex items-start gap-3">
|
||||||
|
<div className="w-11 shrink-0" />
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="relative z-10 select-text whitespace-pre-line break-words text-base text-zinc-100">
|
||||||
|
{data.content.parsed}
|
||||||
|
</div>
|
||||||
|
{data.content.images.length > 0 && (
|
||||||
|
<ImagePreview urls={data.content.images} />
|
||||||
|
)}
|
||||||
|
{data.content.videos.length > 0 && (
|
||||||
|
<VideoPreview urls={data.content.videos} />
|
||||||
|
)}
|
||||||
|
{data.content.links.length > 0 && <LinkPreview urls={data.content.links} />}
|
||||||
|
{data.content.notes.length > 0 &&
|
||||||
|
data.content.notes.map((note: string) => (
|
||||||
|
<MentionNote key={note} id={note} />
|
||||||
|
))}
|
||||||
|
<NoteActions id={data.id} eventPubkey={data.pubkey} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
58
src/shared/notes/kinds/thread.tsx
Normal file
58
src/shared/notes/kinds/thread.tsx
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
LinkPreview,
|
||||||
|
NoteActions,
|
||||||
|
NoteMetadata,
|
||||||
|
SubNote,
|
||||||
|
VideoPreview,
|
||||||
|
} from '@shared/notes';
|
||||||
|
import { MentionNote } from '@shared/notes/mentions/note';
|
||||||
|
import { ImagePreview } from '@shared/notes/preview/image';
|
||||||
|
import { User } from '@shared/user';
|
||||||
|
|
||||||
|
import { parser } from '@utils/parser';
|
||||||
|
import { LumeEvent } from '@utils/types';
|
||||||
|
|
||||||
|
export function NoteThread({
|
||||||
|
event,
|
||||||
|
root,
|
||||||
|
reply,
|
||||||
|
}: {
|
||||||
|
event: LumeEvent;
|
||||||
|
root: string;
|
||||||
|
reply: string;
|
||||||
|
}) {
|
||||||
|
const content = useMemo(() => parser(event), [event.id]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="h-min w-full px-3 py-1.5">
|
||||||
|
<div className="overflow-hidden rounded-xl border-t border-zinc-800/50 bg-zinc-900 px-3 pt-3">
|
||||||
|
<div className="relative">{root && <SubNote id={root} />}</div>
|
||||||
|
<div className="relative">{reply && <SubNote id={reply} />}</div>
|
||||||
|
<div className="relative flex flex-col">
|
||||||
|
<User pubkey={event.pubkey} time={event.created_at} />
|
||||||
|
<div className="relative z-20 -mt-5 flex items-start gap-3">
|
||||||
|
<div className="w-11 shrink-0" />
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="relative z-10 select-text whitespace-pre-line break-words text-base text-zinc-100">
|
||||||
|
{content.parsed}
|
||||||
|
</div>
|
||||||
|
{content.images.length > 0 && <ImagePreview urls={content.images} />}
|
||||||
|
{content.videos.length > 0 && <VideoPreview urls={content.videos} />}
|
||||||
|
{content.links.length > 0 && <LinkPreview urls={content.links} />}
|
||||||
|
{content.notes.length > 0 &&
|
||||||
|
content.notes.map((note: string) => <MentionNote key={note} id={note} />)}
|
||||||
|
<NoteActions
|
||||||
|
id={event.event_id}
|
||||||
|
rootID={event.parent_id}
|
||||||
|
eventPubkey={event.pubkey}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<NoteMetadata id={event.event_id} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
40
src/shared/notes/kinds/unsupport.tsx
Normal file
40
src/shared/notes/kinds/unsupport.tsx
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { NoteActions, NoteMetadata } from '@shared/notes';
|
||||||
|
import { User } from '@shared/user';
|
||||||
|
|
||||||
|
import { LumeEvent } from '@utils/types';
|
||||||
|
|
||||||
|
export function NoteKindUnsupport({ event }: { event: LumeEvent }) {
|
||||||
|
return (
|
||||||
|
<div className="h-min w-full px-3 py-1.5">
|
||||||
|
<div className="relative overflow-hidden rounded-xl border-t border-zinc-800/50 bg-zinc-900 px-3 pt-3">
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<User pubkey={event.pubkey} time={event.created_at} />
|
||||||
|
<div className="relative z-20 -mt-6 flex items-start gap-3">
|
||||||
|
<div className="w-11 shrink-0" />
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="mt-3 flex w-full flex-col gap-2">
|
||||||
|
<div className="inline-flex flex-col gap-1 rounded-md bg-zinc-800 px-2 py-2">
|
||||||
|
<span className="text-sm font-medium leading-none text-zinc-500">
|
||||||
|
Kind: {event.kind}
|
||||||
|
</span>
|
||||||
|
<p className="text-sm leading-none text-fuchsia-500">
|
||||||
|
Lume isn't fully support this kind
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="select-text whitespace-pre-line break-all text-zinc-100">
|
||||||
|
<p>{event.content.toString()}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<NoteActions
|
||||||
|
id={event.event_id}
|
||||||
|
rootID={event.parent_id}
|
||||||
|
eventPubkey={event.pubkey}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<NoteMetadata id={event.event_id} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -3,17 +3,15 @@ import { memo } from 'react';
|
|||||||
|
|
||||||
import { createBlock } from '@libs/storage';
|
import { createBlock } from '@libs/storage';
|
||||||
|
|
||||||
import { Kind1 } from '@shared/notes/contents/kind1';
|
|
||||||
import { Kind1063 } from '@shared/notes/contents/kind1063';
|
|
||||||
import { NoteSkeleton } from '@shared/notes/skeleton';
|
import { NoteSkeleton } from '@shared/notes/skeleton';
|
||||||
import { User } from '@shared/user';
|
import { User } from '@shared/user';
|
||||||
|
|
||||||
import { useEvent } from '@utils/hooks/useEvent';
|
import { useEvent } from '@utils/hooks/useEvent';
|
||||||
|
|
||||||
export const MentionNote = memo(function MentionNote({ id }: { id: string }) {
|
export const MentionNote = memo(function MentionNote({ id }: { id: string }) {
|
||||||
const queryClient = useQueryClient();
|
|
||||||
const { status, data } = useEvent(id);
|
const { status, data } = useEvent(id);
|
||||||
|
|
||||||
|
const queryClient = useQueryClient();
|
||||||
const block = useMutation({
|
const block = useMutation({
|
||||||
mutationFn: (data: any) => {
|
mutationFn: (data: any) => {
|
||||||
return createBlock(data.kind, data.title, data.content);
|
return createBlock(data.kind, data.title, data.content);
|
||||||
@ -45,25 +43,7 @@ export const MentionNote = memo(function MentionNote({ id }: { id: string }) {
|
|||||||
) : status === 'success' ? (
|
) : status === 'success' ? (
|
||||||
<>
|
<>
|
||||||
<User pubkey={data.pubkey} time={data.created_at} size="small" />
|
<User pubkey={data.pubkey} time={data.created_at} size="small" />
|
||||||
<div>
|
<div className="mt-2 flex items-start gap-3">{data.content.parsed}</div>
|
||||||
{data.kind === 1 && <Kind1 content={data.content} truncate={true} />}
|
|
||||||
{data.kind === 1063 && <Kind1063 metadata={data.tags} />}
|
|
||||||
{data.kind !== 1 && data.kind !== 1063 && (
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<div className="inline-flex flex-col gap-1 rounded-md bg-zinc-800 px-2 py-2">
|
|
||||||
<span className="text-sm font-medium leading-none text-zinc-500">
|
|
||||||
Kind: {data.kind}
|
|
||||||
</span>
|
|
||||||
<p className="text-sm leading-none text-fuchsia-500">
|
|
||||||
Lume isn't fully support this kind in newsfeed
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="select-text whitespace-pre-line break-words text-base text-zinc-100">
|
|
||||||
<p>{data.content}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<p>Failed to fetch event</p>
|
<p>Failed to fetch event</p>
|
||||||
|
@ -1,74 +1,66 @@
|
|||||||
import { NDKEvent, NDKFilter } from '@nostr-dev-kit/ndk';
|
import { NDKEvent, NDKFilter } from '@nostr-dev-kit/ndk';
|
||||||
import * as Tooltip from '@radix-ui/react-tooltip';
|
|
||||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
import { decode } from 'light-bolt11-decoder';
|
import { decode } from 'light-bolt11-decoder';
|
||||||
|
|
||||||
import { useNDK } from '@libs/ndk/provider';
|
import { useNDK } from '@libs/ndk/provider';
|
||||||
import { createBlock, createReplyNote } from '@libs/storage';
|
import { createBlock, createReplyNote } from '@libs/storage';
|
||||||
|
|
||||||
import { LoaderIcon, ReplyIcon, RepostIcon, ZapIcon } from '@shared/icons';
|
import { LoaderIcon } from '@shared/icons';
|
||||||
import { ThreadIcon } from '@shared/icons/thread';
|
import { MiniUser } from '@shared/notes/users/mini';
|
||||||
import { NoteReply } from '@shared/notes/metadata/reply';
|
|
||||||
import { NoteRepost } from '@shared/notes/metadata/repost';
|
|
||||||
import { NoteZap } from '@shared/notes/metadata/zap';
|
|
||||||
|
|
||||||
export function NoteMetadata({
|
import { compactNumber } from '@utils/number';
|
||||||
id,
|
|
||||||
rootID,
|
export function NoteMetadata({ id }: { id: string }) {
|
||||||
eventPubkey,
|
|
||||||
}: {
|
|
||||||
id: string;
|
|
||||||
rootID?: string;
|
|
||||||
eventPubkey: string;
|
|
||||||
}) {
|
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const { ndk } = useNDK();
|
const { ndk } = useNDK();
|
||||||
const { status, data } = useQuery(['note-metadata', id], async () => {
|
const { status, data } = useQuery(
|
||||||
let replies = 0;
|
['note-metadata', id],
|
||||||
let reposts = 0;
|
async () => {
|
||||||
let zap = 0;
|
let replies = 0;
|
||||||
|
let zap = 0;
|
||||||
|
const users = [];
|
||||||
|
|
||||||
const filter: NDKFilter = {
|
const filter: NDKFilter = {
|
||||||
'#e': [id],
|
'#e': [id],
|
||||||
kinds: [1, 6, 9735],
|
kinds: [1, 9735],
|
||||||
};
|
};
|
||||||
|
|
||||||
const events = await ndk.fetchEvents(filter);
|
const events = await ndk.fetchEvents(filter);
|
||||||
events.forEach((event: NDKEvent) => {
|
events.forEach((event: NDKEvent) => {
|
||||||
switch (event.kind) {
|
switch (event.kind) {
|
||||||
case 1:
|
case 1:
|
||||||
replies += 1;
|
replies += 1;
|
||||||
createReplyNote(
|
if (users.length < 3) users.push(event.pubkey);
|
||||||
id,
|
createReplyNote(
|
||||||
event.id,
|
id,
|
||||||
event.pubkey,
|
event.id,
|
||||||
event.kind,
|
event.pubkey,
|
||||||
event.tags,
|
event.kind,
|
||||||
event.content,
|
event.tags,
|
||||||
event.created_at
|
event.content,
|
||||||
);
|
event.created_at
|
||||||
break;
|
);
|
||||||
case 6:
|
break;
|
||||||
reposts += 1;
|
case 9735: {
|
||||||
break;
|
const bolt11 = event.tags.find((tag) => tag[0] === 'bolt11')[1];
|
||||||
case 9735: {
|
if (bolt11) {
|
||||||
const bolt11 = event.tags.find((tag) => tag[0] === 'bolt11')[1];
|
const decoded = decode(bolt11);
|
||||||
if (bolt11) {
|
const amount = decoded.sections.find((item) => item.name === 'amount');
|
||||||
const decoded = decode(bolt11);
|
const sats = amount.value / 1000;
|
||||||
const amount = decoded.sections.find((item) => item.name === 'amount');
|
zap += sats;
|
||||||
const sats = amount.value / 1000;
|
}
|
||||||
zap += sats;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
default:
|
});
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return { replies, reposts, zap };
|
return { replies, users, zap };
|
||||||
});
|
},
|
||||||
|
{ refetchOnWindowFocus: false, refetchOnReconnect: false }
|
||||||
|
);
|
||||||
|
|
||||||
const block = useMutation({
|
const block = useMutation({
|
||||||
mutationFn: (data: any) => {
|
mutationFn: (data: any) => {
|
||||||
@ -85,81 +77,50 @@ export function NoteMetadata({
|
|||||||
|
|
||||||
if (status === 'loading') {
|
if (status === 'loading') {
|
||||||
return (
|
return (
|
||||||
<div className="mt-2 inline-flex h-12 w-full items-center">
|
<div className="mb-3 flex items-center gap-3">
|
||||||
<div className="group inline-flex w-20 items-center gap-1.5">
|
<div className="mt-2h-6 w-11 shrink-0"></div>
|
||||||
<ReplyIcon
|
<div className="mt-2 inline-flex h-6 items-center">
|
||||||
width={16}
|
<LoaderIcon className="h-4 w-4 animate-spin text-zinc-100" />
|
||||||
height={16}
|
|
||||||
className="text-zinc-400 group-hover:text-green-400"
|
|
||||||
/>
|
|
||||||
<LoaderIcon
|
|
||||||
width={12}
|
|
||||||
height={12}
|
|
||||||
className="animate-spin text-black dark:text-zinc-100"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="group inline-flex w-20 items-center gap-1.5">
|
|
||||||
<RepostIcon
|
|
||||||
width={16}
|
|
||||||
height={16}
|
|
||||||
className="text-zinc-400 group-hover:text-green-400"
|
|
||||||
/>
|
|
||||||
<LoaderIcon
|
|
||||||
width={12}
|
|
||||||
height={12}
|
|
||||||
className="animate-spin text-black dark:text-zinc-100"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="group inline-flex w-20 items-center gap-1.5">
|
|
||||||
<ZapIcon
|
|
||||||
width={16}
|
|
||||||
height={16}
|
|
||||||
className="text-zinc-400 group-hover:text-green-400"
|
|
||||||
/>
|
|
||||||
<LoaderIcon
|
|
||||||
width={12}
|
|
||||||
height={12}
|
|
||||||
className="animate-spin text-black dark:text-zinc-100"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip.Provider>
|
<div>
|
||||||
<div className="mt-2 inline-flex h-12 w-full items-center justify-between">
|
{data.replies > 0 ? (
|
||||||
<div className="inline-flex items-center justify-between">
|
<>
|
||||||
<NoteReply
|
<div className="absolute bottom-0 left-[18px] h-[calc(100%-3.4rem)] w-0.5 bg-gradient-to-t from-zinc-800 to-zinc-600" />
|
||||||
id={id}
|
<div className="relative z-10 flex items-center gap-3 bg-zinc-900 pb-3">
|
||||||
rootID={rootID}
|
<div className="mt-2 inline-flex h-6 w-11 shrink-0 items-center justify-center">
|
||||||
pubkey={eventPubkey}
|
<div className="isolate flex -space-x-1 overflow-hidden">
|
||||||
replies={data.replies}
|
{data.users?.map((user, index) => (
|
||||||
/>
|
<MiniUser key={user + index} pubkey={user} />
|
||||||
<NoteRepost id={id} pubkey={eventPubkey} reposts={data.reposts} />
|
))}
|
||||||
<NoteZap zaps={data.zap} />
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Tooltip.Root delayDuration={150}>
|
<div className="mt-2 inline-flex h-6 items-center gap-2">
|
||||||
<Tooltip.Trigger asChild>
|
<button
|
||||||
<button
|
type="button"
|
||||||
type="button"
|
onClick={() => openThread(id)}
|
||||||
onClick={() => openThread(id)}
|
className="text-zinc-500"
|
||||||
className="inline-flex h-6 w-6 items-center justify-center rounded border-t border-zinc-700/50 bg-zinc-800 hover:bg-zinc-700"
|
>
|
||||||
>
|
<span className="font-semibold text-zinc-300">{data.replies}</span>{' '}
|
||||||
<ThreadIcon className="h-4 w-4 text-zinc-400" />
|
replies
|
||||||
</button>
|
</button>
|
||||||
</Tooltip.Trigger>
|
<span className="text-zinc-500">·</span>
|
||||||
<Tooltip.Portal>
|
<p className="text-zinc-500">
|
||||||
<Tooltip.Content
|
<span className="font-semibold text-zinc-300">
|
||||||
className="-left-10 select-none rounded-md bg-zinc-800/80 px-3.5 py-1.5 text-sm leading-none text-zinc-100 backdrop-blur-lg will-change-[transform,opacity] data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade"
|
{compactNumber.format(data.zap)}
|
||||||
sideOffset={5}
|
</span>{' '}
|
||||||
>
|
zaps
|
||||||
Open thread
|
</p>
|
||||||
<Tooltip.Arrow className="fill-zinc-800/80 backdrop-blur-lg" />
|
</div>
|
||||||
</Tooltip.Content>
|
</div>
|
||||||
</Tooltip.Portal>
|
</>
|
||||||
</Tooltip.Root>
|
) : (
|
||||||
</div>
|
<div className="pb-3" />
|
||||||
</Tooltip.Provider>
|
)}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,89 +0,0 @@
|
|||||||
import { useMemo } from 'react';
|
|
||||||
|
|
||||||
import { Kind1 } from '@shared/notes/contents/kind1';
|
|
||||||
import { Kind1063 } from '@shared/notes/contents/kind1063';
|
|
||||||
import { NoteMetadata } from '@shared/notes/metadata';
|
|
||||||
import { NoteParent } from '@shared/notes/parent';
|
|
||||||
import { Repost } from '@shared/notes/repost';
|
|
||||||
import { User } from '@shared/user';
|
|
||||||
|
|
||||||
import { parser } from '@utils/parser';
|
|
||||||
import { LumeEvent } from '@utils/types';
|
|
||||||
|
|
||||||
interface Note {
|
|
||||||
event: LumeEvent;
|
|
||||||
skipMetadata?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Note({ event, skipMetadata = false }: Note) {
|
|
||||||
const isRepost = event.kind === 6;
|
|
||||||
|
|
||||||
const renderParent = useMemo(() => {
|
|
||||||
if (!isRepost && event.parent_id && event.parent_id !== event.event_id) {
|
|
||||||
return <NoteParent id={event.parent_id} />;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}, [event.parent_id]);
|
|
||||||
|
|
||||||
const renderRepost = useMemo(() => {
|
|
||||||
if (isRepost) {
|
|
||||||
return <Repost event={event} />;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}, [event.kind]);
|
|
||||||
|
|
||||||
const renderContent = useMemo(() => {
|
|
||||||
switch (event.kind) {
|
|
||||||
case 1: {
|
|
||||||
const content = parser(event);
|
|
||||||
return <Kind1 content={content} />;
|
|
||||||
}
|
|
||||||
case 6:
|
|
||||||
return null;
|
|
||||||
case 1063:
|
|
||||||
return <Kind1063 metadata={event.tags} />;
|
|
||||||
default:
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<div className="inline-flex flex-col gap-1 rounded-md bg-zinc-800 px-2 py-2">
|
|
||||||
<span className="text-sm font-medium leading-none text-zinc-500">
|
|
||||||
Kind: {event.kind}
|
|
||||||
</span>
|
|
||||||
<p className="text-sm leading-none text-fuchsia-500">
|
|
||||||
Lume isn't fully support this kind in newsfeed
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="select-text whitespace-pre-line break-words text-base text-zinc-100">
|
|
||||||
<p>{event.content}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}, [event.kind]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="h-min w-full px-3 py-1.5">
|
|
||||||
<div className="rounded-xl border-t border-zinc-800/50 bg-zinc-900 px-3 pt-3">
|
|
||||||
{renderParent}
|
|
||||||
<div className="flex flex-col">
|
|
||||||
<User pubkey={event.pubkey} time={event.created_at} repost={isRepost} />
|
|
||||||
<div className="-mt-6 pl-[49px]">
|
|
||||||
{renderContent}
|
|
||||||
{!isRepost && !skipMetadata ? (
|
|
||||||
<NoteMetadata
|
|
||||||
id={event.event_id}
|
|
||||||
rootID={event.parent_id}
|
|
||||||
eventPubkey={event.pubkey}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<div className={isRepost ? 'h-0' : 'h-3'} />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{renderRepost}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,46 +0,0 @@
|
|||||||
import { Kind1 } from '@shared/notes/contents/kind1';
|
|
||||||
import { Kind1063 } from '@shared/notes/contents/kind1063';
|
|
||||||
import { NoteMetadata } from '@shared/notes/metadata';
|
|
||||||
import { NoteSkeleton } from '@shared/notes/skeleton';
|
|
||||||
import { User } from '@shared/user';
|
|
||||||
|
|
||||||
import { useEvent } from '@utils/hooks/useEvent';
|
|
||||||
|
|
||||||
export function NoteParent({ id }: { id: string }) {
|
|
||||||
const { status, data } = useEvent(id);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="relative flex flex-col pb-6">
|
|
||||||
<div className="absolute left-[18px] top-0 h-full w-0.5 bg-gradient-to-t from-zinc-800 to-zinc-600" />
|
|
||||||
{status === 'loading' ? (
|
|
||||||
<NoteSkeleton />
|
|
||||||
) : status === 'success' ? (
|
|
||||||
<>
|
|
||||||
<User pubkey={data.pubkey} time={data.created_at} />
|
|
||||||
<div className="-mt-6 pl-[49px]">
|
|
||||||
{data.kind === 1 && <Kind1 content={data.content} />}
|
|
||||||
{data.kind === 1063 && <Kind1063 metadata={data.tags} />}
|
|
||||||
{data.kind !== 1 && data.kind !== 1063 && (
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<div className="inline-flex flex-col gap-1 rounded-md bg-zinc-800 px-2 py-2">
|
|
||||||
<span className="text-sm font-medium leading-none text-zinc-500">
|
|
||||||
Kind: {data.kind}
|
|
||||||
</span>
|
|
||||||
<p className="text-sm leading-none text-fuchsia-500">
|
|
||||||
Lume isn't fully support this kind in newsfeed
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="select-text whitespace-pre-line break-words text-base text-zinc-100">
|
|
||||||
<p>{data.content || data.toString()}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<NoteMetadata id={data.event_id || data.id} eventPubkey={data.pubkey} />
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<p>Failed to fetch event</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,4 +1,3 @@
|
|||||||
import { Kind1 } from '@shared/notes/contents/kind1';
|
|
||||||
import { NoteMetadata } from '@shared/notes/metadata';
|
import { NoteMetadata } from '@shared/notes/metadata';
|
||||||
import { User } from '@shared/user';
|
import { User } from '@shared/user';
|
||||||
|
|
||||||
@ -12,7 +11,6 @@ export function Reply({ data }: { data: any }) {
|
|||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<User pubkey={data.pubkey} time={data.created_at} />
|
<User pubkey={data.pubkey} time={data.created_at} />
|
||||||
<div className="-mt-[20px] pl-[50px]">
|
<div className="-mt-[20px] pl-[50px]">
|
||||||
<Kind1 content={content} />
|
|
||||||
<NoteMetadata id={data.event_id} eventPubkey={data.pubkey} />
|
<NoteMetadata id={data.event_id} eventPubkey={data.pubkey} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,49 +0,0 @@
|
|||||||
import { Kind1 } from '@shared/notes/contents/kind1';
|
|
||||||
import { Kind1063 } from '@shared/notes/contents/kind1063';
|
|
||||||
import { NoteMetadata } from '@shared/notes/metadata';
|
|
||||||
import { NoteSkeleton } from '@shared/notes/skeleton';
|
|
||||||
import { User } from '@shared/user';
|
|
||||||
|
|
||||||
import { useEvent } from '@utils/hooks/useEvent';
|
|
||||||
import { getRepostID } from '@utils/transform';
|
|
||||||
import { LumeEvent } from '@utils/types';
|
|
||||||
|
|
||||||
export function Repost({ event }: { event: LumeEvent }) {
|
|
||||||
const repostID = getRepostID(event.tags);
|
|
||||||
const { status, data } = useEvent(repostID);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="relative mt-12 flex flex-col">
|
|
||||||
<div className="absolute -top-10 left-[18px] h-[50px] w-0.5 bg-gradient-to-t from-zinc-800 to-zinc-600" />
|
|
||||||
{status === 'loading' ? (
|
|
||||||
<NoteSkeleton />
|
|
||||||
) : status === 'success' ? (
|
|
||||||
<>
|
|
||||||
<User pubkey={data.pubkey} time={data.created_at} />
|
|
||||||
<div className="-mt-6 pl-[49px]">
|
|
||||||
{data.kind === 1 && <Kind1 content={data.content} />}
|
|
||||||
{data.kind === 1063 && <Kind1063 metadata={data.tags} />}
|
|
||||||
{data.kind !== 1 && data.kind !== 1063 && (
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<div className="inline-flex flex-col gap-1 rounded-md bg-zinc-800 px-2 py-2">
|
|
||||||
<span className="text-sm font-medium leading-none text-zinc-500">
|
|
||||||
Kind: {data.kind}
|
|
||||||
</span>
|
|
||||||
<p className="text-sm leading-none text-fuchsia-500">
|
|
||||||
Lume isn't fully support this kind in newsfeed
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="select-text whitespace-pre-line break-words text-base text-zinc-100">
|
|
||||||
<p>{data.content || data.toString()}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<NoteMetadata id={data.event_id || data.id} eventPubkey={data.pubkey} />
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<p>Failed to fetch event</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
22
src/shared/notes/users/mini.tsx
Normal file
22
src/shared/notes/users/mini.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { Image } from '@shared/image';
|
||||||
|
|
||||||
|
import { DEFAULT_AVATAR } from '@stores/constants';
|
||||||
|
|
||||||
|
import { useProfile } from '@utils/hooks/useProfile';
|
||||||
|
|
||||||
|
export function MiniUser({ pubkey }: { pubkey: string }) {
|
||||||
|
const { status, user } = useProfile(pubkey);
|
||||||
|
|
||||||
|
if (status === 'loading') {
|
||||||
|
return <div className="h-4 w-4 animate-pulse rounded bg-zinc-700"></div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Image
|
||||||
|
src={user?.picture || user?.image || DEFAULT_AVATAR}
|
||||||
|
fallback={DEFAULT_AVATAR}
|
||||||
|
alt={pubkey}
|
||||||
|
className="relative z-20 inline-block h-4 w-4 rounded bg-white ring-1 ring-zinc-800"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
34
src/shared/notes/users/repost.tsx
Normal file
34
src/shared/notes/users/repost.tsx
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { Image } from '@shared/image';
|
||||||
|
|
||||||
|
import { DEFAULT_AVATAR } from '@stores/constants';
|
||||||
|
|
||||||
|
import { useProfile } from '@utils/hooks/useProfile';
|
||||||
|
import { shortenKey } from '@utils/shortenKey';
|
||||||
|
|
||||||
|
export function RepostUser({ pubkey }: { pubkey: string }) {
|
||||||
|
const { status, user } = useProfile(pubkey);
|
||||||
|
|
||||||
|
if (status === 'loading') {
|
||||||
|
return <div className="h-4 w-4 animate-pulse rounded bg-zinc-700"></div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex gap-2 pl-6">
|
||||||
|
<Image
|
||||||
|
src={user?.picture || user?.image || DEFAULT_AVATAR}
|
||||||
|
fallback={DEFAULT_AVATAR}
|
||||||
|
alt={pubkey}
|
||||||
|
className="relative z-20 inline-block h-6 w-6 rounded bg-white ring-1 ring-zinc-800"
|
||||||
|
/>
|
||||||
|
<div className="inline-flex items-baseline gap-1">
|
||||||
|
<h5 className="max-w-[18rem] truncate text-zinc-400">
|
||||||
|
{user?.nip05?.toLowerCase() ||
|
||||||
|
user?.name ||
|
||||||
|
user?.display_name ||
|
||||||
|
shortenKey(pubkey)}
|
||||||
|
</h5>
|
||||||
|
<span className="text-zinc-400">reposted</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -14,13 +14,13 @@ export function User({
|
|||||||
pubkey,
|
pubkey,
|
||||||
time,
|
time,
|
||||||
size,
|
size,
|
||||||
repost,
|
isRepost = false,
|
||||||
isChat = false,
|
isChat = false,
|
||||||
}: {
|
}: {
|
||||||
pubkey: string;
|
pubkey: string;
|
||||||
time: number;
|
time: number;
|
||||||
size?: string;
|
size?: string;
|
||||||
repost?: boolean;
|
isRepost?: boolean;
|
||||||
isChat?: boolean;
|
isChat?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const { status, user } = useProfile(pubkey);
|
const { status, user } = useProfile(pubkey);
|
||||||
@ -50,9 +50,7 @@ export function User({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover
|
<Popover
|
||||||
className={`relative flex ${
|
className={`flex ${size === 'small' ? 'items-center gap-2' : 'items-start gap-3'}`}
|
||||||
size === 'small' ? 'items-center gap-2' : 'items-start gap-3'
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
<Popover.Button
|
<Popover.Button
|
||||||
className={`${avatarWidth} ${avatarHeight} relative z-10 shrink-0 overflow-hidden bg-zinc-900`}
|
className={`${avatarWidth} ${avatarHeight} relative z-10 shrink-0 overflow-hidden bg-zinc-900`}
|
||||||
@ -61,23 +59,26 @@ export function User({
|
|||||||
src={user?.picture || user?.image}
|
src={user?.picture || user?.image}
|
||||||
fallback={DEFAULT_AVATAR}
|
fallback={DEFAULT_AVATAR}
|
||||||
alt={pubkey}
|
alt={pubkey}
|
||||||
className={`${avatarWidth} ${avatarHeight} ${
|
className={`${
|
||||||
|
isRepost ? 'ring-1 ring-zinc-800' : ''
|
||||||
|
} ${avatarWidth} ${avatarHeight} ${
|
||||||
size === 'small' ? 'rounded' : 'rounded-lg'
|
size === 'small' ? 'rounded' : 'rounded-lg'
|
||||||
} object-cover`}
|
} object-cover`}
|
||||||
/>
|
/>
|
||||||
</Popover.Button>
|
</Popover.Button>
|
||||||
<div className="flex flex-wrap items-baseline gap-1">
|
<div
|
||||||
|
className={`${isRepost ? 'mt-4' : ''} flex flex-1 items-baseline justify-between`}
|
||||||
|
>
|
||||||
<h5
|
<h5
|
||||||
className={`truncate font-semibold leading-none text-zinc-100 ${
|
className={`truncate font-semibold leading-none text-zinc-100 ${
|
||||||
size === 'small' ? 'max-w-[8rem]' : 'max-w-[12rem]'
|
size === 'small' ? 'max-w-[8rem]' : 'max-w-[18rem]'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{user?.nip05 || user?.name || user?.displayName || shortenKey(pubkey)}
|
{user?.nip05?.toLowerCase() ||
|
||||||
|
user?.name ||
|
||||||
|
user?.display_name ||
|
||||||
|
shortenKey(pubkey)}
|
||||||
</h5>
|
</h5>
|
||||||
{repost && (
|
|
||||||
<span className="font-semibold leading-none text-fuchsia-500"> reposted</span>
|
|
||||||
)}
|
|
||||||
<span className="leading-none text-zinc-500">·</span>
|
|
||||||
<span className="leading-none text-zinc-500">{createdAt}</span>
|
<span className="leading-none text-zinc-500">{createdAt}</span>
|
||||||
</div>
|
</div>
|
||||||
<Transition
|
<Transition
|
||||||
|
@ -5,18 +5,29 @@ import { createNote, getNoteByID } from '@libs/storage';
|
|||||||
|
|
||||||
import { parser } from '@utils/parser';
|
import { parser } from '@utils/parser';
|
||||||
|
|
||||||
export function useEvent(id: string) {
|
export function useEvent(id: string, fallback?: string) {
|
||||||
const { ndk } = useNDK();
|
const { ndk } = useNDK();
|
||||||
const { status, data, error, isFetching } = useQuery(
|
const { status, data, error, isFetching } = useQuery(
|
||||||
['note', id],
|
['note', id],
|
||||||
async () => {
|
async () => {
|
||||||
const result = await getNoteByID(id);
|
const result = await getNoteByID(id);
|
||||||
if (result) {
|
if (result) {
|
||||||
if (result.kind === 1 || result.kind === 1063) {
|
if (result.kind === 1) {
|
||||||
result['content'] = parser(result);
|
result['content'] = parser(result);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
} else {
|
} else {
|
||||||
|
if (fallback) {
|
||||||
|
const embed = JSON.parse(fallback);
|
||||||
|
await createNote(
|
||||||
|
embed.id,
|
||||||
|
embed.pubkey,
|
||||||
|
embed.kind,
|
||||||
|
embed.tags,
|
||||||
|
embed.content,
|
||||||
|
embed.created_at
|
||||||
|
);
|
||||||
|
}
|
||||||
const event = await ndk.fetchEvent(id);
|
const event = await ndk.fetchEvent(id);
|
||||||
await createNote(
|
await createNote(
|
||||||
event.id,
|
event.id,
|
||||||
@ -27,7 +38,7 @@ export function useEvent(id: string) {
|
|||||||
event.created_at
|
event.created_at
|
||||||
);
|
);
|
||||||
event['event_id'] = event.id;
|
event['event_id'] = event.id;
|
||||||
if (event.kind === 1 || event.kind === 1063) {
|
if (event.kind === 1) {
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
event['content'] = parser(event);
|
event['content'] = parser(event);
|
||||||
|
@ -24,9 +24,6 @@ export function useProfile(pubkey: string, fallback?: string) {
|
|||||||
if (latest) {
|
if (latest) {
|
||||||
await createMetadata(pubkey, pubkey, latest.content);
|
await createMetadata(pubkey, pubkey, latest.content);
|
||||||
return JSON.parse(latest.content);
|
return JSON.parse(latest.content);
|
||||||
} else {
|
|
||||||
await createMetadata(pubkey, pubkey, [...events][0].content);
|
|
||||||
return JSON.parse([...events][0].content);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import destr from 'destr';
|
|
||||||
import getUrls from 'get-urls';
|
import getUrls from 'get-urls';
|
||||||
import { Event, parseReferences } from 'nostr-tools';
|
import { Event, parseReferences } from 'nostr-tools';
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
@ -9,20 +8,7 @@ import { MentionUser } from '@shared/notes/mentions/user';
|
|||||||
|
|
||||||
import { LumeEvent } from '@utils/types';
|
import { LumeEvent } from '@utils/types';
|
||||||
|
|
||||||
function isJsonString(str: string[][] | string) {
|
|
||||||
try {
|
|
||||||
if (typeof str === 'string') JSON.parse(str);
|
|
||||||
} catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function parser(event: LumeEvent) {
|
export function parser(event: LumeEvent) {
|
||||||
if (isJsonString(event.tags)) {
|
|
||||||
event['tags'] = destr(event.tags);
|
|
||||||
}
|
|
||||||
|
|
||||||
const references = parseReferences(event as Event);
|
const references = parseReferences(event as Event);
|
||||||
const urls = getUrls(event.content);
|
const urls = getUrls(event.content);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user