mirror of
https://github.com/luminous-devs/lume.git
synced 2024-09-19 19:46:34 +00:00
WIP
This commit is contained in:
parent
6af0b453e3
commit
7fb62a6afa
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
@ -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 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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} />}
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
@ -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>
|
||||||
|
@ -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)}
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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 }) => (
|
||||||
|
@ -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
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
))}
|
))}
|
||||||
|
@ -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"
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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";
|
||||||
|
Loading…
Reference in New Issue
Block a user