feat: group message by date

This commit is contained in:
reya
2024-07-29 11:04:26 +07:00
parent 9a293c6083
commit a65d5d0c1a
14 changed files with 381 additions and 422 deletions

View File

@@ -47,9 +47,9 @@ try {
else return { status: "error", error: e as any };
}
},
async getChats(dbOnly: boolean) : Promise<Result<string[], string>> {
async getChats() : Promise<Result<string[], string>> {
try {
return { status: "ok", data: await TAURI_INVOKE("get_chats", { dbOnly }) };
return { status: "ok", data: await TAURI_INVOKE("get_chats") };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };

View File

@@ -1,11 +1,9 @@
import { useQuery } from "@tanstack/react-query";
import { type ClassValue, clsx } from "clsx";
import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime";
import updateLocale from "dayjs/plugin/updateLocale";
import { nip19 } from "nostr-tools";
import { type NostrEvent, nip19 } from "nostr-tools";
import { twMerge } from "tailwind-merge";
import { commands } from "./commands";
dayjs.extend(relativeTime);
dayjs.extend(updateLocale);
@@ -81,3 +79,11 @@ export function getChatId(pubkey: string, tags: string[][]) {
const id = [pubkey, tags.map((tag) => tag[0] === "p" && tag[1])].join("-");
return id;
}
export function groupEventByDate(events: NostrEvent[]) {
const groups = Object.groupBy(events, (event) => {
return dayjs.unix(event.created_at).startOf("day").format("MMM DD, YYYY");
});
return groups;
}

View File

@@ -1,8 +1,8 @@
import { commands } from "@/commands";
import { cn, getReceivers, time } from "@/commons";
import { cn, getReceivers, groupEventByDate, time } from "@/commons";
import { Spinner } from "@/components/spinner";
import { User } from "@/components/user";
import { ArrowUp, DotsThree, Paperclip } from "@phosphor-icons/react";
import { ArrowUp, Paperclip } from "@phosphor-icons/react";
import * as ScrollArea from "@radix-ui/react-scroll-area";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { createFileRoute } from "@tanstack/react-router";
@@ -90,15 +90,17 @@ function List() {
if (res.status === "ok") {
const raw = res.data;
const events = raw
.map((item) => JSON.parse(item) as NostrEvent)
.sort((a, b) => a.created_at - b.created_at);
const events: NostrEvent[] = raw.map((item) => JSON.parse(item));
return events;
} else {
throw new Error(res.error);
}
},
select: (data) => {
const groups = groupEventByDate(data);
return Object.entries(groups).reverse();
},
refetchOnWindowFocus: false,
});
@@ -122,10 +124,10 @@ function List() {
>
<div
className={cn(
"py-2 px-3 w-fit max-w-[400px] text-pretty break-message rounded-t-2xl",
"py-2 px-3 w-fit max-w-[400px] text-pretty break-message",
!self
? "bg-neutral-100 dark:bg-neutral-800 rounded-l-md rounded-r-xl"
: "bg-blue-500 text-white rounded-l-xl rounded-r-md",
? "bg-neutral-100 dark:bg-neutral-800 rounded-tl-3xl rounded-tr-3xl rounded-br-3xl rounded-bl-md"
: "bg-blue-500 text-white rounded-tl-3xl rounded-tr-3xl rounded-br-md rounded-bl-3xl",
)}
>
{item.content}
@@ -177,31 +179,19 @@ function List() {
className="relative h-full py-2 [&>div]:!flex [&>div]:flex-col [&>div]:justify-end [&>div]:min-h-full"
>
<Virtualizer scrollRef={ref} shift>
{isLoading || !data ? (
{isLoading ? (
<>
<div className="flex items-center justify-between gap-3 my-1.5 px-3">
<div className="flex-1 min-w-0 inline-flex">
<div className="py-2 px-3 w-fit max-w-[400px] bg-neutral-100 dark:bg-neutral-800 rounded-l-md rounded-r-xl rounded-t-2xl">
Loading...
</div>
</div>
<div className="shrink-0 w-16 flex items-center justify-end">
<span className="text-xs text-right text-neutral-600 dark:text-neutral-400">
{time(Math.floor(Date.now() / 1000))}
</span>
<div className="w-44 h-[35px] py-2 max-w-[400px] bg-neutral-100 dark:bg-neutral-800 animate-pulse rounded-tl-3xl rounded-tr-3xl rounded-br-3xl rounded-bl-md" />
</div>
<div className="shrink-0 w-16 flex items-center justify-end" />
</div>
<div className="flex items-center justify-between gap-3 my-1.5 px-3">
<div className="flex-1 min-w-0 inline-flex justify-end">
<div className="py-2 px-3 w-fit max-w-[400px] bg-blue-500 text-white rounded-l-xl rounded-r-md rounded-t-2xl">
Loading...
</div>
</div>
<div className="shrink-0 w-16 flex items-center justify-end">
<span className="text-xs text-right text-neutral-600 dark:text-neutral-400">
{time(Math.floor(Date.now() / 1000))}
</span>
<div className="w-44 h-[35px] py-2 max-w-[400px] bg-blue-500 text-white animate-pulse rounded-tl-3xl rounded-tr-3xl rounded-br-md rounded-bl-3xl" />
</div>
<div className="shrink-0 w-16 flex items-center justify-end" />
</div>
</>
) : isError ? (
@@ -211,7 +201,21 @@ function List() {
</div>
</div>
) : (
data.map((item, idx) => renderItem(item, idx))
data.map((item) => (
<div
key={item[0]}
className="w-full flex flex-col items-center mt-3 gap-3"
>
<div className="text-xs text-center text-neutral-600 dark:text-neutral-400">
{item[0]}
</div>
<div className="w-full">
{item[1]
.sort((a, b) => a.created_at - b.created_at)
.map((item, idx) => renderItem(item, idx))}
</div>
</div>
))
)}
</Virtualizer>
</ScrollArea.Viewport>

View File

@@ -67,7 +67,7 @@ function ChatList() {
const { isLoading, data } = useQuery({
queryKey: ["chats"],
queryFn: async () => {
const res = await commands.getChats(true);
const res = await commands.getChats();
if (res.status === "ok") {
const raw = res.data;
@@ -79,11 +79,20 @@ function ChatList() {
}
},
select: (data) => data.sort((a, b) => b.created_at - a.created_at),
refetchOnWindowFocus: false,
});
useEffect(() => {
const unlisten = listen<Payload>("new_chat", async (data) => {
const unlisten = listen("synchronized", async () => {
await queryClient.refetchQueries({ queryKey: ["chats"] })
});
return () => {
unlisten.then((f) => f());
};
}, []);
useEffect(() => {
const unlisten = listen<Payload>("event", async (data) => {
const event: NostrEvent = JSON.parse(data.payload.event);
const chats: NostrEvent[] = await queryClient.getQueryData(["chats"]);