This commit is contained in:
Ren Amamiya 2023-06-26 10:24:12 +07:00
parent 6af0b453e3
commit 7fb62a6afa
21 changed files with 479 additions and 374 deletions

View File

@ -27,6 +27,7 @@ export function ChatsList() {
return ( return (
<div className="flex flex-col"> <div className="flex flex-col">
<NewMessageModal />
{account ? ( {account ? (
<ChatsListSelfItem data={account} /> <ChatsListSelfItem data={account} />
) : ( ) : (
@ -59,7 +60,6 @@ export function ChatsList() {
<div className="h-3 w-full rounded-sm animate-pulse bg-zinc-800" /> <div className="h-3 w-full rounded-sm animate-pulse bg-zinc-800" />
</div> </div>
)} )}
<NewMessageModal />
</div> </div>
); );
} }

View File

@ -10,7 +10,7 @@ import { useNavigate } from "react-router-dom";
export function NewMessageModal() { export function NewMessageModal() {
const navigate = useNavigate(); const navigate = useNavigate();
const { status, data, isFetching }: any = useQuery(["plebs"], async () => { const { status, data }: any = useQuery(["plebs"], async () => {
return await getPlebs(); return await getPlebs();
}); });
@ -96,7 +96,7 @@ export function NewMessageModal() {
</div> </div>
</div> </div>
<div className="h-[500px] flex flex-col pb-5 overflow-x-hidden overflow-y-auto"> <div className="h-[500px] flex flex-col pb-5 overflow-x-hidden overflow-y-auto">
{status === "loading" || isFetching ? ( {status === "loading" ? (
<p>Loading...</p> <p>Loading...</p>
) : ( ) : (
data.map((pleb) => ( data.map((pleb) => (
@ -111,10 +111,10 @@ export function NewMessageModal() {
className="w-9 h-9 shrink-0 object-cover rounded" className="w-9 h-9 shrink-0 object-cover rounded"
/> />
<div className="inline-flex flex-col gap-1"> <div className="inline-flex flex-col gap-1">
<h3 className="leading-none max-w-[15rem] font-medium text-zinc-100"> <h3 className="leading-none max-w-[15rem] line-clamp-1 font-medium text-zinc-100">
{pleb.display_name || pleb.name} {pleb.display_name || pleb.name}
</h3> </h3>
<span className="leading-none max-w-[10rem] text-sm text-zinc-400"> <span className="leading-none max-w-[10rem] line-clamp-1 text-sm text-zinc-400">
{pleb.nip05 || {pleb.nip05 ||
pleb.npub.substring(0, 16).concat("...")} pleb.npub.substring(0, 16).concat("...")}
</span> </span>

View File

@ -144,8 +144,8 @@ export function Root() {
const notes = await fetchNotes(); const notes = await fetchNotes();
if (notes) { if (notes) {
const chats = await fetchChats(); const chats = await fetchChats();
const channels = await fetchChannelMessages(); // const channels = await fetchChannelMessages();
if (chats && channels) { if (chats) {
navigate("/app/space", { replace: true }); navigate("/app/space", { replace: true });
} }
} }

View File

@ -1,66 +1,11 @@
import { AddFeedBlock } from "@app/space/components/addFeed"; import { AddFeedBlock } from "@app/space/components/addFeed";
import { AddImageBlock } from "@app/space/components/addImage"; import { AddImageBlock } from "@app/space/components/addImage";
import { Menu, Transition } from "@headlessui/react";
import { FeedIcon, ImageIcon, PlusIcon } from "@shared/icons";
import { Fragment, useState } from "react";
export function AddBlock() { export function AddBlock() {
const [imageModal, setImageModal] = useState(false);
const [feedModal, setFeedModal] = useState(false);
const openAddImageModal = () => {
setImageModal(true);
};
const openAddFeedModal = () => {
setFeedModal(true);
};
return ( return (
<> <div className="flex flex-col gap-1">
<Menu as="div" className="relative inline-block text-left"> <AddImageBlock />
<Menu.Button className="group inline-flex flex-col items-center gap-2.5"> <AddFeedBlock />
<div className="inline-flex h-9 w-9 shrink items-center justify-center rounded-lg bg-zinc-900 group-hover:bg-zinc-800">
<PlusIcon width={16} height={16} className="text-zinc-500" />
</div> </div>
</Menu.Button>
<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items className="absolute mt-2 right-1/2 transform translate-x-1/2 w-56 origin-top-right rounded-md bg-zinc-900/80 backdrop-blur-md focus:outline-none">
<div className="px-1 py-1">
<Menu.Item>
<button
type="button"
onClick={() => openAddImageModal()}
className="group flex w-full items-center rounded-md hover:bg-zinc-700/50 text-zinc-300 hover:text-zinc-100 px-2 py-2 text-sm"
>
<ImageIcon width={15} height={15} className="mr-2" />
Add image
</button>
</Menu.Item>
<Menu.Item>
<button
type="button"
onClick={() => openAddFeedModal()}
className="group flex w-full items-center rounded-md hover:bg-zinc-700/50 text-zinc-300 hover:text-zinc-100 px-2 py-2 text-sm"
>
<FeedIcon width={15} height={15} className="mr-2" />
Add feed
</button>
</Menu.Item>
</div>
</Menu.Items>
</Transition>
</Menu>
{imageModal && <AddImageBlock parentState={setImageModal} />}
{feedModal && <AddFeedBlock parentState={setFeedModal} />}
</>
); );
} }

View File

@ -1,24 +1,37 @@
import { User } from "@app/auth/components/user";
import { Dialog, Transition } from "@headlessui/react"; import { Dialog, Transition } from "@headlessui/react";
import { createBlock } from "@libs/storage"; import { Combobox } from "@headlessui/react";
import { CancelIcon } from "@shared/icons"; import { createBlock, getPlebs } from "@libs/storage";
import { useMutation, useQueryClient } from "@tanstack/react-query"; import { CancelIcon, CheckCircleIcon, CommandIcon } from "@shared/icons";
import { DEFAULT_AVATAR } from "@stores/constants";
import { ADD_FEEDBLOCK_SHORTCUT } from "@stores/shortcuts";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { useAccount } from "@utils/hooks/useAccount";
import { nip19 } from "nostr-tools"; import { nip19 } from "nostr-tools";
import { Fragment, useState } from "react"; import { Fragment, useEffect, useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { useHotkeys } from "react-hotkeys-hook";
export function AddFeedBlock({ parentState }: { parentState: any }) { export function AddFeedBlock() {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [isOpen, setIsOpen] = useState(true); const [isOpen, setIsOpen] = useState(false);
const [selected, setSelected] = useState([]);
const [query, setQuery] = useState("");
const { status, account } = useAccount();
const openModal = () => {
setIsOpen(true);
};
const closeModal = () => { const closeModal = () => {
// update local state
setIsOpen(false); setIsOpen(false);
// update parent state
parentState(false);
}; };
useHotkeys(ADD_FEEDBLOCK_SHORTCUT, () => openModal());
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);
@ -38,25 +51,45 @@ export function AddFeedBlock({ parentState }: { parentState: any }) {
const onSubmit = (data: any) => { const onSubmit = (data: any) => {
setLoading(true); setLoading(true);
let pubkey = data.content; selected.forEach((item, index) => {
if (item.substring(0, 4) === "npub") {
if (pubkey.substring(0, 4) === "npub") { selected[index] = nip19.decode(item).data;
pubkey = nip19.decode(pubkey).data;
} }
});
// insert to database // insert to database
block.mutate({ kind: 1, title: data.title, content: pubkey }); block.mutate({
kind: 1,
title: data.title,
content: JSON.stringify(selected),
});
setTimeout(() => {
setLoading(false); setLoading(false);
// reset form // reset form
reset(); reset();
// close modal // close modal
closeModal(); closeModal();
}, 1200);
}; };
return ( return (
<>
<button
type="button"
onClick={() => openModal()}
className="inline-flex w-56 h-9 items-center justify-start gap-2.5 rounded-md px-2.5"
>
<div className="flex items-center gap-2">
<div className="inline-flex h-6 w-6 shrink-0 items-center justify-center rounded border-t border-zinc-800/50 bg-zinc-900">
<CommandIcon width={12} height={12} className="text-zinc-500" />
</div>
<div className="inline-flex h-6 w-6 shrink-0 items-center justify-center rounded border-t border-zinc-800/50 bg-zinc-900">
<span className="text-zinc-500 text-sm leading-none">F</span>
</div>
</div>
<div>
<h5 className="font-medium text-zinc-400">New feed block</h5>
</div>
</button>
<Transition appear show={isOpen} as={Fragment}> <Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-50" onClose={closeModal}> <Dialog as="div" className="relative z-50" onClose={closeModal}>
<Transition.Child <Transition.Child
@ -88,7 +121,7 @@ export function AddFeedBlock({ parentState }: { parentState: any }) {
as="h3" as="h3"
className="text-lg font-semibold leading-none text-zinc-100" className="text-lg font-semibold leading-none text-zinc-100"
> >
Create image block Create feed block
</Dialog.Title> </Dialog.Title>
<button <button
type="button" type="button"
@ -103,9 +136,8 @@ export function AddFeedBlock({ parentState }: { parentState: any }) {
</button> </button>
</div> </div>
<Dialog.Description className="text-sm leading-tight text-zinc-400"> <Dialog.Description className="text-sm leading-tight text-zinc-400">
Pin your favorite image to Space then you can view every Specific newsfeed space for people you want to keep up to
time that you use Lume, your image will be broadcast to date
Nostr Relay as well
</Dialog.Description> </Dialog.Description>
</div> </div>
</div> </div>
@ -118,30 +150,83 @@ export function AddFeedBlock({ parentState }: { parentState: any }) {
<label className="text-sm font-medium uppercase tracking-wider text-zinc-400"> <label className="text-sm font-medium uppercase tracking-wider text-zinc-400">
Title * Title *
</label> </label>
<div className="relative w-full shrink-0 overflow-hidden before:pointer-events-none before:absolute before:-inset-1 before:rounded-[6px] before:border before:border-fuchsia-500 before:opacity-0 before:ring-2 before:ring-fuchsia-500/20 before:transition after:pointer-events-none after:absolute after:inset-px after:rounded-[6px] after:shadow-highlight after:shadow-white/5 after:transition focus-within:before:opacity-100 focus-within:after:shadow-fuchsia-500/100 dark:focus-within:after:shadow-fuchsia-500/20">
<input <input
type={"text"} type={"text"}
{...register("title", { {...register("title", {
required: true, required: true,
})} })}
spellCheck={false} spellCheck={false}
className="relative h-10 w-full rounded-md border border-black/5 px-3 py-2 shadow-input shadow-black/5 !outline-none placeholder:text-zinc-400 dark:bg-zinc-800 dark:text-zinc-100 dark:shadow-black/10 dark:placeholder:text-zinc-500" className="relative h-10 w-full rounded-md px-3 py-2 !outline-none placeholder:text-zinc-500 bg-zinc-800 text-zinc-100"
/> />
</div> </div>
</div>
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
<label className="text-sm font-medium uppercase tracking-wider text-zinc-400"> <label className="text-sm font-medium uppercase tracking-wider text-zinc-400">
Pubkey OR Npub * Choose at least 1 user *
</label> </label>
<div className="relative w-full shrink-0 overflow-hidden before:pointer-events-none before:absolute before:-inset-1 before:rounded-[6px] before:border before:border-fuchsia-500 before:opacity-0 before:ring-2 before:ring-fuchsia-500/20 before:transition after:pointer-events-none after:absolute after:inset-px after:rounded-[6px] after:shadow-highlight after:shadow-white/5 after:transition focus-within:before:opacity-100 focus-within:after:shadow-fuchsia-500/100 dark:focus-within:after:shadow-fuchsia-500/20"> <div className="w-full h-[300px] flex flex-col rounded-lg border-t border-zinc-700/50 bg-zinc-800 overflow-x-hidden overflow-y-auto">
<input <div className="w-full px-3 py-2">
type={"text"} <Combobox
{...register("content", { value={selected}
required: true, onChange={setSelected}
})} multiple
>
<Combobox.Input
onChange={(event) => setQuery(event.target.value)}
spellCheck={false} spellCheck={false}
className="relative h-10 w-full rounded-md border border-black/5 px-3 py-2 shadow-input shadow-black/5 !outline-none placeholder:text-zinc-400 dark:bg-zinc-800 dark:text-zinc-100 dark:shadow-black/10 dark:placeholder:text-zinc-500" autoFocus={false}
placeholder="Enter pubkey or npub..."
className="mb-2 relative h-10 w-full rounded-md px-3 py-2 !outline-none placeholder:text-zinc-500 bg-zinc-700 text-zinc-100"
/> />
<Combobox.Options static>
{query.length > 0 && (
<Combobox.Option
value={query}
className="group w-full flex items-center justify-between px-2 py-2 rounded-md hover:bg-zinc-700"
>
{({ selected }) => (
<>
<div className="flex items-center gap-2">
<img
alt={query}
src={DEFAULT_AVATAR}
className="w-11 h-11 shrink-0 object-cover rounded"
/>
<div className="inline-flex flex-col gap-1">
<span className="text-base leading-tight text-zinc-400">
{query}
</span>
</div>
</div>
{selected && (
<CheckCircleIcon className="w-4 h-4 text-green-500" />
)}
</>
)}
</Combobox.Option>
)}
{status === "loading" ? (
<p>Loading...</p>
) : (
JSON.parse(account.follows).map((follow) => (
<Combobox.Option
key={follow}
value={follow}
className="group w-full flex items-center justify-between px-2 py-2 rounded-md hover:bg-zinc-700"
>
{({ selected }) => (
<>
<User pubkey={follow} />
{selected && (
<CheckCircleIcon className="w-4 h-4 text-green-500" />
)}
</>
)}
</Combobox.Option>
))
)}
</Combobox.Options>
</Combobox>
</div>
</div> </div>
</div> </div>
<div> <div>
@ -184,5 +269,6 @@ export function AddFeedBlock({ parentState }: { parentState: any }) {
</div> </div>
</Dialog> </Dialog>
</Transition> </Transition>
</>
); );
} }

View File

@ -1,10 +1,11 @@
import { Dialog, Transition } from "@headlessui/react"; import { Dialog, Transition } from "@headlessui/react";
import { createBlock } from "@libs/storage"; import { createBlock } from "@libs/storage";
import { NDKEvent, NDKPrivateKeySigner } from "@nostr-dev-kit/ndk"; import { NDKEvent, NDKPrivateKeySigner } from "@nostr-dev-kit/ndk";
import { CancelIcon } from "@shared/icons"; import { CancelIcon, CommandIcon } from "@shared/icons";
import { Image } from "@shared/image"; import { Image } from "@shared/image";
import { RelayContext } from "@shared/relayProvider"; import { RelayContext } from "@shared/relayProvider";
import { DEFAULT_AVATAR } from "@stores/constants"; import { DEFAULT_AVATAR } from "@stores/constants";
import { ADD_IMAGEBLOCK_SHORTCUT } from "@stores/shortcuts";
import { useMutation, useQueryClient } from "@tanstack/react-query"; import { useMutation, useQueryClient } from "@tanstack/react-query";
import { open } from "@tauri-apps/api/dialog"; import { open } from "@tauri-apps/api/dialog";
import { Body, fetch } from "@tauri-apps/api/http"; import { Body, fetch } from "@tauri-apps/api/http";
@ -13,26 +14,30 @@ import { dateToUnix } from "@utils/date";
import { useAccount } from "@utils/hooks/useAccount"; import { useAccount } from "@utils/hooks/useAccount";
import { Fragment, useContext, useEffect, useRef, useState } from "react"; import { Fragment, useContext, useEffect, useRef, useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { useHotkeys } from "react-hotkeys-hook";
export function AddImageBlock({ parentState }: { parentState: any }) { export function AddImageBlock() {
const ndk = useContext(RelayContext); const ndk = useContext(RelayContext);
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [isOpen, setIsOpen] = useState(true); const [isOpen, setIsOpen] = useState(false);
const [image, setImage] = useState(""); const [image, setImage] = useState("");
const { account } = useAccount(); const { account } = useAccount();
const tags = useRef(null); const tags = useRef(null);
const closeModal = () => { const openModal = () => {
// update local state setIsOpen(true);
setIsOpen(false);
// update parent state
parentState(false);
}; };
const closeModal = () => {
setIsOpen(false);
};
useHotkeys(ADD_IMAGEBLOCK_SHORTCUT, () => openModal());
const { const {
register, register,
handleSubmit, handleSubmit,
@ -118,13 +123,11 @@ export function AddImageBlock({ parentState }: { parentState: any }) {
// mutate // mutate
block.mutate({ kind: 0, title: data.title, content: data.content }); block.mutate({ kind: 0, title: data.title, content: data.content });
setTimeout(() => {
setLoading(false); setLoading(false);
// reset form // reset form
reset(); reset();
// close modal // close modal
closeModal(); closeModal();
}, 1200);
}; };
useEffect(() => { useEffect(() => {
@ -132,6 +135,24 @@ export function AddImageBlock({ parentState }: { parentState: any }) {
}, [setValue, image]); }, [setValue, image]);
return ( return (
<>
<button
type="button"
onClick={() => openModal()}
className="inline-flex w-56 h-9 items-center justify-start gap-2.5 rounded-md px-2.5"
>
<div className="flex items-center gap-2">
<div className="inline-flex h-6 w-6 shrink-0 items-center justify-center rounded border-t border-zinc-800/50 bg-zinc-900">
<CommandIcon width={12} height={12} className="text-zinc-500" />
</div>
<div className="inline-flex h-6 w-6 shrink-0 items-center justify-center rounded border-t border-zinc-800/50 bg-zinc-900">
<span className="text-zinc-500 text-sm leading-none">I</span>
</div>
</div>
<div>
<h5 className="font-medium text-zinc-400">New image block</h5>
</div>
</button>
<Transition appear show={isOpen} as={Fragment}> <Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-50" onClose={closeModal}> <Dialog as="div" className="relative z-50" onClose={closeModal}>
<Transition.Child <Transition.Child
@ -272,5 +293,6 @@ export function AddImageBlock({ parentState }: { parentState: any }) {
</div> </div>
</Dialog> </Dialog>
</Transition> </Transition>
</>
); );
} }

View File

@ -1,4 +1,4 @@
import { getNotesByAuthor, removeBlock } from "@libs/storage"; import { getNotesByAuthors, removeBlock } from "@libs/storage";
import { Note } from "@shared/notes/note"; import { Note } from "@shared/notes/note";
import { NoteSkeleton } from "@shared/notes/skeleton"; import { NoteSkeleton } from "@shared/notes/skeleton";
import { TitleBar } from "@shared/titleBar"; import { TitleBar } from "@shared/titleBar";
@ -25,7 +25,7 @@ export function FeedBlock({ params }: { params: any }) {
}: any = useInfiniteQuery({ }: any = useInfiniteQuery({
queryKey: ["newsfeed", params.content], queryKey: ["newsfeed", params.content],
queryFn: async ({ pageParam = 0 }) => { queryFn: async ({ pageParam = 0 }) => {
return await getNotesByAuthor( return await getNotesByAuthors(
params.content, params.content,
TIME, TIME,
ITEM_PER_PAGE, ITEM_PER_PAGE,
@ -91,9 +91,9 @@ export function FeedBlock({ params }: { params: any }) {
className="scrollbar-hide flex w-full h-full flex-col justify-between gap-1.5 pt-1.5 pb-20 overflow-y-auto" className="scrollbar-hide flex w-full h-full flex-col justify-between gap-1.5 pt-1.5 pb-20 overflow-y-auto"
style={{ contain: "strict" }} style={{ contain: "strict" }}
> >
{status === "loading" || isFetching ? ( {status === "loading" ? (
<div className="px-3 py-1.5"> <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"> <div className="rounded-xl border-t border-zinc-800/50 bg-zinc-900 px-3 py-3">
<NoteSkeleton /> <NoteSkeleton />
</div> </div>
</div> </div>
@ -119,6 +119,13 @@ export function FeedBlock({ params }: { params: any }) {
</div> </div>
</div> </div>
)} )}
{isFetching && !isFetchingNextPage && (
<div className="px-3 py-1.5">
<div className="rounded-xl border-t border-zinc-800/50 bg-zinc-900 px-3 py-3">
<NoteSkeleton />
</div>
</div>
)}
</div> </div>
</div> </div>
); );

View File

@ -112,7 +112,7 @@ export function FollowingBlock({ block }: { block: number }) {
> >
{status === "loading" ? ( {status === "loading" ? (
<div className="px-3 py-1.5"> <div className="px-3 py-1.5">
<div className="rounded-md bg-zinc-900 px-3 py-3 shadow-input shadow-black/20"> <div className="rounded-xl border-t border-zinc-800/50 bg-zinc-900 px-3 py-3">
<NoteSkeleton /> <NoteSkeleton />
</div> </div>
</div> </div>
@ -140,7 +140,7 @@ export function FollowingBlock({ block }: { block: number }) {
)} )}
{isFetching && !isFetchingNextPage && ( {isFetching && !isFetchingNextPage && (
<div className="px-3 py-1.5"> <div className="px-3 py-1.5">
<div className="rounded-md bg-zinc-900 px-3 py-3 shadow-input shadow-black/20"> <div className="rounded-xl border-t border-zinc-800/50 bg-zinc-900 px-3 py-3">
<NoteSkeleton /> <NoteSkeleton />
</div> </div>
</div> </div>

View File

@ -21,7 +21,10 @@ export function ImageBlock({ params }: { params: any }) {
<div className="shrink-0 w-[350px] h-full flex flex-col justify-between border-r border-zinc-900"> <div className="shrink-0 w-[350px] h-full flex flex-col justify-between border-r border-zinc-900">
<div className="relative flex-1 w-full h-full p-3 overflow-hidden"> <div className="relative flex-1 w-full h-full p-3 overflow-hidden">
<div className="absolute top-3 left-0 w-full h-16 px-3"> <div className="absolute top-3 left-0 w-full h-16 px-3">
<div className="h-16 rounded-t-xl overflow-hidden flex items-center justify-end px-5"> <div className="h-16 rounded-t-xl overflow-hidden flex items-center justify-between px-5">
<h3 className="text-white font-medium drop-shadow-lg">
{params.title}
</h3>
<button <button
type="button" type="button"
onClick={() => block.mutate(params.id)} onClick={() => block.mutate(params.id)}

View File

@ -65,12 +65,12 @@ export function SpaceScreen() {
</div> </div>
</div> </div>
)} )}
<div className="shrink-0 w-[90px]"> <div className="shrink-0 w-[350px] flex-col flex border-r border-zinc-900">
<div className="w-full h-full inline-flex items-center justify-center"> <div className="w-full h-full inline-flex items-center justify-center">
<AddBlock /> <AddBlock />
</div> </div>
</div> </div>
<div className="shrink-0 w-[360px]" /> <div className="shrink-0 w-[350px]" />
</div> </div>
); );
} }

View File

@ -135,12 +135,14 @@ export async function countTotalNotes() {
const result = await db.select( const result = await db.select(
'SELECT COUNT(*) AS "total" FROM notes WHERE kind IN (1, 6);', 'SELECT COUNT(*) AS "total" FROM notes WHERE kind IN (1, 6);',
); );
return result[0].total; return parseInt(result[0].total);
} }
// get all notes // get all notes
export async function getNotes(time: number, limit: number, offset: number) { export async function getNotes(time: number, limit: number, offset: number) {
const db = await connect(); const db = await connect();
const totalNotes = await countTotalNotes();
const nextCursor = offset + limit;
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(
@ -148,19 +150,22 @@ export async function getNotes(time: number, limit: number, offset: number) {
); );
notes["data"] = query; notes["data"] = query;
notes["nextCursor"] = offset + limit; notes["nextCursor"] =
Math.round(totalNotes / nextCursor) > 1 ? nextCursor : undefined;
return notes; return notes;
} }
// get all notes by authors // get all notes by pubkey
export async function getNotesByAuthor( export async function getNotesByPubkey(
pubkey: string, pubkey: string,
time: number, time: number,
limit: number, limit: number,
offset: number, offset: number,
) { ) {
const db = await connect(); const db = await connect();
const totalNotes = await countTotalNotes();
const nextCursor = offset + limit;
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(
@ -168,7 +173,33 @@ export async function getNotesByAuthor(
); );
notes["data"] = query; notes["data"] = query;
notes["nextCursor"] = offset + limit; notes["nextCursor"] =
Math.round(totalNotes / nextCursor) > 1 ? nextCursor : undefined;
return notes;
}
// get all notes by authors
export async function getNotesByAuthors(
authors: string,
time: number,
limit: number,
offset: number,
) {
const db = await connect();
const totalNotes = await countTotalNotes();
const nextCursor = offset + limit;
const array = JSON.parse(authors);
const finalArray = `'${array.join("','")}'`;
const notes: any = { data: null, nextCursor: 0 };
const query: any = await db.select(
`SELECT * FROM notes WHERE created_at <= "${time}" AND pubkey IN (${finalArray}) AND kind IN (1, 6, 1063) GROUP BY parent_id ORDER BY created_at DESC LIMIT "${limit}" OFFSET "${offset}";`,
);
notes["data"] = query;
notes["nextCursor"] =
Math.round(totalNotes / nextCursor) > 1 ? nextCursor : undefined;
return notes; return notes;
} }

View File

@ -64,7 +64,7 @@ export function Navigation({ reverse = false }: { reverse?: boolean }) {
</NavLink> </NavLink>
</div> </div>
</div> </div>
{/* Channels */} {/* Channels
<Disclosure defaultOpen={true}> <Disclosure defaultOpen={true}>
{({ open }) => ( {({ open }) => (
<div className="flex flex-col gap-0.5 px-1.5"> <div className="flex flex-col gap-0.5 px-1.5">
@ -90,6 +90,7 @@ export function Navigation({ reverse = false }: { reverse?: boolean }) {
</div> </div>
)} )}
</Disclosure> </Disclosure>
*/}
{/* Chats */} {/* Chats */}
<Disclosure defaultOpen={true}> <Disclosure defaultOpen={true}>
{({ open }) => ( {({ open }) => (

View File

@ -53,7 +53,7 @@ export function Note({ event, block }: Note) {
Lume isn't fully support this kind in newsfeed Lume isn't fully support this kind in newsfeed
</p> </p>
</div> </div>
<div className="markdown"> <div className="select-text whitespace-pre-line break-words text-base text-zinc-100">
<p>{event.content}</p> <p>{event.content}</p>
</div> </div>
</div> </div>
@ -63,7 +63,7 @@ export function Note({ event, block }: Note) {
return ( return (
<div className="h-min w-full px-3 py-1.5"> <div className="h-min w-full px-3 py-1.5">
<div className="rounded-md bg-zinc-900 px-5 pt-5"> <div className="rounded-xl border-t border-zinc-800/50 bg-zinc-900 px-3 pt-3">
{renderParent} {renderParent}
<div className="flex flex-col"> <div className="flex flex-col">
<User <User

View File

@ -32,7 +32,7 @@ export function NoteParent({
Lume isn't fully support this kind in newsfeed Lume isn't fully support this kind in newsfeed
</p> </p>
</div> </div>
<div className="markdown"> <div className="select-text whitespace-pre-line break-words text-base text-zinc-100">
<p>{data.content || data.toString()}</p> <p>{data.content || data.toString()}</p>
</div> </div>
</div> </div>

View File

@ -10,7 +10,7 @@ export function ImagePreview({ urls }: { urls: string[] }) {
src={url} src={url}
fallback="https://void.cat/d/XTmrMkpid8DGLjv1AzdvcW" fallback="https://void.cat/d/XTmrMkpid8DGLjv1AzdvcW"
alt="image" alt="image"
className="h-auto w-full rounded-lg object-cover" className="h-auto w-full border border-zinc-800/50 rounded-lg object-cover"
/> />
</div> </div>
))} ))}

View File

@ -20,7 +20,7 @@ export function LinkPreview({ urls }: { urls: string[] }) {
</div> </div>
) : ( ) : (
<a <a
className="flex flex-col rounded-lg border border-transparent hover:border-fuchsia-900" className="flex flex-col rounded-lg border border-zinc-800/50 hover:border-fuchsia-900"
href={urls[0]} href={urls[0]}
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"

View File

@ -4,9 +4,14 @@ export function VideoPreview({ urls }: { urls: string[] }) {
return ( return (
<div className="relative mt-3 max-w-[420px] flex w-full flex-col gap-2"> <div className="relative mt-3 max-w-[420px] flex w-full flex-col gap-2">
{urls.map((url) => ( {urls.map((url) => (
<div key={url} className="aspect-video"> <ReactPlayer
<ReactPlayer url={url} width="100%" height="100%" /> key={url}
</div> url={url}
width="100%"
className="w-full h-auto border border-zinc-800/50 rounded-lg"
controls={true}
pip={true}
/>
))} ))}
</div> </div>
); );

View File

@ -29,8 +29,10 @@ export function RepliesList({ parent_id }: { parent_id: string }) {
<div className="px=3"> <div className="px=3">
<div className="w-full flex items-center justify-center rounded-md bg-zinc-900"> <div className="w-full flex items-center justify-center rounded-md bg-zinc-900">
<div className="py-6 flex flex-col items-center justify-center gap-2"> <div className="py-6 flex flex-col items-center justify-center gap-2">
<EmptyIcon width={56} height={56} /> <h3 className="text-3xl">👋</h3>
<p className="text-zinc-500 text-sm font-medium">No replies</p> <p className="leading-none text-zinc-400">
Share your thought on it...
</p>
</div> </div>
</div> </div>
</div> </div>

View File

@ -15,7 +15,8 @@ export function Repost({
const { status, data, isFetching } = useEvent(repostID); const { status, data, isFetching } = useEvent(repostID);
return ( return (
<div className="relative overflow-hidden flex flex-col mt-12"> <div className="relative flex flex-col mt-12">
<div className="absolute left-[18px] -top-10 h-[50px] w-0.5 bg-gradient-to-t from-zinc-800 to-zinc-600" />
{isFetching || status === "loading" ? ( {isFetching || status === "loading" ? (
<NoteSkeleton /> <NoteSkeleton />
) : ( ) : (
@ -34,7 +35,7 @@ export function Repost({
Lume isn't fully support this kind in newsfeed Lume isn't fully support this kind in newsfeed
</p> </p>
</div> </div>
<div className="markdown"> <div className="select-text whitespace-pre-line break-words text-base text-zinc-100">
<p>{data.content || data.toString()}</p> <p>{data.content || data.toString()}</p>
</div> </div>
</div> </div>

View File

@ -33,14 +33,14 @@ export function User({
}`} }`}
> >
<Popover.Button <Popover.Button
className={`${avatarWidth} ${avatarHeight} shrink-0 overflow-hidden`} className={`${avatarWidth} ${avatarHeight} relative z-10 bg-zinc-900 shrink-0 overflow-hidden`}
> >
<Image <Image
src={user?.image} src={user?.image}
fallback={DEFAULT_AVATAR} fallback={DEFAULT_AVATAR}
alt={pubkey} alt={pubkey}
className={`${avatarWidth} ${avatarHeight} ${ className={`${avatarWidth} ${avatarHeight} ${
size === "small" ? "rounded" : "rounded-md" size === "small" ? "rounded" : "rounded-lg"
} object-cover`} } object-cover`}
/> />
</Popover.Button> </Popover.Button>

View File

@ -1 +1,3 @@
export const COMPOSE_SHORTCUT = "meta+n"; export const COMPOSE_SHORTCUT = "meta+n";
export const ADD_IMAGEBLOCK_SHORTCUT = "meta+i";
export const ADD_FEEDBLOCK_SHORTCUT = "meta+f";