mirror of
https://github.com/luminous-devs/lume.git
synced 2024-09-19 11:43:30 +00:00
WIP
This commit is contained in:
parent
6af0b453e3
commit
7fb62a6afa
@ -27,6 +27,7 @@ export function ChatsList() {
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<NewMessageModal />
|
||||
{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>
|
||||
)}
|
||||
<NewMessageModal />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import { useNavigate } from "react-router-dom";
|
||||
export function NewMessageModal() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { status, data, isFetching }: any = useQuery(["plebs"], async () => {
|
||||
const { status, data }: any = useQuery(["plebs"], async () => {
|
||||
return await getPlebs();
|
||||
});
|
||||
|
||||
@ -96,7 +96,7 @@ export function NewMessageModal() {
|
||||
</div>
|
||||
</div>
|
||||
<div className="h-[500px] flex flex-col pb-5 overflow-x-hidden overflow-y-auto">
|
||||
{status === "loading" || isFetching ? (
|
||||
{status === "loading" ? (
|
||||
<p>Loading...</p>
|
||||
) : (
|
||||
data.map((pleb) => (
|
||||
@ -111,10 +111,10 @@ export function NewMessageModal() {
|
||||
className="w-9 h-9 shrink-0 object-cover rounded"
|
||||
/>
|
||||
<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}
|
||||
</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.npub.substring(0, 16).concat("...")}
|
||||
</span>
|
||||
|
@ -144,8 +144,8 @@ export function Root() {
|
||||
const notes = await fetchNotes();
|
||||
if (notes) {
|
||||
const chats = await fetchChats();
|
||||
const channels = await fetchChannelMessages();
|
||||
if (chats && channels) {
|
||||
// const channels = await fetchChannelMessages();
|
||||
if (chats) {
|
||||
navigate("/app/space", { replace: true });
|
||||
}
|
||||
}
|
||||
|
@ -1,66 +1,11 @@
|
||||
import { AddFeedBlock } from "@app/space/components/addFeed";
|
||||
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() {
|
||||
const [imageModal, setImageModal] = useState(false);
|
||||
const [feedModal, setFeedModal] = useState(false);
|
||||
|
||||
const openAddImageModal = () => {
|
||||
setImageModal(true);
|
||||
};
|
||||
|
||||
const openAddFeedModal = () => {
|
||||
setFeedModal(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Menu as="div" className="relative inline-block text-left">
|
||||
<Menu.Button className="group inline-flex flex-col items-center gap-2.5">
|
||||
<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>
|
||||
</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} />}
|
||||
</>
|
||||
<div className="flex flex-col gap-1">
|
||||
<AddImageBlock />
|
||||
<AddFeedBlock />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,24 +1,37 @@
|
||||
import { User } from "@app/auth/components/user";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { createBlock } from "@libs/storage";
|
||||
import { CancelIcon } from "@shared/icons";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { Combobox } from "@headlessui/react";
|
||||
import { createBlock, getPlebs } from "@libs/storage";
|
||||
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 { Fragment, useState } from "react";
|
||||
import { Fragment, useEffect, useState } from "react";
|
||||
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 [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 = () => {
|
||||
// update local state
|
||||
setIsOpen(false);
|
||||
// update parent state
|
||||
parentState(false);
|
||||
};
|
||||
|
||||
useHotkeys(ADD_FEEDBLOCK_SHORTCUT, () => openModal());
|
||||
|
||||
const block = useMutation({
|
||||
mutationFn: (data: any) => {
|
||||
return createBlock(data.kind, data.title, data.content);
|
||||
@ -38,151 +51,224 @@ export function AddFeedBlock({ parentState }: { parentState: any }) {
|
||||
const onSubmit = (data: any) => {
|
||||
setLoading(true);
|
||||
|
||||
let pubkey = data.content;
|
||||
|
||||
if (pubkey.substring(0, 4) === "npub") {
|
||||
pubkey = nip19.decode(pubkey).data;
|
||||
}
|
||||
selected.forEach((item, index) => {
|
||||
if (item.substring(0, 4) === "npub") {
|
||||
selected[index] = nip19.decode(item).data;
|
||||
}
|
||||
});
|
||||
|
||||
// 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);
|
||||
// reset form
|
||||
reset();
|
||||
// close modal
|
||||
closeModal();
|
||||
}, 1200);
|
||||
setLoading(false);
|
||||
// reset form
|
||||
reset();
|
||||
// close modal
|
||||
closeModal();
|
||||
};
|
||||
|
||||
return (
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
<Dialog as="div" className="relative z-50" onClose={closeModal}>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 z-50 bg-black bg-opacity-30 backdrop-blur-md" />
|
||||
</Transition.Child>
|
||||
<div className="fixed inset-0 z-50 flex min-h-full items-center justify-center">
|
||||
<>
|
||||
<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}>
|
||||
<Dialog as="div" className="relative z-50" onClose={closeModal}>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<Dialog.Panel className="relative flex h-min w-full max-w-lg flex-col gap-2 rounded-xl border-t border-zinc-800/50 bg-zinc-900">
|
||||
<div className="h-min w-full shrink-0 border-b border-zinc-800 px-5 py-5">
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<Dialog.Title
|
||||
as="h3"
|
||||
className="text-lg font-semibold leading-none text-zinc-100"
|
||||
>
|
||||
Create image block
|
||||
</Dialog.Title>
|
||||
<button
|
||||
type="button"
|
||||
onClick={closeModal}
|
||||
className="inline-flex h-5 w-5 items-center justify-center rounded hover:bg-zinc-900"
|
||||
>
|
||||
<CancelIcon
|
||||
width={14}
|
||||
height={14}
|
||||
className="text-zinc-300"
|
||||
/>
|
||||
</button>
|
||||
<div className="fixed inset-0 z-50 bg-black bg-opacity-30 backdrop-blur-md" />
|
||||
</Transition.Child>
|
||||
<div className="fixed inset-0 z-50 flex min-h-full items-center justify-center">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Dialog.Panel className="relative flex h-min w-full max-w-lg flex-col gap-2 rounded-xl border-t border-zinc-800/50 bg-zinc-900">
|
||||
<div className="h-min w-full shrink-0 border-b border-zinc-800 px-5 py-5">
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<Dialog.Title
|
||||
as="h3"
|
||||
className="text-lg font-semibold leading-none text-zinc-100"
|
||||
>
|
||||
Create feed block
|
||||
</Dialog.Title>
|
||||
<button
|
||||
type="button"
|
||||
onClick={closeModal}
|
||||
className="inline-flex h-5 w-5 items-center justify-center rounded hover:bg-zinc-900"
|
||||
>
|
||||
<CancelIcon
|
||||
width={14}
|
||||
height={14}
|
||||
className="text-zinc-300"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<Dialog.Description className="text-sm leading-tight text-zinc-400">
|
||||
Specific newsfeed space for people you want to keep up to
|
||||
date
|
||||
</Dialog.Description>
|
||||
</div>
|
||||
<Dialog.Description className="text-sm leading-tight text-zinc-400">
|
||||
Pin your favorite image to Space then you can view every
|
||||
time that you use Lume, your image will be broadcast to
|
||||
Nostr Relay as well
|
||||
</Dialog.Description>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex h-full w-full flex-col overflow-y-auto px-5 pb-5 pt-3">
|
||||
<form
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
className="flex h-full w-full flex-col gap-4 mb-0"
|
||||
>
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-sm font-medium uppercase tracking-wider text-zinc-400">
|
||||
Title *
|
||||
</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="flex h-full w-full flex-col overflow-y-auto px-5 pb-5 pt-3">
|
||||
<form
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
className="flex h-full w-full flex-col gap-4 mb-0"
|
||||
>
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-sm font-medium uppercase tracking-wider text-zinc-400">
|
||||
Title *
|
||||
</label>
|
||||
<input
|
||||
type={"text"}
|
||||
{...register("title", {
|
||||
required: true,
|
||||
})}
|
||||
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 className="flex flex-col gap-1">
|
||||
<label className="text-sm font-medium uppercase tracking-wider text-zinc-400">
|
||||
Pubkey OR Npub *
|
||||
</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
|
||||
type={"text"}
|
||||
{...register("content", {
|
||||
required: true,
|
||||
})}
|
||||
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"
|
||||
/>
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-sm font-medium uppercase tracking-wider text-zinc-400">
|
||||
Choose at least 1 user *
|
||||
</label>
|
||||
<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">
|
||||
<div className="w-full px-3 py-2">
|
||||
<Combobox
|
||||
value={selected}
|
||||
onChange={setSelected}
|
||||
multiple
|
||||
>
|
||||
<Combobox.Input
|
||||
onChange={(event) => setQuery(event.target.value)}
|
||||
spellCheck={false}
|
||||
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>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={!isDirty || !isValid}
|
||||
className="inline-flex h-11 w-full transform items-center justify-center rounded-lg bg-fuchsia-500 font-medium text-zinc-100 shadow-button active:translate-y-1 disabled:cursor-not-allowed disabled:opacity-30"
|
||||
>
|
||||
{loading ? (
|
||||
<svg
|
||||
className="h-4 w-4 animate-spin text-black dark:text-zinc-100"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<title id="loading">Loading</title>
|
||||
<circle
|
||||
className="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
strokeWidth="4"
|
||||
/>
|
||||
<path
|
||||
className="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
/>
|
||||
</svg>
|
||||
) : (
|
||||
"Confirm"
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
<div>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={!isDirty || !isValid}
|
||||
className="inline-flex h-11 w-full transform items-center justify-center rounded-lg bg-fuchsia-500 font-medium text-zinc-100 shadow-button active:translate-y-1 disabled:cursor-not-allowed disabled:opacity-30"
|
||||
>
|
||||
{loading ? (
|
||||
<svg
|
||||
className="h-4 w-4 animate-spin text-black dark:text-zinc-100"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<title id="loading">Loading</title>
|
||||
<circle
|
||||
className="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
strokeWidth="4"
|
||||
/>
|
||||
<path
|
||||
className="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
/>
|
||||
</svg>
|
||||
) : (
|
||||
"Confirm"
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -1,10 +1,11 @@
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { createBlock } from "@libs/storage";
|
||||
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 { RelayContext } from "@shared/relayProvider";
|
||||
import { DEFAULT_AVATAR } from "@stores/constants";
|
||||
import { ADD_IMAGEBLOCK_SHORTCUT } from "@stores/shortcuts";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { open } from "@tauri-apps/api/dialog";
|
||||
import { Body, fetch } from "@tauri-apps/api/http";
|
||||
@ -13,26 +14,30 @@ import { dateToUnix } from "@utils/date";
|
||||
import { useAccount } from "@utils/hooks/useAccount";
|
||||
import { Fragment, useContext, useEffect, useRef, useState } from "react";
|
||||
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 queryClient = useQueryClient();
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [isOpen, setIsOpen] = useState(true);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [image, setImage] = useState("");
|
||||
|
||||
const { account } = useAccount();
|
||||
|
||||
const tags = useRef(null);
|
||||
|
||||
const closeModal = () => {
|
||||
// update local state
|
||||
setIsOpen(false);
|
||||
// update parent state
|
||||
parentState(false);
|
||||
const openModal = () => {
|
||||
setIsOpen(true);
|
||||
};
|
||||
|
||||
const closeModal = () => {
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
useHotkeys(ADD_IMAGEBLOCK_SHORTCUT, () => openModal());
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
@ -118,13 +123,11 @@ export function AddImageBlock({ parentState }: { parentState: any }) {
|
||||
// mutate
|
||||
block.mutate({ kind: 0, title: data.title, content: data.content });
|
||||
|
||||
setTimeout(() => {
|
||||
setLoading(false);
|
||||
// reset form
|
||||
reset();
|
||||
// close modal
|
||||
closeModal();
|
||||
}, 1200);
|
||||
setLoading(false);
|
||||
// reset form
|
||||
reset();
|
||||
// close modal
|
||||
closeModal();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@ -132,145 +135,164 @@ export function AddImageBlock({ parentState }: { parentState: any }) {
|
||||
}, [setValue, image]);
|
||||
|
||||
return (
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
<Dialog as="div" className="relative z-50" onClose={closeModal}>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 z-50 bg-black bg-opacity-30 backdrop-blur-md" />
|
||||
</Transition.Child>
|
||||
<div className="fixed inset-0 z-50 flex min-h-full items-center justify-center">
|
||||
<>
|
||||
<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}>
|
||||
<Dialog as="div" className="relative z-50" onClose={closeModal}>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<Dialog.Panel className="relative flex h-min w-full max-w-lg flex-col gap-2 rounded-xl border-t border-zinc-800/50 bg-zinc-900">
|
||||
<div className="h-min w-full shrink-0 border-b border-zinc-800 px-5 py-5">
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<Dialog.Title
|
||||
as="h3"
|
||||
className="text-lg font-semibold leading-none text-zinc-100"
|
||||
>
|
||||
Create image block
|
||||
</Dialog.Title>
|
||||
<button
|
||||
type="button"
|
||||
onClick={closeModal}
|
||||
className="inline-flex h-5 w-5 items-center justify-center rounded hover:bg-zinc-900"
|
||||
>
|
||||
<CancelIcon
|
||||
width={14}
|
||||
height={14}
|
||||
className="text-zinc-300"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<Dialog.Description className="text-sm leading-tight text-zinc-400">
|
||||
Pin your favorite image to Space then you can view every
|
||||
time that you use Lume, your image will be broadcast to
|
||||
Nostr Relay as well
|
||||
</Dialog.Description>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex h-full w-full flex-col overflow-y-auto px-5 pb-5 pt-3">
|
||||
<form
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
className="flex h-full w-full flex-col gap-4 mb-0"
|
||||
>
|
||||
<input
|
||||
type={"hidden"}
|
||||
{...register("content")}
|
||||
value={image}
|
||||
className="relative h-10 w-full rounded-lg 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"
|
||||
/>
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-sm font-medium uppercase tracking-wider text-zinc-400">
|
||||
Title *
|
||||
</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
|
||||
type={"text"}
|
||||
{...register("title", {
|
||||
required: true,
|
||||
})}
|
||||
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"
|
||||
/>
|
||||
<div className="fixed inset-0 z-50 bg-black bg-opacity-30 backdrop-blur-md" />
|
||||
</Transition.Child>
|
||||
<div className="fixed inset-0 z-50 flex min-h-full items-center justify-center">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Dialog.Panel className="relative flex h-min w-full max-w-lg flex-col gap-2 rounded-xl border-t border-zinc-800/50 bg-zinc-900">
|
||||
<div className="h-min w-full shrink-0 border-b border-zinc-800 px-5 py-5">
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<Dialog.Title
|
||||
as="h3"
|
||||
className="text-lg font-semibold leading-none text-zinc-100"
|
||||
>
|
||||
Create image block
|
||||
</Dialog.Title>
|
||||
<button
|
||||
type="button"
|
||||
onClick={closeModal}
|
||||
className="inline-flex h-5 w-5 items-center justify-center rounded hover:bg-zinc-900"
|
||||
>
|
||||
<CancelIcon
|
||||
width={14}
|
||||
height={14}
|
||||
className="text-zinc-300"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<Dialog.Description className="text-sm leading-tight text-zinc-400">
|
||||
Pin your favorite image to Space then you can view every
|
||||
time that you use Lume, your image will be broadcast to
|
||||
Nostr Relay as well
|
||||
</Dialog.Description>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-sm font-medium uppercase tracking-wider text-zinc-400">
|
||||
Picture
|
||||
</label>
|
||||
<div className="relative inline-flex h-56 w-full items-center justify-center overflow-hidden rounded-lg border border-zinc-900 bg-zinc-950">
|
||||
<Image
|
||||
src={image}
|
||||
fallback={DEFAULT_AVATAR}
|
||||
alt="content"
|
||||
className="relative z-10 max-h-[156px] h-auto w-[150px] object-cover rounded-md"
|
||||
/>
|
||||
<div className="absolute bottom-3 right-3 z-10">
|
||||
<button
|
||||
onClick={() => openFileDialog()}
|
||||
type="button"
|
||||
className="inline-flex h-6 items-center justify-center rounded bg-zinc-900 px-3 text-sm font-medium text-zinc-300 ring-1 ring-zinc-800 hover:bg-zinc-800"
|
||||
>
|
||||
Upload
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex h-full w-full flex-col overflow-y-auto px-5 pb-5 pt-3">
|
||||
<form
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
className="flex h-full w-full flex-col gap-4 mb-0"
|
||||
>
|
||||
<input
|
||||
type={"hidden"}
|
||||
{...register("content")}
|
||||
value={image}
|
||||
className="relative h-10 w-full rounded-lg 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"
|
||||
/>
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-sm font-medium uppercase tracking-wider text-zinc-400">
|
||||
Title *
|
||||
</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
|
||||
type={"text"}
|
||||
{...register("title", {
|
||||
required: true,
|
||||
})}
|
||||
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"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={!isDirty || !isValid}
|
||||
className="inline-flex h-11 w-full transform items-center justify-center rounded-lg bg-fuchsia-500 font-medium text-zinc-100 shadow-button active:translate-y-1 disabled:cursor-not-allowed disabled:opacity-30"
|
||||
>
|
||||
{loading ? (
|
||||
<svg
|
||||
className="h-4 w-4 animate-spin text-black dark:text-zinc-100"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<title id="loading">Loading</title>
|
||||
<circle
|
||||
className="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
strokeWidth="4"
|
||||
/>
|
||||
<path
|
||||
className="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
/>
|
||||
</svg>
|
||||
) : (
|
||||
"Confirm"
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-sm font-medium uppercase tracking-wider text-zinc-400">
|
||||
Picture
|
||||
</label>
|
||||
<div className="relative inline-flex h-56 w-full items-center justify-center overflow-hidden rounded-lg border border-zinc-900 bg-zinc-950">
|
||||
<Image
|
||||
src={image}
|
||||
fallback={DEFAULT_AVATAR}
|
||||
alt="content"
|
||||
className="relative z-10 max-h-[156px] h-auto w-[150px] object-cover rounded-md"
|
||||
/>
|
||||
<div className="absolute bottom-3 right-3 z-10">
|
||||
<button
|
||||
onClick={() => openFileDialog()}
|
||||
type="button"
|
||||
className="inline-flex h-6 items-center justify-center rounded bg-zinc-900 px-3 text-sm font-medium text-zinc-300 ring-1 ring-zinc-800 hover:bg-zinc-800"
|
||||
>
|
||||
Upload
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={!isDirty || !isValid}
|
||||
className="inline-flex h-11 w-full transform items-center justify-center rounded-lg bg-fuchsia-500 font-medium text-zinc-100 shadow-button active:translate-y-1 disabled:cursor-not-allowed disabled:opacity-30"
|
||||
>
|
||||
{loading ? (
|
||||
<svg
|
||||
className="h-4 w-4 animate-spin text-black dark:text-zinc-100"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<title id="loading">Loading</title>
|
||||
<circle
|
||||
className="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
strokeWidth="4"
|
||||
/>
|
||||
<path
|
||||
className="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
/>
|
||||
</svg>
|
||||
) : (
|
||||
"Confirm"
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { getNotesByAuthor, removeBlock } from "@libs/storage";
|
||||
import { getNotesByAuthors, removeBlock } from "@libs/storage";
|
||||
import { Note } from "@shared/notes/note";
|
||||
import { NoteSkeleton } from "@shared/notes/skeleton";
|
||||
import { TitleBar } from "@shared/titleBar";
|
||||
@ -25,7 +25,7 @@ export function FeedBlock({ params }: { params: any }) {
|
||||
}: any = useInfiniteQuery({
|
||||
queryKey: ["newsfeed", params.content],
|
||||
queryFn: async ({ pageParam = 0 }) => {
|
||||
return await getNotesByAuthor(
|
||||
return await getNotesByAuthors(
|
||||
params.content,
|
||||
TIME,
|
||||
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"
|
||||
style={{ contain: "strict" }}
|
||||
>
|
||||
{status === "loading" || isFetching ? (
|
||||
{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">
|
||||
<div className="rounded-xl border-t border-zinc-800/50 bg-zinc-900 px-3 py-3">
|
||||
<NoteSkeleton />
|
||||
</div>
|
||||
</div>
|
||||
@ -119,6 +119,13 @@ export function FeedBlock({ params }: { params: any }) {
|
||||
</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>
|
||||
);
|
||||
|
@ -112,7 +112,7 @@ export function FollowingBlock({ block }: { block: number }) {
|
||||
>
|
||||
{status === "loading" ? (
|
||||
<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 />
|
||||
</div>
|
||||
</div>
|
||||
@ -140,7 +140,7 @@ export function FollowingBlock({ block }: { block: number }) {
|
||||
)}
|
||||
{isFetching && !isFetchingNextPage && (
|
||||
<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 />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -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="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="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
|
||||
type="button"
|
||||
onClick={() => block.mutate(params.id)}
|
||||
|
@ -65,12 +65,12 @@ export function SpaceScreen() {
|
||||
</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">
|
||||
<AddBlock />
|
||||
</div>
|
||||
</div>
|
||||
<div className="shrink-0 w-[360px]" />
|
||||
<div className="shrink-0 w-[350px]" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -135,12 +135,14 @@ export async function countTotalNotes() {
|
||||
const result = await db.select(
|
||||
'SELECT COUNT(*) AS "total" FROM notes WHERE kind IN (1, 6);',
|
||||
);
|
||||
return result[0].total;
|
||||
return parseInt(result[0].total);
|
||||
}
|
||||
|
||||
// get all notes
|
||||
export async function getNotes(time: number, limit: number, offset: number) {
|
||||
const db = await connect();
|
||||
const totalNotes = await countTotalNotes();
|
||||
const nextCursor = offset + limit;
|
||||
|
||||
const notes: any = { data: null, nextCursor: 0 };
|
||||
const query: any = await db.select(
|
||||
@ -148,19 +150,22 @@ export async function getNotes(time: number, limit: number, offset: number) {
|
||||
);
|
||||
|
||||
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 getNotesByAuthor(
|
||||
// get all notes by pubkey
|
||||
export async function getNotesByPubkey(
|
||||
pubkey: string,
|
||||
time: number,
|
||||
limit: number,
|
||||
offset: number,
|
||||
) {
|
||||
const db = await connect();
|
||||
const totalNotes = await countTotalNotes();
|
||||
const nextCursor = offset + limit;
|
||||
|
||||
const notes: any = { data: null, nextCursor: 0 };
|
||||
const query: any = await db.select(
|
||||
@ -168,7 +173,33 @@ export async function getNotesByAuthor(
|
||||
);
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ export function Navigation({ reverse = false }: { reverse?: boolean }) {
|
||||
</NavLink>
|
||||
</div>
|
||||
</div>
|
||||
{/* Channels */}
|
||||
{/* Channels
|
||||
<Disclosure defaultOpen={true}>
|
||||
{({ open }) => (
|
||||
<div className="flex flex-col gap-0.5 px-1.5">
|
||||
@ -90,6 +90,7 @@ export function Navigation({ reverse = false }: { reverse?: boolean }) {
|
||||
</div>
|
||||
)}
|
||||
</Disclosure>
|
||||
*/}
|
||||
{/* Chats */}
|
||||
<Disclosure defaultOpen={true}>
|
||||
{({ open }) => (
|
||||
|
@ -53,7 +53,7 @@ export function Note({ event, block }: Note) {
|
||||
Lume isn't fully support this kind in newsfeed
|
||||
</p>
|
||||
</div>
|
||||
<div className="markdown">
|
||||
<div className="select-text whitespace-pre-line break-words text-base text-zinc-100">
|
||||
<p>{event.content}</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -63,7 +63,7 @@ export function Note({ event, block }: Note) {
|
||||
|
||||
return (
|
||||
<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}
|
||||
<div className="flex flex-col">
|
||||
<User
|
||||
|
@ -32,7 +32,7 @@ export function NoteParent({
|
||||
Lume isn't fully support this kind in newsfeed
|
||||
</p>
|
||||
</div>
|
||||
<div className="markdown">
|
||||
<div className="select-text whitespace-pre-line break-words text-base text-zinc-100">
|
||||
<p>{data.content || data.toString()}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -10,7 +10,7 @@ export function ImagePreview({ urls }: { urls: string[] }) {
|
||||
src={url}
|
||||
fallback="https://void.cat/d/XTmrMkpid8DGLjv1AzdvcW"
|
||||
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>
|
||||
))}
|
||||
|
@ -20,7 +20,7 @@ export function LinkPreview({ urls }: { urls: string[] }) {
|
||||
</div>
|
||||
) : (
|
||||
<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]}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
|
@ -4,9 +4,14 @@ export function VideoPreview({ urls }: { urls: string[] }) {
|
||||
return (
|
||||
<div className="relative mt-3 max-w-[420px] flex w-full flex-col gap-2">
|
||||
{urls.map((url) => (
|
||||
<div key={url} className="aspect-video">
|
||||
<ReactPlayer url={url} width="100%" height="100%" />
|
||||
</div>
|
||||
<ReactPlayer
|
||||
key={url}
|
||||
url={url}
|
||||
width="100%"
|
||||
className="w-full h-auto border border-zinc-800/50 rounded-lg"
|
||||
controls={true}
|
||||
pip={true}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
@ -29,8 +29,10 @@ export function RepliesList({ parent_id }: { parent_id: string }) {
|
||||
<div className="px=3">
|
||||
<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">
|
||||
<EmptyIcon width={56} height={56} />
|
||||
<p className="text-zinc-500 text-sm font-medium">No replies</p>
|
||||
<h3 className="text-3xl">👋</h3>
|
||||
<p className="leading-none text-zinc-400">
|
||||
Share your thought on it...
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -15,7 +15,8 @@ export function Repost({
|
||||
const { status, data, isFetching } = useEvent(repostID);
|
||||
|
||||
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" ? (
|
||||
<NoteSkeleton />
|
||||
) : (
|
||||
@ -34,7 +35,7 @@ export function Repost({
|
||||
Lume isn't fully support this kind in newsfeed
|
||||
</p>
|
||||
</div>
|
||||
<div className="markdown">
|
||||
<div className="select-text whitespace-pre-line break-words text-base text-zinc-100">
|
||||
<p>{data.content || data.toString()}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -33,14 +33,14 @@ export function User({
|
||||
}`}
|
||||
>
|
||||
<Popover.Button
|
||||
className={`${avatarWidth} ${avatarHeight} shrink-0 overflow-hidden`}
|
||||
className={`${avatarWidth} ${avatarHeight} relative z-10 bg-zinc-900 shrink-0 overflow-hidden`}
|
||||
>
|
||||
<Image
|
||||
src={user?.image}
|
||||
fallback={DEFAULT_AVATAR}
|
||||
alt={pubkey}
|
||||
className={`${avatarWidth} ${avatarHeight} ${
|
||||
size === "small" ? "rounded" : "rounded-md"
|
||||
size === "small" ? "rounded" : "rounded-lg"
|
||||
} object-cover`}
|
||||
/>
|
||||
</Popover.Button>
|
||||
|
@ -1 +1,3 @@
|
||||
export const COMPOSE_SHORTCUT = "meta+n";
|
||||
export const ADD_IMAGEBLOCK_SHORTCUT = "meta+i";
|
||||
export const ADD_FEEDBLOCK_SHORTCUT = "meta+f";
|
||||
|
Loading…
Reference in New Issue
Block a user