mirror of
https://github.com/lumehq/lume.git
synced 2025-10-06 18:33:21 +02:00
initial support nip-23
This commit is contained in:
@@ -20,6 +20,15 @@ import { getParentID, nip02ToArray } from "@utils/transform";
|
|||||||
import { useContext, useEffect, useRef } from "react";
|
import { useContext, useEffect, useRef } from "react";
|
||||||
import { navigate } from "vite-plugin-ssr/client/router";
|
import { navigate } from "vite-plugin-ssr/client/router";
|
||||||
|
|
||||||
|
function isJSON(str: string) {
|
||||||
|
try {
|
||||||
|
JSON.parse(str);
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
export function Page() {
|
export function Page() {
|
||||||
const pool: any = useContext(RelayContext);
|
const pool: any = useContext(RelayContext);
|
||||||
const now = useRef(new Date());
|
const now = useRef(new Date());
|
||||||
@@ -157,8 +166,10 @@ export function Page() {
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
// long post
|
// long post
|
||||||
case 30023:
|
case 30023: {
|
||||||
// insert event to local database
|
// insert event to local database
|
||||||
|
const verifyMetadata = isJSON(event.tags);
|
||||||
|
if (verifyMetadata) {
|
||||||
createNote(
|
createNote(
|
||||||
event.id,
|
event.id,
|
||||||
account.id,
|
account.id,
|
||||||
@@ -169,7 +180,9 @@ export function Page() {
|
|||||||
event.created_at,
|
event.created_at,
|
||||||
"",
|
"",
|
||||||
);
|
);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
1
src/app/threads/_default.page.tsx
Normal file
1
src/app/threads/_default.page.tsx
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { LayoutNewsfeed as Layout } from "./layout";
|
11
src/app/threads/components/author.tsx
Normal file
11
src/app/threads/components/author.tsx
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
export function ThreadAuthor({
|
||||||
|
pubkey,
|
||||||
|
time,
|
||||||
|
}: { pubkey: string; time: number }) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<p>{pubkey}</p>
|
||||||
|
<span>{time}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
21
src/app/threads/components/base.tsx
Normal file
21
src/app/threads/components/base.tsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { ThreadAuthor } from "@app/threads/components/author";
|
||||||
|
|
||||||
|
export function ThreadBase({ event }: { event: any }) {
|
||||||
|
const metadata = JSON.parse(event.metadata);
|
||||||
|
const title = metadata.find((i: any) => i[0] === "title")[1];
|
||||||
|
const summary = metadata.find((i: any) => i[0] === "summary")[1] || "";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="h-min w-full px-3 py-1.5">
|
||||||
|
<div className="rounded-md border border-zinc-800 bg-zinc-900 shadow-input shadow-black/20">
|
||||||
|
<div className="px-3 py-3">
|
||||||
|
<h3>{title}</h3>
|
||||||
|
<p>{summary}</p>
|
||||||
|
</div>
|
||||||
|
<div className="inline-flex w-full justify-between items-center px-3 h-10">
|
||||||
|
<ThreadAuthor pubkey={event.pubkey} time={event.created_at} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
31
src/app/threads/layout.tsx
Normal file
31
src/app/threads/layout.tsx
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import AppHeader from "@shared/appHeader";
|
||||||
|
import MultiAccounts from "@shared/multiAccounts";
|
||||||
|
import Navigation from "@shared/navigation";
|
||||||
|
|
||||||
|
export function LayoutNewsfeed({ children }: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<div className="h-screen w-screen bg-zinc-50 text-zinc-900 dark:bg-zinc-950 dark:text-white">
|
||||||
|
<div className="flex h-screen w-full flex-col">
|
||||||
|
<div
|
||||||
|
data-tauri-drag-region
|
||||||
|
className="relative h-9 shrink-0 border-b border-zinc-100 bg-white dark:border-zinc-900 dark:bg-black"
|
||||||
|
>
|
||||||
|
<AppHeader />
|
||||||
|
</div>
|
||||||
|
<div className="relative flex min-h-0 w-full flex-1">
|
||||||
|
<div className="relative w-[68px] shrink-0 border-r border-zinc-900">
|
||||||
|
<MultiAccounts />
|
||||||
|
</div>
|
||||||
|
<div className="grid w-full grid-cols-4 xl:grid-cols-5">
|
||||||
|
<div className="scrollbar-hide col-span-1 overflow-y-auto overflow-x-hidden border-r border-zinc-900">
|
||||||
|
<Navigation />
|
||||||
|
</div>
|
||||||
|
<div className="col-span-3 overflow-hidden xl:col-span-4">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@@ -1,7 +1,113 @@
|
|||||||
|
import { NoteSkeleton } from "@app/note/components/skeleton";
|
||||||
|
import { ThreadBase } from "@app/threads/components/base";
|
||||||
|
import { useInfiniteQuery } from "@tanstack/react-query";
|
||||||
|
import { useVirtualizer } from "@tanstack/react-virtual";
|
||||||
|
import { getLongNotes } from "@utils/storage";
|
||||||
|
import { useEffect, useRef } from "react";
|
||||||
|
|
||||||
|
const ITEM_PER_PAGE = 10;
|
||||||
|
const TIME = Math.floor(Date.now() / 1000);
|
||||||
|
|
||||||
export function Page() {
|
export function Page() {
|
||||||
|
const {
|
||||||
|
status,
|
||||||
|
error,
|
||||||
|
data,
|
||||||
|
fetchNextPage,
|
||||||
|
hasNextPage,
|
||||||
|
isFetching,
|
||||||
|
isFetchingNextPage,
|
||||||
|
}: any = useInfiniteQuery({
|
||||||
|
queryKey: ["threads"],
|
||||||
|
queryFn: async ({ pageParam = 0 }) => {
|
||||||
|
return await getLongNotes(TIME, ITEM_PER_PAGE, pageParam);
|
||||||
|
},
|
||||||
|
getNextPageParam: (lastPage) => lastPage.nextCursor,
|
||||||
|
});
|
||||||
|
|
||||||
|
const allRows = data ? data.pages.flatMap((d: { data: any }) => d.data) : [];
|
||||||
|
const parentRef = useRef();
|
||||||
|
|
||||||
|
const rowVirtualizer = useVirtualizer({
|
||||||
|
count: hasNextPage ? allRows.length + 1 : allRows.length,
|
||||||
|
getScrollElement: () => parentRef.current,
|
||||||
|
estimateSize: () => 500,
|
||||||
|
overscan: 2,
|
||||||
|
});
|
||||||
|
|
||||||
|
const itemsVirtualizer = rowVirtualizer.getVirtualItems();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const [lastItem] = [...rowVirtualizer.getVirtualItems()].reverse();
|
||||||
|
|
||||||
|
if (!lastItem) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
lastItem.index >= allRows.length - 1 &&
|
||||||
|
hasNextPage &&
|
||||||
|
!isFetchingNextPage
|
||||||
|
) {
|
||||||
|
fetchNextPage();
|
||||||
|
}
|
||||||
|
}, [fetchNextPage, allRows.length, rowVirtualizer.getVirtualItems()]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<div
|
||||||
|
ref={parentRef}
|
||||||
|
className="scrollbar-hide flex h-full flex-col justify-between gap-1.5 overflow-y-auto"
|
||||||
|
style={{ contain: "strict" }}
|
||||||
|
>
|
||||||
|
{status === "loading" ? (
|
||||||
|
<div className="px-3 py-1.5">
|
||||||
|
<div className="rounded-md border border-zinc-800 bg-zinc-900 px-3 py-3 shadow-input shadow-black/20">
|
||||||
|
<NoteSkeleton />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : status === "error" ? (
|
||||||
|
<div>{error.message}</div>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
className="relative w-full"
|
||||||
|
style={{
|
||||||
|
height: `${rowVirtualizer.getTotalSize()}px`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="absolute left-0 top-0 w-full"
|
||||||
|
style={{
|
||||||
|
transform: `translateY(${
|
||||||
|
itemsVirtualizer[0].start - rowVirtualizer.options.scrollMargin
|
||||||
|
}px)`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{rowVirtualizer.getVirtualItems().map((virtualRow) => {
|
||||||
|
const note = allRows[virtualRow.index];
|
||||||
|
if (note) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={virtualRow.index}
|
||||||
|
data-index={virtualRow.index}
|
||||||
|
ref={rowVirtualizer.measureElement}
|
||||||
|
>
|
||||||
|
<ThreadBase key={note.event_id} event={note} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div>
|
<div>
|
||||||
<p>MySpace</p>
|
{isFetching && !isFetchingNextPage ? (
|
||||||
|
<div className="px-3 py-1.5">
|
||||||
|
<div className="rounded-md border border-zinc-800 bg-zinc-900 px-3 py-3 shadow-input shadow-black/20">
|
||||||
|
<NoteSkeleton />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -55,7 +55,6 @@ export function Page() {
|
|||||||
) {
|
) {
|
||||||
fetchNextPage();
|
fetchNextPage();
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [fetchNextPage, allRows.length, rowVirtualizer.getVirtualItems()]);
|
}, [fetchNextPage, allRows.length, rowVirtualizer.getVirtualItems()]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@@ -14,6 +14,15 @@ import { useSetAtom } from "jotai";
|
|||||||
import { useContext, useRef } from "react";
|
import { useContext, useRef } from "react";
|
||||||
import useSWRSubscription from "swr/subscription";
|
import useSWRSubscription from "swr/subscription";
|
||||||
|
|
||||||
|
function isJSON(str: string) {
|
||||||
|
try {
|
||||||
|
JSON.parse(str);
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
export default function EventCollector() {
|
export default function EventCollector() {
|
||||||
const pool: any = useContext(RelayContext);
|
const pool: any = useContext(RelayContext);
|
||||||
|
|
||||||
@@ -97,7 +106,9 @@ export default function EventCollector() {
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
// long post
|
// long post
|
||||||
case 30023:
|
case 30023: {
|
||||||
|
const verifyMetadata = isJSON(event.tags);
|
||||||
|
if (verifyMetadata) {
|
||||||
// insert event to local database
|
// insert event to local database
|
||||||
createNote(
|
createNote(
|
||||||
event.id,
|
event.id,
|
||||||
@@ -109,7 +120,9 @@ export default function EventCollector() {
|
|||||||
event.created_at,
|
event.created_at,
|
||||||
"",
|
"",
|
||||||
);
|
);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@@ -132,7 +132,7 @@ export async function getLongNotes(
|
|||||||
|
|
||||||
const notes: any = { data: null, nextCursor: 0 };
|
const notes: any = { data: null, nextCursor: 0 };
|
||||||
const query: any = await db.select(
|
const query: any = await db.select(
|
||||||
`SELECT * FROM notes WHERE created_at <= "${time}" AND kind = 30023 GROUP BY parent_id ORDER BY created_at DESC LIMIT "${limit}" OFFSET "${offset}";`,
|
`SELECT * FROM notes WHERE created_at <= "${time}" AND kind = 30023 ORDER BY created_at DESC LIMIT "${limit}" OFFSET "${offset}";`,
|
||||||
);
|
);
|
||||||
|
|
||||||
notes["data"] = query;
|
notes["data"] = query;
|
||||||
|
Reference in New Issue
Block a user