mirror of
https://github.com/luminous-devs/lume.git
synced 2024-09-29 16:30:55 +00:00
feat: improve
This commit is contained in:
parent
cfcb9bc6ed
commit
ca0e041731
@ -15,10 +15,12 @@
|
||||
"@lume/utils": "workspace:^",
|
||||
"@radix-ui/react-checkbox": "^1.0.4",
|
||||
"@radix-ui/react-collapsible": "^1.0.3",
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
||||
"@radix-ui/react-popover": "^1.0.7",
|
||||
"@tanstack/query-sync-storage-persister": "^5.24.1",
|
||||
"@tanstack/react-query": "^5.22.2",
|
||||
"@tanstack/react-query-persist-client": "^5.22.2",
|
||||
"@tanstack/react-router": "^1.16.6",
|
||||
"@tanstack/react-query": "^5.24.1",
|
||||
"@tanstack/react-query-persist-client": "^5.24.1",
|
||||
"@tanstack/react-router": "^1.17.4",
|
||||
"i18next": "^23.10.0",
|
||||
"i18next-resources-to-backend": "^1.2.0",
|
||||
"nostr-tools": "^2.3.1",
|
||||
@ -27,16 +29,16 @@
|
||||
"react-i18next": "^14.0.5",
|
||||
"slate": "^0.101.5",
|
||||
"slate-react": "^0.101.6",
|
||||
"sonner": "^1.4.1",
|
||||
"sonner": "^1.4.2",
|
||||
"virtua": "^0.27.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lume/tailwindcss": "workspace:^",
|
||||
"@lume/tsconfig": "workspace:^",
|
||||
"@lume/types": "workspace:^",
|
||||
"@tanstack/router-devtools": "^1.16.6",
|
||||
"@tanstack/router-devtools": "^1.17.4",
|
||||
"@tanstack/router-vite-plugin": "^1.16.5",
|
||||
"@types/react": "^18.2.58",
|
||||
"@types/react": "^18.2.60",
|
||||
"@types/react-dom": "^18.2.19",
|
||||
"@vitejs/plugin-react-swc": "^3.6.0",
|
||||
"autoprefixer": "^10.4.17",
|
||||
|
@ -1,9 +1,12 @@
|
||||
import { useArk } from "@lume/ark";
|
||||
import { PlusIcon } from "@lume/icons";
|
||||
import { Account } from "@lume/types";
|
||||
import { User } from "@lume/ui";
|
||||
import { Link, useNavigate, useParams } from "@tanstack/react-router";
|
||||
import { useNavigate, useParams, useSearch } from "@tanstack/react-router";
|
||||
import { useEffect, useState } from "react";
|
||||
import * as Popover from "@radix-ui/react-popover";
|
||||
import { Link } from "@tanstack/react-router";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
|
||||
|
||||
export function Accounts() {
|
||||
const ark = useArk();
|
||||
@ -22,12 +25,6 @@ export function Accounts() {
|
||||
|
||||
return (
|
||||
<div data-tauri-drag-region className="flex items-center gap-4">
|
||||
<Link
|
||||
to="/landing"
|
||||
className="inline-flex size-7 items-center justify-center rounded-full bg-neutral-300 ring-offset-2 ring-offset-neutral-200 hover:ring-1 hover:ring-blue-500 dark:bg-neutral-700 dark:ring-offset-neutral-950"
|
||||
>
|
||||
<PlusIcon className="size-4" />
|
||||
</Link>
|
||||
{accounts
|
||||
? accounts.map((account) =>
|
||||
// @ts-ignore, useless
|
||||
@ -48,15 +45,14 @@ function Inactive({ pubkey }: { pubkey: string }) {
|
||||
|
||||
const changeAccount = async (npub: string) => {
|
||||
const select = await ark.load_selected_account(npub);
|
||||
if (select)
|
||||
navigate({ to: "/$account/home/local", params: { account: npub } });
|
||||
if (select) navigate({ to: "/$account/home", params: { account: npub } });
|
||||
};
|
||||
|
||||
return (
|
||||
<button type="button" onClick={() => changeAccount(pubkey)}>
|
||||
<User.Provider pubkey={pubkey}>
|
||||
<User.Root className="rounded-full ring-offset-2 ring-offset-neutral-200 hover:ring-1 hover:ring-blue-500 dark:ring-offset-neutral-950">
|
||||
<User.Avatar className="aspect-square h-auto w-7 rounded-full object-cover" />
|
||||
<User.Root className="rounded-full">
|
||||
<User.Avatar className="aspect-square h-auto w-8 rounded-full object-cover" />
|
||||
</User.Root>
|
||||
</User.Provider>
|
||||
</button>
|
||||
@ -64,11 +60,99 @@ function Inactive({ pubkey }: { pubkey: string }) {
|
||||
}
|
||||
|
||||
function Active({ pubkey }: { pubkey: string }) {
|
||||
const [open, setOpen] = useState(true);
|
||||
// @ts-ignore, magic !!!
|
||||
const { guest } = useSearch({ strict: false });
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (guest) {
|
||||
return (
|
||||
<Popover.Root open={open} onOpenChange={setOpen}>
|
||||
<Popover.Trigger asChild>
|
||||
<button type="button">
|
||||
<User.Provider pubkey={pubkey}>
|
||||
<User.Root className="rounded-full ring-1 ring-teal-500 ring-offset-2 ring-offset-neutral-200 dark:ring-offset-neutral-950">
|
||||
<User.Avatar className="aspect-square h-auto w-7 rounded-full object-cover" />
|
||||
</User.Root>
|
||||
</User.Provider>
|
||||
</button>
|
||||
</Popover.Trigger>
|
||||
<Popover.Portal>
|
||||
<Popover.Content
|
||||
className="flex w-[280px] flex-col gap-4 rounded-xl bg-black p-5 text-neutral-100 focus:outline-none dark:bg-white dark:text-neutral-900 dark:shadow-none"
|
||||
sideOffset={10}
|
||||
side="bottom"
|
||||
>
|
||||
<div>
|
||||
<h1 className="mb-1 font-semibold">You're using guest account</h1>
|
||||
<p className="text-sm text-neutral-500 dark:text-neutral-600">
|
||||
You can continue by claim and backup this account, or you can
|
||||
import your own account key.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Link
|
||||
to="/backup"
|
||||
className="inline-flex h-9 w-full items-center justify-center rounded-lg bg-white text-sm font-medium leading-tight text-neutral-900 hover:bg-neutral-100"
|
||||
>
|
||||
Claim & Backup
|
||||
</Link>
|
||||
<Link
|
||||
to="/login"
|
||||
className="inline-flex h-9 w-full items-center justify-center rounded-lg bg-neutral-900 text-sm font-medium leading-tight text-neutral-100 hover:bg-neutral-800"
|
||||
>
|
||||
{t("welcome.login")}
|
||||
</Link>
|
||||
</div>
|
||||
<Popover.Arrow className="fill-black dark:fill-white" />
|
||||
</Popover.Content>
|
||||
</Popover.Portal>
|
||||
</Popover.Root>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<User.Provider pubkey={pubkey}>
|
||||
<User.Root className="rounded-full ring-1 ring-teal-500 ring-offset-2 ring-offset-neutral-200 dark:ring-offset-neutral-950">
|
||||
<User.Avatar className="aspect-square h-auto w-7 rounded-full object-cover" />
|
||||
</User.Root>
|
||||
</User.Provider>
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
<User.Provider pubkey={pubkey}>
|
||||
<User.Root className="rounded-full ring-1 ring-teal-500 ring-offset-2 ring-offset-neutral-200 dark:ring-offset-neutral-950">
|
||||
<User.Avatar className="aspect-square h-auto w-7 rounded-full object-cover" />
|
||||
</User.Root>
|
||||
</User.Provider>
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Portal>
|
||||
<DropdownMenu.Content
|
||||
className="flex w-[220px] flex-col rounded-xl bg-black p-2 text-neutral-100 focus:outline-none dark:bg-white dark:text-neutral-900 dark:shadow-none"
|
||||
sideOffset={10}
|
||||
side="bottom"
|
||||
>
|
||||
<DropdownMenu.Item className="group relative flex h-9 select-none items-center rounded-md px-3 text-sm font-medium leading-none outline-none hover:bg-neutral-900 dark:hover:bg-neutral-100">
|
||||
Add account
|
||||
<div className="ml-auto pl-5 text-xs text-neutral-800 dark:text-neutral-200">
|
||||
⌘+Shift+N
|
||||
</div>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item className="group relative flex h-9 select-none items-center rounded-md px-3 text-sm font-medium leading-none outline-none hover:bg-neutral-900 dark:hover:bg-neutral-100">
|
||||
Profile
|
||||
<div className="ml-auto pl-5 text-xs text-neutral-800 dark:text-neutral-200">
|
||||
⌘+Shift+P
|
||||
</div>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item className="group relative flex h-9 select-none items-center rounded-md px-3 text-sm font-medium leading-none outline-none hover:bg-neutral-900 dark:hover:bg-neutral-100">
|
||||
Settings
|
||||
<div className="ml-auto pl-5 text-xs text-neutral-800 dark:text-neutral-200">
|
||||
⌘+Shift+S
|
||||
</div>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item className="group relative flex h-9 select-none items-center rounded-md px-3 text-sm font-medium leading-none outline-none hover:bg-neutral-900 dark:hover:bg-neutral-100">
|
||||
Logout
|
||||
<div className="ml-auto pl-5 text-xs text-neutral-800 dark:text-neutral-200">
|
||||
⌘+Shift+L
|
||||
</div>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Arrow className="fill-black dark:fill-white" />
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Portal>
|
||||
</DropdownMenu.Root>
|
||||
);
|
||||
}
|
||||
|
56
apps/desktop2/src/components/suggest.tsx
Normal file
56
apps/desktop2/src/components/suggest.tsx
Normal file
@ -0,0 +1,56 @@
|
||||
import { LoaderIcon } from "@lume/icons";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { User } from "@lume/ui";
|
||||
|
||||
export function Suggest() {
|
||||
const { t } = useTranslation();
|
||||
const { isLoading, isError, data } = useQuery({
|
||||
queryKey: ["trending-users"],
|
||||
queryFn: async ({ signal }: { signal: AbortSignal }) => {
|
||||
const res = await fetch("https://api.nostr.band/v0/trending/profiles", {
|
||||
signal,
|
||||
});
|
||||
if (!res.ok) {
|
||||
throw new Error("Failed to fetch trending users from nostr.band API.");
|
||||
}
|
||||
return res.json();
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="flex flex-col divide-y divide-neutral-100 dark:divide-neutral-900">
|
||||
<div className="h-10 shrink-0 text-lg font-semibold">
|
||||
Suggested Follows
|
||||
</div>
|
||||
{isLoading ? (
|
||||
<div className="flex h-44 w-full items-center justify-center">
|
||||
<LoaderIcon className="size-4 animate-spin" />
|
||||
</div>
|
||||
) : isError ? (
|
||||
<div className="flex h-44 w-full items-center justify-center">
|
||||
{t("suggestion.error")}
|
||||
</div>
|
||||
) : (
|
||||
data?.profiles.map((item: { pubkey: string }) => (
|
||||
<div key={item.pubkey} className="h-max w-full overflow-hidden py-5">
|
||||
<User.Provider pubkey={item.pubkey}>
|
||||
<User.Root>
|
||||
<div className="flex h-full w-full flex-col gap-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2.5">
|
||||
<User.Avatar className="size-10 shrink-0 rounded-full" />
|
||||
<User.Name className="leadning-tight max-w-[15rem] truncate font-semibold" />
|
||||
</div>
|
||||
<User.Button className="inline-flex h-8 w-20 items-center justify-center rounded-lg bg-neutral-100 text-sm font-medium hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800" />
|
||||
</div>
|
||||
<User.About className="mt-1 line-clamp-3 max-w-none select-text text-neutral-800 dark:text-neutral-400" />
|
||||
</div>
|
||||
</User.Root>
|
||||
</User.Provider>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,17 +1,19 @@
|
||||
import {
|
||||
BellFilledIcon,
|
||||
BellIcon,
|
||||
EditIcon,
|
||||
ComposeFilledIcon,
|
||||
HomeFilledIcon,
|
||||
HomeIcon,
|
||||
HorizontalDotsIcon,
|
||||
SpaceFilledIcon,
|
||||
SpaceIcon,
|
||||
} from "@lume/icons";
|
||||
import { Link, useParams } from "@tanstack/react-router";
|
||||
import { Link } from "@tanstack/react-router";
|
||||
import { Outlet, createFileRoute } from "@tanstack/react-router";
|
||||
import { cn } from "@lume/utils";
|
||||
import { Accounts } from "@/components/accounts";
|
||||
import { useArk } from "@lume/ark";
|
||||
import { Box } from "@lume/ui";
|
||||
|
||||
export const Route = createFileRoute("/$account")({
|
||||
component: App,
|
||||
@ -36,37 +38,37 @@ function App() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => ark.open_editor()}
|
||||
className="inline-flex h-7 w-max items-center justify-center gap-1 rounded-full bg-blue-500 px-2.5 text-sm font-medium text-white hover:bg-blue-600"
|
||||
className="inline-flex h-8 w-max items-center justify-center gap-1 rounded-full bg-blue-500 px-3 text-sm font-medium text-white hover:bg-blue-600"
|
||||
>
|
||||
<EditIcon className="size-4" />
|
||||
New
|
||||
<ComposeFilledIcon className="size-4" />
|
||||
New post
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex h-full min-h-0 w-full">
|
||||
<div className="h-full w-full flex-1 px-2 pb-2">
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
<Box>
|
||||
<Outlet />
|
||||
</Box>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Navigation() {
|
||||
// @ts-ignore, useless
|
||||
const { account } = useParams({ strict: false });
|
||||
const { account } = Route.useParams();
|
||||
|
||||
return (
|
||||
<div
|
||||
data-tauri-drag-region
|
||||
className="flex h-full flex-1 items-center gap-2"
|
||||
>
|
||||
<Link to="/$account/home/local" params={{ account }}>
|
||||
<Link to="/$account/home" params={{ account }}>
|
||||
{({ isActive }) => (
|
||||
<div
|
||||
className={cn(
|
||||
"inline-flex h-9 w-max items-center justify-center gap-2 rounded-lg px-3 hover:bg-black/10 dark:hover:bg-white/10",
|
||||
isActive ? "bg-white shadow dark:bg-neutral-950" : "",
|
||||
"inline-flex h-8 w-max items-center justify-center gap-2 rounded-full px-3",
|
||||
isActive
|
||||
? "bg-neutral-300 hover:bg-neutral-400 dark:bg-neutral-800 dark:hover:bg-neutral-700"
|
||||
: "hover:bg-black/10 dark:hover:bg-white/10",
|
||||
)}
|
||||
>
|
||||
{isActive ? (
|
||||
@ -82,8 +84,10 @@ function Navigation() {
|
||||
{({ isActive }) => (
|
||||
<div
|
||||
className={cn(
|
||||
"inline-flex h-9 w-max items-center justify-center gap-2 rounded-lg px-3 hover:bg-black/10 dark:hover:bg-white/10",
|
||||
isActive ? "bg-white shadow dark:bg-neutral-950" : "",
|
||||
"inline-flex h-8 w-max items-center justify-center gap-2 rounded-full px-3 hover:bg-black/10 dark:hover:bg-white/10",
|
||||
isActive
|
||||
? "bg-neutral-300 hover:bg-neutral-400 dark:bg-neutral-800 dark:hover:bg-neutral-700"
|
||||
: "hover:bg-black/10 dark:hover:bg-white/10",
|
||||
)}
|
||||
>
|
||||
{isActive ? (
|
||||
@ -99,8 +103,10 @@ function Navigation() {
|
||||
{({ isActive }) => (
|
||||
<div
|
||||
className={cn(
|
||||
"inline-flex h-9 w-max items-center justify-center gap-2 rounded-lg px-3 hover:bg-black/10 dark:hover:bg-white/10",
|
||||
isActive ? "bg-white shadow dark:bg-neutral-950" : "",
|
||||
"inline-flex h-8 w-max items-center justify-center gap-2 rounded-full px-3 hover:bg-black/10 dark:hover:bg-white/10",
|
||||
isActive
|
||||
? "bg-neutral-300 hover:bg-neutral-400 dark:bg-neutral-800 dark:hover:bg-neutral-700"
|
||||
: "hover:bg-black/10 dark:hover:bg-white/10",
|
||||
)}
|
||||
>
|
||||
{isActive ? (
|
||||
|
133
apps/desktop2/src/routes/$account/home.lazy.tsx
Normal file
133
apps/desktop2/src/routes/$account/home.lazy.tsx
Normal file
@ -0,0 +1,133 @@
|
||||
import { RepostNote } from "@/components/repost";
|
||||
import { Suggest } from "@/components/suggest";
|
||||
import { TextNote } from "@/components/text";
|
||||
import { useArk } from "@lume/ark";
|
||||
import {
|
||||
LoaderIcon,
|
||||
ArrowRightCircleIcon,
|
||||
RefreshIcon,
|
||||
InfoIcon,
|
||||
} from "@lume/icons";
|
||||
import { Event, Kind } from "@lume/types";
|
||||
import { EmptyFeed } from "@lume/ui";
|
||||
import { FETCH_LIMIT } from "@lume/utils";
|
||||
import { useInfiniteQuery } from "@tanstack/react-query";
|
||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||
import { Virtualizer } from "virtua";
|
||||
|
||||
export const Route = createLazyFileRoute("/$account/home")({
|
||||
component: Home,
|
||||
});
|
||||
|
||||
function Home() {
|
||||
const ark = useArk();
|
||||
const currentDate = new Date().toLocaleString("default", {
|
||||
weekday: "long",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
});
|
||||
|
||||
const { account } = Route.useParams();
|
||||
const {
|
||||
data,
|
||||
hasNextPage,
|
||||
isLoading,
|
||||
isRefetching,
|
||||
isFetchingNextPage,
|
||||
fetchNextPage,
|
||||
refetch,
|
||||
} = useInfiniteQuery({
|
||||
queryKey: ["local_newsfeed", account],
|
||||
initialPageParam: 0,
|
||||
queryFn: async ({ pageParam }: { pageParam: number }) => {
|
||||
const events = await ark.get_events(
|
||||
"local",
|
||||
FETCH_LIMIT,
|
||||
pageParam,
|
||||
true,
|
||||
);
|
||||
return events;
|
||||
},
|
||||
getNextPageParam: (lastPage) => {
|
||||
const lastEvent = lastPage?.at(-1);
|
||||
if (!lastEvent) return;
|
||||
return lastEvent.created_at - 1;
|
||||
},
|
||||
select: (data) => data?.pages.flatMap((page) => page),
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
|
||||
const renderItem = (event: Event) => {
|
||||
if (!event) return;
|
||||
switch (event.kind) {
|
||||
case Kind.Repost:
|
||||
return <RepostNote key={event.id} event={event} />;
|
||||
default:
|
||||
return <TextNote key={event.id} event={event} />;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="mx-auto flex h-12 w-full max-w-xl shrink-0 items-center justify-between border-b border-neutral-100 dark:border-neutral-900">
|
||||
<h3 className="text-sm font-medium uppercase leading-tight text-neutral-600 dark:text-neutral-400">
|
||||
{currentDate}
|
||||
</h3>
|
||||
<div className="flex items-center gap-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => refetch()}
|
||||
className="text-neutral-700 hover:text-blue-500 dark:text-neutral-300"
|
||||
>
|
||||
<RefreshIcon className="size-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mx-auto flex w-full max-w-xl flex-1 flex-col">
|
||||
<div className="flex-1">
|
||||
{isLoading || isRefetching ? (
|
||||
<div className="flex h-20 w-full flex-col items-center justify-center gap-1">
|
||||
<LoaderIcon className="size-5 animate-spin" />
|
||||
</div>
|
||||
) : !data.length ? (
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex items-center gap-2 rounded-xl bg-neutral-100 p-3 dark:bg-neutral-900">
|
||||
<InfoIcon className="size-5" />
|
||||
<p>
|
||||
Empty newsfeed. Or you can go to{" "}
|
||||
<a href="" className="text-blue-500 hover:text-blue-600">
|
||||
Discover
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
<Suggest />
|
||||
</div>
|
||||
) : (
|
||||
<Virtualizer overscan={3}>
|
||||
{data.map((item) => renderItem(item))}
|
||||
</Virtualizer>
|
||||
)}
|
||||
<div className="flex h-20 items-center justify-center">
|
||||
{hasNextPage ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => fetchNextPage()}
|
||||
disabled={!hasNextPage || isFetchingNextPage}
|
||||
className="inline-flex h-12 w-36 items-center justify-center gap-2 rounded-full bg-neutral-100 px-3 font-medium hover:bg-neutral-200 focus:outline-none dark:bg-neutral-900 dark:hover:bg-neutral-800"
|
||||
>
|
||||
{isFetchingNextPage ? (
|
||||
<LoaderIcon className="size-5 animate-spin" />
|
||||
) : (
|
||||
<>
|
||||
<ArrowRightCircleIcon className="size-5" />
|
||||
Load more
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,66 +0,0 @@
|
||||
import { cn } from "@lume/utils";
|
||||
import {
|
||||
Outlet,
|
||||
Link,
|
||||
createFileRoute,
|
||||
useParams,
|
||||
} from "@tanstack/react-router";
|
||||
|
||||
export const Route = createFileRoute("/$account/home")({
|
||||
component: Home,
|
||||
});
|
||||
|
||||
function Home() {
|
||||
// @ts-ignore, useless
|
||||
const { account } = useParams({ strict: false });
|
||||
|
||||
return (
|
||||
<div className="h-full w-full overflow-hidden overflow-y-auto rounded-xl bg-white shadow-[rgba(50,_50,_105,_0.15)_0px_2px_5px_0px,_rgba(0,_0,_0,_0.05)_0px_1px_1px_0px] dark:bg-black dark:shadow-none dark:ring-1 dark:ring-white/5">
|
||||
<div className="mx-auto flex w-full max-w-xl flex-col">
|
||||
<div className="mx-auto flex h-28 w-1/2 items-center">
|
||||
<div className="flex h-11 w-full flex-1 items-center rounded-full bg-neutral-100 dark:bg-neutral-900">
|
||||
<Link
|
||||
to="/$account/home/local"
|
||||
params={{ account }}
|
||||
className="h-11 flex-1 p-1"
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<div
|
||||
className={cn(
|
||||
"inline-flex h-full w-full items-center justify-center rounded-full text-sm font-medium",
|
||||
isActive
|
||||
? "bg-white shadow shadow-neutral-500/20 dark:bg-black dark:shadow-none dark:ring-1 dark:ring-neutral-800"
|
||||
: "",
|
||||
)}
|
||||
>
|
||||
Local
|
||||
</div>
|
||||
)}
|
||||
</Link>
|
||||
<Link
|
||||
to="/$account/home/global"
|
||||
params={{ account }}
|
||||
className="h-11 flex-1 p-1"
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<div
|
||||
className={cn(
|
||||
"inline-flex h-full w-full items-center justify-center rounded-full text-sm font-medium",
|
||||
isActive
|
||||
? "bg-white shadow shadow-neutral-500/20 dark:bg-black dark:shadow-none dark:ring-1 dark:ring-neutral-800"
|
||||
: "",
|
||||
)}
|
||||
>
|
||||
Global
|
||||
</div>
|
||||
)}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,92 +0,0 @@
|
||||
import { useArk } from "@lume/ark";
|
||||
import { ArrowRightCircleIcon, LoaderIcon, SearchIcon } from "@lume/icons";
|
||||
import { Event, Kind } from "@lume/types";
|
||||
import { EmptyFeed } from "@lume/ui";
|
||||
import { FETCH_LIMIT } from "@lume/utils";
|
||||
import { useInfiniteQuery } from "@tanstack/react-query";
|
||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||
import { Virtualizer } from "virtua";
|
||||
import { TextNote } from "./-components/text";
|
||||
import { RepostNote } from "./-components/repost";
|
||||
|
||||
export const Route = createLazyFileRoute("/$account/home/global")({
|
||||
component: GlobalTimeline,
|
||||
});
|
||||
|
||||
function GlobalTimeline() {
|
||||
const ark = useArk();
|
||||
const { data, hasNextPage, isLoading, isFetchingNextPage, fetchNextPage } =
|
||||
useInfiniteQuery({
|
||||
queryKey: ["events", "global"],
|
||||
initialPageParam: 0,
|
||||
queryFn: async ({ pageParam }: { pageParam: number }) => {
|
||||
const events = await ark.get_events(
|
||||
"global",
|
||||
FETCH_LIMIT,
|
||||
pageParam,
|
||||
true,
|
||||
);
|
||||
return events;
|
||||
},
|
||||
getNextPageParam: (lastPage) => {
|
||||
const lastEvent = lastPage.at(-1);
|
||||
if (!lastEvent) return;
|
||||
return lastEvent.created_at - 1;
|
||||
},
|
||||
select: (data) => data?.pages.flatMap((page) => page),
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
|
||||
const renderItem = (event: Event) => {
|
||||
switch (event.kind) {
|
||||
case Kind.Repost:
|
||||
return <RepostNote key={event.id} event={event} />;
|
||||
default:
|
||||
return <TextNote key={event.id} event={event} />;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading ? (
|
||||
<div className="flex h-20 w-full items-center justify-center">
|
||||
<LoaderIcon className="size-5 animate-spin" />
|
||||
</div>
|
||||
) : !data.length ? (
|
||||
<div className="flex flex-col gap-3">
|
||||
<EmptyFeed />
|
||||
<a
|
||||
href="/suggest"
|
||||
className="inline-flex h-9 w-full items-center justify-center gap-2 rounded-lg bg-blue-500 text-sm font-medium text-white hover:bg-blue-600"
|
||||
>
|
||||
<SearchIcon className="size-5" />
|
||||
Find accounts to follow
|
||||
</a>
|
||||
</div>
|
||||
) : (
|
||||
<Virtualizer overscan={3}>
|
||||
{data.map((item) => renderItem(item))}
|
||||
</Virtualizer>
|
||||
)}
|
||||
<div className="flex h-20 items-center justify-center">
|
||||
{hasNextPage ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => fetchNextPage()}
|
||||
disabled={!hasNextPage || isFetchingNextPage}
|
||||
className="inline-flex h-12 w-36 items-center justify-center gap-2 rounded-full bg-neutral-100 px-3 font-medium hover:bg-neutral-200 focus:outline-none dark:bg-neutral-900 dark:hover:bg-neutral-800"
|
||||
>
|
||||
{isFetchingNextPage ? (
|
||||
<LoaderIcon className="size-5 animate-spin" />
|
||||
) : (
|
||||
<>
|
||||
<ArrowRightCircleIcon className="size-5" />
|
||||
Load more
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,89 +0,0 @@
|
||||
import { useArk } from "@lume/ark";
|
||||
import { ArrowRightCircleIcon, ArrowRightIcon, LoaderIcon } from "@lume/icons";
|
||||
import { Event, Kind } from "@lume/types";
|
||||
import { EmptyFeed } from "@lume/ui";
|
||||
import { FETCH_LIMIT } from "@lume/utils";
|
||||
import { useInfiniteQuery } from "@tanstack/react-query";
|
||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||
import { Virtualizer } from "virtua";
|
||||
import { TextNote } from "@/components/text";
|
||||
import { RepostNote } from "@/components/repost";
|
||||
|
||||
export const Route = createLazyFileRoute("/$account/home/local")({
|
||||
component: LocalTimeline,
|
||||
});
|
||||
|
||||
function LocalTimeline() {
|
||||
const ark = useArk();
|
||||
|
||||
const { account } = Route.useParams();
|
||||
const { data, hasNextPage, isLoading, isFetchingNextPage, fetchNextPage } =
|
||||
useInfiniteQuery({
|
||||
queryKey: ["local_newsfeed", account],
|
||||
initialPageParam: 0,
|
||||
queryFn: async ({ pageParam }: { pageParam: number }) => {
|
||||
const events = await ark.get_events(
|
||||
"local",
|
||||
FETCH_LIMIT,
|
||||
pageParam,
|
||||
true,
|
||||
);
|
||||
return events;
|
||||
},
|
||||
getNextPageParam: (lastPage) => {
|
||||
const lastEvent = lastPage?.at(-1);
|
||||
if (!lastEvent) return;
|
||||
return lastEvent.created_at - 1;
|
||||
},
|
||||
select: (data) => data?.pages.flatMap((page) => page),
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
|
||||
const renderItem = (event: Event) => {
|
||||
if (!event) return;
|
||||
switch (event.kind) {
|
||||
case Kind.Repost:
|
||||
return <RepostNote key={event.id} event={event} />;
|
||||
default:
|
||||
return <TextNote key={event.id} event={event} />;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading ? (
|
||||
<div className="flex h-20 w-full flex-col items-center justify-center gap-1">
|
||||
<LoaderIcon className="size-5 animate-spin" />
|
||||
<p>Loading...</p>
|
||||
</div>
|
||||
) : !data.length ? (
|
||||
<div className="flex flex-col gap-3">
|
||||
<EmptyFeed />
|
||||
</div>
|
||||
) : (
|
||||
<Virtualizer overscan={3}>
|
||||
{data.map((item) => renderItem(item))}
|
||||
</Virtualizer>
|
||||
)}
|
||||
<div className="flex h-20 items-center justify-center">
|
||||
{hasNextPage ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => fetchNextPage()}
|
||||
disabled={!hasNextPage || isFetchingNextPage}
|
||||
className="inline-flex h-12 w-36 items-center justify-center gap-2 rounded-full bg-neutral-100 px-3 font-medium hover:bg-neutral-200 focus:outline-none dark:bg-neutral-900 dark:hover:bg-neutral-800"
|
||||
>
|
||||
{isFetchingNextPage ? (
|
||||
<LoaderIcon className="size-5 animate-spin" />
|
||||
) : (
|
||||
<>
|
||||
<ArrowRightCircleIcon className="size-5" />
|
||||
Load more
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -26,7 +26,7 @@ function Create() {
|
||||
try {
|
||||
await ark.save_account(keys);
|
||||
navigate({
|
||||
to: "/$account/home/local",
|
||||
to: "/$account/home",
|
||||
params: { account: keys.npub },
|
||||
search: { onboarding: true },
|
||||
replace: true,
|
||||
|
@ -32,7 +32,7 @@ function Import() {
|
||||
nsec: key,
|
||||
});
|
||||
navigate({
|
||||
to: "/$account/home/local",
|
||||
to: "/$account/home",
|
||||
params: { account: npub },
|
||||
search: { onboarding: true },
|
||||
replace: true,
|
||||
|
20
apps/desktop2/src/routes/backup.lazy.tsx
Normal file
20
apps/desktop2/src/routes/backup.lazy.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export const Route = createLazyFileRoute("/backup")({
|
||||
component: Screen,
|
||||
});
|
||||
|
||||
function Screen() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<div className="mx-auto flex w-full max-w-md flex-col gap-8">
|
||||
<div className="flex flex-col items-center gap-1 text-center">
|
||||
<h1 className="text-2xl font-semibold">{t("backup.title")}</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -31,7 +31,7 @@ function Event() {
|
||||
return (
|
||||
<WindowVirtualizer>
|
||||
<Container withDrag>
|
||||
<Box>
|
||||
<Box className="px-3 pt-3">
|
||||
<MainNote data={data} />
|
||||
{data ? <ReplyList eventId={eventId} /> : null}
|
||||
</Box>
|
||||
|
@ -11,13 +11,14 @@ export const Route = createFileRoute("/")({
|
||||
const accounts = await ark.get_all_accounts();
|
||||
|
||||
switch (accounts.length) {
|
||||
// Empty account
|
||||
// Guest account
|
||||
case 0:
|
||||
const guest = await ark.create_guest_account();
|
||||
throw redirect({
|
||||
to: "/landing",
|
||||
search: {
|
||||
redirect: location.href,
|
||||
},
|
||||
to: "/$account/home",
|
||||
params: { account: guest },
|
||||
search: { guest: true },
|
||||
replace: true,
|
||||
});
|
||||
// Only 1 account, skip account selection screen
|
||||
case 1:
|
||||
@ -25,11 +26,9 @@ export const Route = createFileRoute("/")({
|
||||
const loadAccount = await ark.load_selected_account(account);
|
||||
if (loadAccount) {
|
||||
throw redirect({
|
||||
to: "/$account/home/local",
|
||||
to: "/$account/home",
|
||||
params: { account },
|
||||
search: {
|
||||
redirect: location.href,
|
||||
},
|
||||
replace: true,
|
||||
});
|
||||
}
|
||||
// Account selection
|
||||
@ -51,24 +50,24 @@ function Screen() {
|
||||
const loadAccount = await ark.load_selected_account(npub);
|
||||
if (loadAccount) {
|
||||
navigate({
|
||||
to: "/$account/home/local",
|
||||
to: "/$account/home",
|
||||
params: { account: npub },
|
||||
replace: true,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const weekday = new Date().toLocaleString("default", { weekday: "long" });
|
||||
const day = new Date().getDate();
|
||||
const month = new Date()
|
||||
.toLocaleString("default", { month: "long" })
|
||||
.toString();
|
||||
const currentDate = new Date().toLocaleString("default", {
|
||||
weekday: "long",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="relative flex h-full w-full items-center justify-center">
|
||||
<div className="relative z-20 flex flex-col items-center gap-16">
|
||||
<div className="text-center text-white">
|
||||
<h2 className="mb-1 text-2xl">{`${weekday}, ${month} ${day}`}</h2>
|
||||
<h2 className="mb-1 text-2xl">{currentDate}</h2>
|
||||
<h2 className="text-2xl font-semibold">Welcome back!</h2>
|
||||
</div>
|
||||
<div className="flex items-center justify-center gap-6">
|
||||
|
14
apps/desktop2/src/routes/login.tsx
Normal file
14
apps/desktop2/src/routes/login.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
import { Outlet, createFileRoute } from "@tanstack/react-router";
|
||||
|
||||
export const Route = createFileRoute("/login")({
|
||||
component: Screen,
|
||||
});
|
||||
|
||||
function Screen() {
|
||||
return (
|
||||
<div>
|
||||
<h1>Login</h1>
|
||||
<Outlet />
|
||||
</div>
|
||||
);
|
||||
}
|
9
apps/desktop2/src/routes/settings/index.lazy.tsx
Normal file
9
apps/desktop2/src/routes/settings/index.lazy.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||
|
||||
export const Route = createLazyFileRoute("/settings/")({
|
||||
component: Screen,
|
||||
});
|
||||
|
||||
function Screen() {
|
||||
return <div>Settings</div>;
|
||||
}
|
@ -13,7 +13,7 @@
|
||||
"@astrojs/check": "^0.4.1",
|
||||
"@astrojs/tailwind": "^5.1.0",
|
||||
"@fontsource/geist-mono": "^5.0.1",
|
||||
"astro": "^4.4.4",
|
||||
"astro": "^4.4.6",
|
||||
"astro-seo-meta": "^4.1.0",
|
||||
"astro-seo-schema": "^4.0.0",
|
||||
"schema-dts": "^1.1.2",
|
||||
|
@ -14,9 +14,8 @@
|
||||
"@radix-ui/react-hover-card": "^1.0.7",
|
||||
"@radix-ui/react-popover": "^1.0.7",
|
||||
"@radix-ui/react-tooltip": "^1.0.7",
|
||||
"@tanstack/react-query": "^5.22.2",
|
||||
"@tanstack/react-query": "^5.24.1",
|
||||
"get-urls": "^12.1.0",
|
||||
"jotai": "^2.6.5",
|
||||
"media-chrome": "^2.2.5",
|
||||
"minidenticons": "^4.2.0",
|
||||
"nanoid": "^5.0.6",
|
||||
@ -26,7 +25,7 @@
|
||||
"react-currency-input-field": "^3.8.0",
|
||||
"react-i18next": "^14.0.5",
|
||||
"react-string-replace": "^1.1.1",
|
||||
"sonner": "^1.4.1",
|
||||
"sonner": "^1.4.2",
|
||||
"string-strip-html": "^13.4.6",
|
||||
"virtua": "^0.27.5"
|
||||
},
|
||||
@ -34,7 +33,7 @@
|
||||
"@lume/tailwindcss": "workspace:^",
|
||||
"@lume/tsconfig": "workspace:^",
|
||||
"@lume/types": "workspace:^",
|
||||
"@types/react": "^18.2.58",
|
||||
"@types/react": "^18.2.60",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
|
@ -50,6 +50,17 @@ export class Ark {
|
||||
}
|
||||
}
|
||||
|
||||
public async create_guest_account() {
|
||||
try {
|
||||
const keys = await this.create_keys();
|
||||
await this.save_account(keys);
|
||||
|
||||
return keys.npub;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
public async create_keys() {
|
||||
try {
|
||||
const cmd: Keys = await invoke("create_keys");
|
||||
|
@ -8,7 +8,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lume/tsconfig": "workspace:*",
|
||||
"@types/react": "^18.2.58",
|
||||
"@types/react": "^18.2.60",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
}
|
@ -1,24 +1,16 @@
|
||||
import { SVGProps } from "react";
|
||||
|
||||
export function BellIcon(
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
|
||||
) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M9.159 17.724a59.522 59.522 0 01-3.733-.297 1.587 1.587 0 01-1.33-2.08c.161-.485.324-.963.367-1.478l.355-4.26a7.207 7.207 0 0114.365 0l.355 4.262c.043.515.206.993.367 1.479a1.587 1.587 0 01-1.33 2.077 59.5 59.5 0 01-3.732.297m-5.684 0c1.893.09 3.79.09 5.684 0m-5.684 0v.434a2.842 2.842 0 105.684 0v-.434"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
return (
|
||||
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" {...props}>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M16 18c-.673 1.766-2.21 3-4 3s-3.327-1.234-4-3m-1.716 0h11.432a2 2 0 0 0 1.982-2.264l-.905-6.789a6.853 6.853 0 0 0-13.586 0l-.905 6.789A2 2 0 0 0 6.284 18Z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
@ -1,23 +1,16 @@
|
||||
import { SVGProps } from "react";
|
||||
|
||||
export function BellFilledIcon(
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
|
||||
) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
fillRule="evenodd"
|
||||
d="M3.822 9.526a8.207 8.207 0 0116.358 0l.355 4.262c.03.374.15.735.32 1.246a2.587 2.587 0 01-2.17 3.387c-.957.106-1.916.19-2.876.25a3.843 3.843 0 01-7.616 0c-.96-.06-1.92-.143-2.877-.25a2.588 2.588 0 01-2.17-3.39c.17-.51.29-.872.32-1.245l.356-4.26zm6.44 9.24a1.843 1.843 0 003.478 0l-.294.008a60.587 60.587 0 01-3.184-.008z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
return (
|
||||
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" {...props}>
|
||||
<path
|
||||
fill="currentColor"
|
||||
fillRule="evenodd"
|
||||
d="M12 2a7.853 7.853 0 0 0-7.784 6.815l-.905 6.789A3 3 0 0 0 6.284 19h1.07c.904 1.748 2.607 3 4.646 3 2.039 0 3.742-1.252 4.646-3h1.07a3 3 0 0 0 2.973-3.396l-.905-6.789A7.853 7.853 0 0 0 12 2Zm2.222 17H9.778c.61.637 1.399 1 2.222 1s1.613-.363 2.222-1Z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
@ -1,21 +1,16 @@
|
||||
import { SVGProps } from "react";
|
||||
|
||||
export function ComposeFilledIcon(
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
|
||||
) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M15.27 2.637a1.51 1.51 0 01.831-.589c.771-.206 1.39.273 1.71.585.373.366.723.879 1.042 1.452.644 1.16 1.273 2.78 1.756 4.585.484 1.806.75 3.522.771 4.85.012.655-.035 1.274-.176 1.779-.12.43-.417 1.154-1.188 1.36a1.51 1.51 0 01-1.015-.093 26.427 26.427 0 00-6.508-.37l1.755 5.5A1 1 0 0113.296 23H8.92a1 1 0 01-.944-.67L6.135 17.07l-.812.124a1 1 0 01-.727-.172 6.126 6.126 0 01-2.03-7.576 1 1 0 01.544-.512l4.67-1.824a26.429 26.429 0 007.49-4.472zm4.093 11.584c.015-.189.022-.411.018-.668-.02-1.12-.249-2.67-.703-4.365-.455-1.696-1.03-3.152-1.574-4.132a6.928 6.928 0 00-.35-.57c-.026.325-.026.73.006 1.204.07 1.06.295 2.395.68 3.83.384 1.434.857 2.702 1.326 3.656.21.427.412.777.597 1.045zM9.629 21h2.298l-1.46-4.575-2.319.343L9.63 21z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
return (
|
||||
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" {...props}>
|
||||
<path
|
||||
fill="currentColor"
|
||||
fillRule="evenodd"
|
||||
d="M19.367 2.814c.565.603.814 1.49.489 2.391-.614 1.705-1.793 3.098-2.765 4.04-.266.256-.52.484-.752.68.894.77 1.345 2.1.652 3.301-1.22 2.116-4.304 5.716-10.928 5.756A32 32 0 0 0 6 21a1 1 0 1 1-2 0c0-4.329.793-8.748 2.831-12.259 2.063-3.553 5.386-6.14 10.288-6.721a2.645 2.645 0 0 1 2.248.794Z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
@ -1,19 +1,12 @@
|
||||
export function HomeIcon(props: JSX.IntrinsicElements["svg"]) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
{...props}
|
||||
>
|
||||
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" {...props}>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M3 12.759c0-1.017 0-1.526.119-2.002a4 4 0 01.513-1.19c.265-.414.634-.763 1.374-1.461l2.6-2.456c1.546-1.46 2.32-2.19 3.201-2.466a4 4 0 012.386 0c.882.275 1.655 1.006 3.201 2.466l2.6 2.456c.74.698 1.11 1.047 1.374 1.46a4 4 0 01.513 1.191c.119.476.119.985.119 2.002V14.6c0 2.24 0 3.36-.436 4.216a4 4 0 01-1.748 1.748C17.96 21 16.84 21 14.6 21H9.4c-2.24 0-3.36 0-4.216-.436a4 4 0 01-1.748-1.748C3 17.96 3 16.84 3 14.6v-1.841z"
|
||||
d="m5 7.511 6.069-4.344c.335-.24.502-.36.685-.406a1 1 0 0 1 .492 0c.183.046.35.166.685.406L19 7.51m-14 0v9.29c0 1.12 0 1.68.218 2.108a2 2 0 0 0 .874.874C6.52 20 7.08 20 8.2 20h7.6c1.12 0 1.68 0 2.108-.218a2 2 0 0 0 .874-.874C19 18.48 19 17.92 19 16.8V7.511m-14 0-2.5 1.79M19 7.511l2.5 1.79"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
@ -1,16 +1,9 @@
|
||||
export function HomeFilledIcon(props: JSX.IntrinsicElements["svg"]) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
{...props}
|
||||
>
|
||||
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" {...props}>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M13.49 2.23a5 5 0 00-2.98 0c-.61.19-1.136.525-1.68.963-.529.425-1.133.996-1.88 1.702L4.232 7.46c-.657.62-1.111 1.049-1.443 1.567a5 5 0 00-.642 1.488C2 11.113 2 11.737 2 12.64v2.003c0 1.084 0 1.958.058 2.666.06.729.185 1.369.487 1.96a5 5 0 002.185 2.186c.592.302 1.233.428 1.961.487C7.4 22 8.273 22 9.357 22h5.286c1.084 0 1.958 0 2.666-.058.729-.06 1.369-.185 1.961-.487a5 5 0 002.185-2.185c.302-.592.428-1.232.487-1.961C22 16.6 22 15.727 22 14.643V12.64c0-.903 0-1.527-.148-2.125a5.002 5.002 0 00-.642-1.488c-.332-.518-.786-.947-1.443-1.567l-2.716-2.565c-.748-.706-1.352-1.277-1.88-1.702-.545-.438-1.071-.773-1.68-.964z"
|
||||
d="M12.492 1.791a2 2 0 0 0-.984 0c-.374.095-.695.327-.95.512l-.071.05-8.569 6.135a1 1 0 0 0 1.164 1.626L4 9.457v7.381c0 .528 0 .982.03 1.357.033.395.104.789.297 1.167a3 3 0 0 0 1.311 1.311c.378.193.772.264 1.167.296.375.031.83.031 1.357.031h7.677c.527 0 .982 0 1.356-.03.395-.033.789-.104 1.167-.297a3 3 0 0 0 1.311-1.311c.193-.378.264-.772.297-1.167.03-.375.03-.83.03-1.356V9.457l.918.657a1 1 0 1 0 1.164-1.626l-8.568-6.134-.071-.051c-.256-.185-.577-.417-.95-.512Z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
@ -1,18 +1,13 @@
|
||||
export function HorizontalDotsIcon(props: JSX.IntrinsicElements["svg"]) {
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
>
|
||||
<path d="M6 12a1 1 0 11-2 0 1 1 0 012 0zM13 12a1 1 0 11-2 0 1 1 0 012 0zM20 12a1 1 0 11-2 0 1 1 0 012 0z" />
|
||||
</svg>
|
||||
);
|
||||
return (
|
||||
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" {...props}>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M12 13a1 1 0 1 0 0-2 1 1 0 0 0 0 2Zm8 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2ZM4 13a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
@ -4,21 +4,14 @@ export function LinkIcon(
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
|
||||
) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
{...props}
|
||||
>
|
||||
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" {...props}>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M20.5 13.5c0 1.395 0 2.092-.138 2.667a5 5 0 01-3.695 3.695C16.092 20 15.394 20 14 20h-1.5c-2.8 0-4.2 0-5.27-.545a5 5 0 01-2.185-2.185C4.5 16.2 4.5 14.8 4.5 12v-.5c0-2.33 0-3.495.38-4.413A5 5 0 017.588 4.38C8.363 4.059 9.317 4.009 11 4m9.26 5.454c.262-1.633.31-3.285.142-4.914a.495.495 0 00-.142-.3m0 0a.496.496 0 00-.301-.143 18.815 18.815 0 00-4.913.142m5.214 0L10.5 14"
|
||||
></path>
|
||||
d="M9 6H7.2c-1.12 0-1.68 0-2.108.218a2 2 0 0 0-.874.874C4 7.52 4 8.08 4 9.2v7.6c0 1.12 0 1.68.218 2.108a2 2 0 0 0 .874.874C5.52 20 6.08 20 7.2 20h7.6c1.12 0 1.68 0 2.108-.218a2 2 0 0 0 .874-.874C18 18.48 18 17.92 18 16.8V15M14 4h6m0 0v6m0-6-9 9"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
@ -1,18 +1,12 @@
|
||||
export function PlusIcon(props: JSX.IntrinsicElements['svg']) {
|
||||
export function PlusIcon(props: JSX.IntrinsicElements["svg"]) {
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
>
|
||||
<path d="M12 19v-7m0 0V5m0 7H5m7 0h7" />
|
||||
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" {...props}>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeWidth="2"
|
||||
d="M12 4v8m0 0v8m0-8H4m8 0h8"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
@ -1,20 +1,13 @@
|
||||
export function ReplyIcon(props: JSX.IntrinsicElements["svg"]) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="25"
|
||||
height="24"
|
||||
fill="none"
|
||||
viewBox="0 0 25 24"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M21.5 12a9.002 9.002 0 01-4.682 7.897 9 9 0 01-5.59 1.013c-1.203-.17-1.805-.255-1.964-.267-.257-.02-.165-.016-.423-.014-.159 0-.34.014-.702.04l-2.153.153c-.857.062-1.286.092-1.607-.06a1.348 1.348 0 01-.641-.64c-.152-.32-.122-.75-.06-1.608l.153-2.153c.026-.362.04-.542.04-.702.002-.258.006-.166-.014-.423-.012-.159-.098-.76-.268-1.964A9 9 0 1121.5 12z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
return (
|
||||
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" {...props}>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M12 21a9 9 0 1 0-9-9c0 1.354.3 2.639.835 3.791.102.219.133.465.076.7l-.778 3.191a1 1 0 0 0 1.191 1.213l3.33-.752c.224-.05.458-.02.667.073A8.969 8.969 0 0 0 12 21Z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
@ -1,18 +1,13 @@
|
||||
export function RepostIcon(props: JSX.IntrinsicElements["svg"]) {
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
>
|
||||
<path d="M12 2a15.267 15.267 0 012.92 2.777c.054.066.08.145.08.225M12 8a15.266 15.266 0 002.92-2.777.356.356 0 00.08-.221M12 16a15.264 15.264 0 00-2.92 2.777.356.356 0 00-.08.221M12 22a15.264 15.264 0 01-2.92-2.777.355.355 0 01-.08-.225m6-13.996C14.7 5 14.368 5 14 5h-4c-1.861 0-2.792 0-3.545.245a5 5 0 00-3.21 3.21C3 9.208 3 10.139 3 12s0 2.792.245 3.545A5 5 0 005 18m4 .998c.3.002.632.002 1 .002h4c1.861 0 2.792 0 3.545-.245a5 5 0 003.21-3.21C21 14.792 21 13.861 21 12s0-2.792-.245-3.545A5 5 0 0019 6" />
|
||||
</svg>
|
||||
);
|
||||
return (
|
||||
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" {...props}>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="m17.25 21 2.47-2.47a.75.75 0 0 0 0-1.06L17.25 15M6.75 3 4.28 5.47a.75.75 0 0 0 0 1.06L6.75 9M5 6h13a2 2 0 0 1 2 2v3M4 13v3a2 2 0 0 0 2 2h13"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
@ -1,31 +1,22 @@
|
||||
import { SVGProps } from "react";
|
||||
|
||||
export function SettingsIcon(
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
|
||||
) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M11 12a1 1 0 112 0 1 1 0 01-2 0z"
|
||||
/>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M8.562 4.32c1.252-.723 1.879-1.085 2.544-1.226a4.3 4.3 0 011.788 0c.665.141 1.292.503 2.544 1.225l1.499.865c1.252.723 1.878 1.084 2.334 1.59.403.447.707.974.894 1.546.21.647.21 1.37.21 2.816v1.728c0 1.446 0 2.169-.21 2.816-.186.572-.49 1.1-.894 1.547-.456.505-1.082.866-2.334 1.59l-1.499.864c-1.252.722-1.879 1.084-2.544 1.225a4.299 4.299 0 01-1.788 0c-.665-.141-1.292-.503-2.544-1.225l-1.499-.865c-1.252-.723-1.879-1.084-2.334-1.59a4.296 4.296 0 01-.894-1.546c-.21-.647-.21-1.37-.21-2.816v-1.728c0-1.446 0-2.169.21-2.816.186-.572.49-1.1.894-1.546.455-.506 1.082-.867 2.334-1.59l1.499-.865z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
return (
|
||||
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" {...props}>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="m7.99 5.398-.685-.158A1.722 1.722 0 0 0 5.24 7.305l.158.684a1.946 1.946 0 0 1-.817 2.057l-.832.555a1.682 1.682 0 0 0 0 2.798l.832.555c.673.449.999 1.268.817 2.057l-.158.684a1.722 1.722 0 0 0 2.065 2.065l.684-.158a1.946 1.946 0 0 1 2.057.817l.555.832a1.682 1.682 0 0 0 2.798 0l.555-.832a1.946 1.946 0 0 1 2.057-.817l.684.158a1.722 1.722 0 0 0 2.065-2.065l-.158-.684a1.946 1.946 0 0 1 .817-2.057l.832-.555a1.682 1.682 0 0 0 0-2.798l-.832-.555a1.946 1.946 0 0 1-.817-2.057l.158-.684a1.722 1.722 0 0 0-2.065-2.065l-.684.158a1.946 1.946 0 0 1-2.057-.817l-.555-.832a1.682 1.682 0 0 0-2.798 0l-.555.832a1.946 1.946 0 0 1-2.057.817Z"
|
||||
/>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
@ -4,21 +4,11 @@ export function SpaceIcon(
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
|
||||
) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
{...props}
|
||||
>
|
||||
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" {...props}>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M8 7v10m4-10v4m4-4v7m-5 7h2c2.8 0 4.2 0 5.27-.545a5 5 0 002.185-2.185C21 17.2 21 15.8 21 13v-2c0-2.8 0-4.2-.545-5.27a5 5 0 00-2.185-2.185C17.2 3 15.8 3 13 3h-2c-2.8 0-4.2 0-5.27.545A5 5 0 003.545 5.73C3 6.8 3 8.2 3 11v2c0 2.8 0 4.2.545 5.27a5 5 0 002.185 2.185C6.8 21 8.2 21 11 21z"
|
||||
></path>
|
||||
fill="currentColor"
|
||||
d="m5.092 19.782.454-.891-.454.891Zm-.874-.874.891-.454-.891.454Zm15.564 0-.891-.454.891.454Zm-.874.874-.454-.891.454.891Zm.874-14.69-.891.454.891-.454Zm-.874-.874-.454.891.454-.891Zm-14.69.874-.891-.454.891.454Zm.874-.874-.454-.891.454.891ZM13 4V3h-2v1h2Zm-2 16v1h2v-1h-2Zm8-12.8v9.6h2V7.2h-2ZM16.8 19H7.2v2h9.6v-2ZM5 16.8V7.2H3v9.6h2ZM7.2 5h9.6V3H7.2v2Zm0 14c-.577 0-.949 0-1.232-.024-.272-.022-.373-.06-.422-.085l-.908 1.782c.378.193.772.264 1.167.296.384.032.851.031 1.395.031v-2ZM3 16.8c0 .543 0 1.011.03 1.395.033.395.104.789.297 1.167l1.782-.908c-.025-.05-.063-.15-.085-.422C5 17.75 5 17.377 5 16.8H3Zm2.546 2.091a1 1 0 0 1-.437-.437l-1.782.908a3 3 0 0 0 1.311 1.311l.908-1.782ZM19 16.8c0 .576 0 .949-.024 1.232-.022.272-.06.372-.085.422l1.782.908c.193-.378.264-.772.296-1.167.032-.384.031-.852.031-1.395h-2ZM16.8 21c.544 0 1.011 0 1.395-.03.395-.033.789-.104 1.167-.297l-.908-1.782c-.05.025-.15.063-.422.085C17.75 19 17.377 19 16.8 19v2Zm2.091-2.546a1 1 0 0 1-.437.437l.908 1.782a3 3 0 0 0 1.311-1.311l-1.782-.908ZM21 7.2c0-.544 0-1.011-.03-1.395-.033-.395-.104-.789-.297-1.167l-1.782.908c.025.05.063.15.085.422C19 6.25 19 6.623 19 7.2h2ZM16.8 5c.577 0 .949 0 1.232.024.272.022.372.06.422.085l.908-1.782c-.378-.193-.772-.264-1.167-.296C17.811 2.999 17.344 3 16.8 3v2Zm3.873-.362a3 3 0 0 0-1.311-1.311l-.908 1.782a1 1 0 0 1 .437.437l1.782-.908ZM5 7.2c0-.577 0-.949.024-1.232.022-.272.06-.373.085-.422l-1.782-.908c-.193.378-.264.772-.296 1.167C2.999 6.189 3 6.656 3 7.2h2ZM7.2 3c-.544 0-1.011 0-1.395.03-.395.033-.789.104-1.167.297l.908 1.782c.05-.025.15-.063.422-.085C6.25 5 6.623 5 7.2 5V3ZM5.109 5.546a1 1 0 0 1 .437-.437l-.908-1.782a3 3 0 0 0-1.311 1.311l1.782.908ZM11 4v16h2V4h-2Z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
@ -4,18 +4,11 @@ export function SpaceFilledIcon(
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
|
||||
) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
{...props}
|
||||
>
|
||||
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" {...props}>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M10.956 2h2.088c1.363 0 2.447 0 3.321.071.896.074 1.66.227 2.359.583a6 6 0 012.622 2.622c.356.7.51 1.463.583 2.359.071.874.071 1.958.071 3.321v2.088c0 1.363 0 2.447-.071 3.321-.074.896-.227 1.66-.583 2.359a6 6 0 01-2.622 2.622c-.7.356-1.463.51-2.359.583-.874.071-1.958.071-3.321.071h-2.088c-1.363 0-2.447 0-3.321-.071-.896-.074-1.66-.227-2.359-.583a6 6 0 01-2.622-2.622c-.356-.7-.51-1.463-.583-2.359C2 15.491 2 14.407 2 13.044v-2.088c0-1.363 0-2.447.071-3.321.074-.896.227-1.66.583-2.359a6 6 0 012.622-2.622c.7-.356 1.463-.51 2.359-.583C8.509 2 9.593 2 10.956 2zM9 7a1 1 0 00-2 0v10a1 1 0 102 0V7zm3-1a1 1 0 00-1 1v4a1 1 0 102 0V7a1 1 0 00-1-1zm5 1a1 1 0 10-2 0v7a1 1 0 102 0V7z"
|
||||
></path>
|
||||
d="M7.161 3H11v18H7.161c-.527 0-.981 0-1.356-.03-.395-.033-.789-.104-1.167-.297a3 3 0 0 1-1.311-1.311c-.193-.378-.264-.772-.296-1.167A17.9 17.9 0 0 1 3 16.839V7.16c0-.527 0-.981.03-1.356.033-.395.104-.789.297-1.167a3 3 0 0 1 1.311-1.311c.378-.193.772-.264 1.167-.296C6.18 3 6.635 3 7.161 3ZM13 21h3.839c.527 0 .982 0 1.356-.03.395-.033.789-.104 1.167-.297a3 3 0 0 0 1.311-1.311c.193-.378.264-.772.296-1.167.031-.375.031-.83.031-1.356V7.16c0-.527 0-.981-.03-1.356-.033-.395-.104-.789-.297-1.167a3 3 0 0 0-1.311-1.311c-.378-.193-.772-.264-1.167-.296A17.9 17.9 0 0 0 16.839 3H13v18Z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
@ -1,18 +1,12 @@
|
||||
export function ZapIcon(props: JSX.IntrinsicElements["svg"]) {
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
>
|
||||
<path d="M6.054 8.38l5.756-4.513c1.638-1.284 2.457-1.926 3.018-1.863.485.055.912.366 1.136.828.26.533-.003 1.58-.53 3.673l-.44 1.753c-.211.838-.316 1.257-.244 1.62.064.32.221.611.45.83.259.249.652.361 1.438.585l.51.146c1.512.432 2.268.648 2.57 1.087.264.382.348.872.23 1.328-.137.526-.772 1.014-2.041 1.99l-5.652 4.342c-1.628 1.25-2.442 1.876-3 1.81a1.458 1.458 0 01-1.129-.831c-.257-.532.003-1.565.522-3.63l.394-1.568c.211-.838.316-1.257.244-1.621a1.575 1.575 0 00-.45-.83c-.259-.248-.652-.36-1.438-.585l-.568-.162c-1.496-.427-2.244-.64-2.546-1.077a1.631 1.631 0 01-.234-1.322c.132-.524.756-1.013 2.004-1.99z" />
|
||||
</svg>
|
||||
);
|
||||
return (
|
||||
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" {...props}>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M19.566 9H13.5a.5.5 0 0 1-.5-.5V2.401a.5.5 0 0 0-.916-.277L4.018 14.223a.5.5 0 0 0 .416.777H10.5a.5.5 0 0 1 .5.5v6.099a.5.5 0 0 0 .916.277l8.066-12.099A.5.5 0 0 0 19.566 9Z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
@ -8,16 +8,16 @@
|
||||
"@lume/icons": "workspace:^",
|
||||
"@lume/ui": "workspace:^",
|
||||
"@lume/utils": "workspace:^",
|
||||
"@tanstack/react-query": "^5.22.2",
|
||||
"@tanstack/react-query": "^5.24.1",
|
||||
"react": "^18.2.0",
|
||||
"sonner": "^1.4.1",
|
||||
"sonner": "^1.4.2",
|
||||
"virtua": "^0.27.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lume/tailwindcss": "workspace:^",
|
||||
"@lume/tsconfig": "workspace:^",
|
||||
"@lume/types": "workspace:^",
|
||||
"@types/react": "^18.2.58",
|
||||
"@types/react": "^18.2.60",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
|
@ -8,16 +8,16 @@
|
||||
"@lume/icons": "workspace:^",
|
||||
"@lume/ui": "workspace:^",
|
||||
"@lume/utils": "workspace:^",
|
||||
"@tanstack/react-query": "^5.22.2",
|
||||
"@tanstack/react-query": "^5.24.1",
|
||||
"react": "^18.2.0",
|
||||
"sonner": "^1.4.1",
|
||||
"sonner": "^1.4.2",
|
||||
"virtua": "^0.27.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lume/tailwindcss": "workspace:^",
|
||||
"@lume/tsconfig": "workspace:^",
|
||||
"@lume/types": "workspace:^",
|
||||
"@types/react": "^18.2.58",
|
||||
"@types/react": "^18.2.60",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
|
@ -8,16 +8,16 @@
|
||||
"@lume/icons": "workspace:^",
|
||||
"@lume/ui": "workspace:^",
|
||||
"@lume/utils": "workspace:^",
|
||||
"@tanstack/react-query": "^5.22.2",
|
||||
"@tanstack/react-query": "^5.24.1",
|
||||
"react": "^18.2.0",
|
||||
"sonner": "^1.4.1",
|
||||
"sonner": "^1.4.2",
|
||||
"virtua": "^0.27.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lume/tailwindcss": "workspace:^",
|
||||
"@lume/tsconfig": "workspace:^",
|
||||
"@lume/types": "workspace:^",
|
||||
"@types/react": "^18.2.58",
|
||||
"@types/react": "^18.2.60",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
|
@ -8,16 +8,16 @@
|
||||
"@lume/icons": "workspace:^",
|
||||
"@lume/ui": "workspace:^",
|
||||
"@lume/utils": "workspace:^",
|
||||
"@tanstack/react-query": "^5.22.2",
|
||||
"@tanstack/react-query": "^5.24.1",
|
||||
"react": "^18.2.0",
|
||||
"sonner": "^1.4.1",
|
||||
"sonner": "^1.4.2",
|
||||
"virtua": "^0.27.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lume/tailwindcss": "workspace:^",
|
||||
"@lume/tsconfig": "workspace:^",
|
||||
"@lume/types": "workspace:^",
|
||||
"@types/react": "^18.2.58",
|
||||
"@types/react": "^18.2.60",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
|
@ -8,16 +8,16 @@
|
||||
"@lume/icons": "workspace:^",
|
||||
"@lume/ui": "workspace:^",
|
||||
"@lume/utils": "workspace:^",
|
||||
"@tanstack/react-query": "^5.22.2",
|
||||
"@tanstack/react-query": "^5.24.1",
|
||||
"react": "^18.2.0",
|
||||
"sonner": "^1.4.1",
|
||||
"sonner": "^1.4.2",
|
||||
"virtua": "^0.27.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lume/tailwindcss": "workspace:^",
|
||||
"@lume/tsconfig": "workspace:^",
|
||||
"@lume/types": "workspace:^",
|
||||
"@types/react": "^18.2.58",
|
||||
"@types/react": "^18.2.60",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
|
@ -8,16 +8,16 @@
|
||||
"@lume/icons": "workspace:^",
|
||||
"@lume/ui": "workspace:^",
|
||||
"@lume/utils": "workspace:^",
|
||||
"@tanstack/react-query": "^5.22.2",
|
||||
"@tanstack/react-query": "^5.24.1",
|
||||
"react": "^18.2.0",
|
||||
"sonner": "^1.4.1",
|
||||
"sonner": "^1.4.2",
|
||||
"virtua": "^0.27.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lume/tailwindcss": "workspace:^",
|
||||
"@lume/tsconfig": "workspace:^",
|
||||
"@lume/types": "workspace:^",
|
||||
"@types/react": "^18.2.58",
|
||||
"@types/react": "^18.2.60",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
|
@ -8,16 +8,16 @@
|
||||
"@lume/icons": "workspace:^",
|
||||
"@lume/ui": "workspace:^",
|
||||
"@lume/utils": "workspace:^",
|
||||
"@tanstack/react-query": "^5.22.2",
|
||||
"@tanstack/react-query": "^5.24.1",
|
||||
"react": "^18.2.0",
|
||||
"sonner": "^1.4.1",
|
||||
"sonner": "^1.4.2",
|
||||
"virtua": "^0.27.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lume/tailwindcss": "workspace:^",
|
||||
"@lume/tsconfig": "workspace:^",
|
||||
"@lume/types": "workspace:^",
|
||||
"@types/react": "^18.2.58",
|
||||
"@types/react": "^18.2.60",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
|
@ -8,16 +8,16 @@
|
||||
"@lume/icons": "workspace:^",
|
||||
"@lume/ui": "workspace:^",
|
||||
"@lume/utils": "workspace:^",
|
||||
"@tanstack/react-query": "^5.22.2",
|
||||
"@tanstack/react-query": "^5.24.1",
|
||||
"react": "^18.2.0",
|
||||
"sonner": "^1.4.1",
|
||||
"sonner": "^1.4.2",
|
||||
"virtua": "^0.27.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lume/tailwindcss": "workspace:^",
|
||||
"@lume/tsconfig": "workspace:^",
|
||||
"@lume/types": "workspace:^",
|
||||
"@types/react": "^18.2.58",
|
||||
"@types/react": "^18.2.60",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
|
@ -8,16 +8,16 @@
|
||||
"@lume/icons": "workspace:^",
|
||||
"@lume/ui": "workspace:^",
|
||||
"@lume/utils": "workspace:^",
|
||||
"@tanstack/react-query": "^5.22.2",
|
||||
"@tanstack/react-query": "^5.24.1",
|
||||
"react": "^18.2.0",
|
||||
"sonner": "^1.4.1",
|
||||
"sonner": "^1.4.2",
|
||||
"virtua": "^0.27.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lume/tailwindcss": "workspace:^",
|
||||
"@lume/tsconfig": "workspace:^",
|
||||
"@lume/types": "workspace:^",
|
||||
"@types/react": "^18.2.58",
|
||||
"@types/react": "^18.2.60",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
|
@ -8,16 +8,16 @@
|
||||
"@lume/icons": "workspace:^",
|
||||
"@lume/ui": "workspace:^",
|
||||
"@lume/utils": "workspace:^",
|
||||
"@tanstack/react-query": "^5.22.2",
|
||||
"@tanstack/react-query": "^5.24.1",
|
||||
"react": "^18.2.0",
|
||||
"sonner": "^1.4.1",
|
||||
"sonner": "^1.4.2",
|
||||
"virtua": "^0.27.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lume/tailwindcss": "workspace:^",
|
||||
"@lume/tsconfig": "workspace:^",
|
||||
"@lume/types": "workspace:^",
|
||||
"@types/react": "^18.2.58",
|
||||
"@types/react": "^18.2.60",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
|
@ -8,14 +8,14 @@
|
||||
"@lume/icons": "workspace:^",
|
||||
"@lume/ui": "workspace:^",
|
||||
"@lume/utils": "workspace:^",
|
||||
"@tanstack/react-query": "^5.22.2",
|
||||
"@tanstack/react-query": "^5.24.1",
|
||||
"react": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lume/tailwindcss": "workspace:^",
|
||||
"@lume/tsconfig": "workspace:^",
|
||||
"@lume/types": "workspace:^",
|
||||
"@types/react": "^18.2.58",
|
||||
"@types/react": "^18.2.60",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
|
@ -18,11 +18,10 @@
|
||||
"@radix-ui/react-hover-card": "^1.0.7",
|
||||
"@radix-ui/react-popover": "^1.0.7",
|
||||
"@radix-ui/react-tooltip": "^1.0.7",
|
||||
"@tanstack/react-query": "^5.22.2",
|
||||
"@tanstack/react-router": "^1.16.6",
|
||||
"@tanstack/react-query": "^5.24.1",
|
||||
"@tanstack/react-router": "^1.17.4",
|
||||
"framer-motion": "^11.0.6",
|
||||
"get-urls": "^12.1.0",
|
||||
"jotai": "^2.6.5",
|
||||
"media-chrome": "^2.2.5",
|
||||
"minidenticons": "^4.2.0",
|
||||
"nanoid": "^5.0.6",
|
||||
@ -34,11 +33,11 @@
|
||||
"react-hook-form": "^7.50.1",
|
||||
"react-hotkeys-hook": "^4.5.0",
|
||||
"react-i18next": "^14.0.5",
|
||||
"react-router-dom": "^6.22.1",
|
||||
"react-router-dom": "^6.22.2",
|
||||
"react-string-replace": "^1.1.1",
|
||||
"slate": "^0.101.5",
|
||||
"slate-react": "^0.101.6",
|
||||
"sonner": "^1.4.1",
|
||||
"sonner": "^1.4.2",
|
||||
"string-strip-html": "^13.4.6",
|
||||
"uqr": "^0.1.2",
|
||||
"use-debounce": "^10.0.0",
|
||||
@ -48,7 +47,7 @@
|
||||
"@lume/tailwindcss": "workspace:^",
|
||||
"@lume/tsconfig": "workspace:^",
|
||||
"@lume/types": "workspace:^",
|
||||
"@types/react": "^18.2.58",
|
||||
"@types/react": "^18.2.60",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
|
@ -1,81 +0,0 @@
|
||||
import { useArk, useProfile } from "@lume/ark";
|
||||
import { SettingsIcon, UserIcon } from "@lume/icons";
|
||||
import { cn, useNetworkStatus } from "@lume/utils";
|
||||
import * as Avatar from "@radix-ui/react-avatar";
|
||||
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
|
||||
import { minidenticon } from "minidenticons";
|
||||
import { useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { LogoutDialog } from "./logoutDialog";
|
||||
|
||||
export function ActiveAccount() {
|
||||
const ark = useArk();
|
||||
const isOnline = useNetworkStatus();
|
||||
const svgURI = useMemo(
|
||||
() =>
|
||||
`data:image/svg+xml;utf8,${encodeURIComponent(
|
||||
minidenticon(ark.account.npub, 90, 50),
|
||||
)}`,
|
||||
[],
|
||||
);
|
||||
|
||||
const { t } = useTranslation();
|
||||
const { profile } = useProfile(ark.account.npub);
|
||||
|
||||
return (
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger asChild>
|
||||
<Avatar.Root
|
||||
className={cn(
|
||||
"rounded-full ring-1 ring-offset-2 ring-offset-neutral-200 dark:ring-offset-neutral-950",
|
||||
isOnline ? "ring-teal-500" : "ring-red-500",
|
||||
)}
|
||||
>
|
||||
<Avatar.Image
|
||||
src={profile?.picture}
|
||||
alt={ark.account.npub}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
style={{ contentVisibility: "auto" }}
|
||||
className="aspect-square h-auto w-7 rounded-full object-cover"
|
||||
/>
|
||||
<Avatar.Fallback delayMs={150}>
|
||||
<img
|
||||
src={svgURI}
|
||||
alt={ark.account.npub}
|
||||
className="aspect-square h-auto w-7 rounded-full bg-black dark:bg-white"
|
||||
/>
|
||||
</Avatar.Fallback>
|
||||
</Avatar.Root>
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Portal>
|
||||
<DropdownMenu.Content
|
||||
side="left"
|
||||
sideOffset={10}
|
||||
className="relative top-2 flex w-[200px] flex-col overflow-hidden rounded-2xl bg-white/50 p-2 ring-1 ring-black/10 backdrop-blur-2xl focus:outline-none dark:bg-black/50 dark:ring-white/10"
|
||||
>
|
||||
<DropdownMenu.Item asChild>
|
||||
<a
|
||||
href="/settings/profile"
|
||||
className="inline-flex h-9 items-center gap-3 rounded-lg px-3 text-sm font-medium text-black/70 hover:bg-black/10 hover:text-black focus:outline-none dark:text-white/70 dark:hover:bg-white/10 dark:hover:text-white"
|
||||
>
|
||||
<UserIcon className="size-4" />
|
||||
{t("user.editProfile")}
|
||||
</a>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item asChild>
|
||||
<a
|
||||
href="/settings/"
|
||||
className="inline-flex h-9 items-center gap-3 rounded-lg px-3 text-sm font-medium text-black/70 hover:bg-black/10 hover:text-black focus:outline-none dark:text-white/70 dark:hover:bg-white/10 dark:hover:text-white"
|
||||
>
|
||||
<SettingsIcon className="size-4" />
|
||||
{t("user.settings")}
|
||||
</a>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator className="my-1 h-px bg-black/10 dark:bg-white/10" />
|
||||
<LogoutDialog />
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Portal>
|
||||
</DropdownMenu.Root>
|
||||
);
|
||||
}
|
@ -1,76 +0,0 @@
|
||||
import { useArk } from "@lume/ark";
|
||||
import { LogoutIcon } from "@lume/icons";
|
||||
import * as AlertDialog from "@radix-ui/react-alert-dialog";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { useNavigate } from "@tanstack/react-router";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { toast } from "sonner";
|
||||
|
||||
export function LogoutDialog() {
|
||||
const ark = useArk();
|
||||
const queryClient = useQueryClient();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const logout = async () => {
|
||||
try {
|
||||
// clear cache
|
||||
queryClient.clear();
|
||||
ark.account = null;
|
||||
|
||||
// redirect to welcome screen
|
||||
navigate({ to: "/auth/" });
|
||||
} catch (e) {
|
||||
toast.error(String(e));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<AlertDialog.Root>
|
||||
<AlertDialog.Trigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex h-9 items-center gap-3 rounded-lg px-3 text-sm font-medium text-black/70 hover:bg-black/10 hover:text-black focus:outline-none dark:text-white/70 dark:hover:bg-white/10 dark:hover:text-white"
|
||||
>
|
||||
<LogoutIcon className="size-4" />
|
||||
{t("user.logout")}
|
||||
</button>
|
||||
</AlertDialog.Trigger>
|
||||
<AlertDialog.Portal>
|
||||
<AlertDialog.Overlay className="fixed inset-0 z-50 bg-black/20 backdrop-blur-sm dark:bg-black/20" />
|
||||
<AlertDialog.Content className="fixed inset-0 z-50 flex min-h-full items-center justify-center">
|
||||
<div className="relative h-min w-full max-w-md rounded-xl bg-neutral-100 dark:bg-neutral-900">
|
||||
<div className="flex flex-col gap-1 border-b border-white/5 px-5 py-4">
|
||||
<AlertDialog.Title className="text-lg font-semibold text-neutral-900 dark:text-neutral-100">
|
||||
{t("user.logoutConfirmTitle")}
|
||||
</AlertDialog.Title>
|
||||
<AlertDialog.Description className="text-sm leading-tight text-neutral-600 dark:text-neutral-400">
|
||||
{t("user.logoutConfirmSubtitle")}
|
||||
</AlertDialog.Description>
|
||||
</div>
|
||||
<div className="flex justify-end gap-2 px-5 py-3">
|
||||
<AlertDialog.Cancel asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex h-9 items-center justify-center rounded-lg px-4 text-sm font-medium text-neutral-900 outline-none hover:bg-neutral-200 dark:text-neutral-100 dark:hover:bg-neutral-800"
|
||||
>
|
||||
{t("global.cancel")}
|
||||
</button>
|
||||
</AlertDialog.Cancel>
|
||||
<AlertDialog.Action asChild>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => logout()}
|
||||
className="inline-flex h-9 items-center justify-center rounded-lg bg-red-500 px-4 text-sm font-medium text-white outline-none hover:bg-red-600"
|
||||
>
|
||||
{t("user.logout")}
|
||||
</button>
|
||||
</AlertDialog.Action>
|
||||
</div>
|
||||
</div>
|
||||
</AlertDialog.Content>
|
||||
</AlertDialog.Portal>
|
||||
</AlertDialog.Root>
|
||||
);
|
||||
}
|
@ -9,9 +9,14 @@ export function Box({
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<div className={cn("flex h-full min-h-0 w-full", className)}>
|
||||
<div className="flex h-full min-h-0 w-full">
|
||||
<div className="h-full w-full flex-1 px-2 pb-2">
|
||||
<div className="h-full w-full overflow-hidden overflow-y-auto rounded-xl bg-white px-3 pt-3 shadow-[rgba(50,_50,_105,_0.15)_0px_2px_5px_0px,_rgba(0,_0,_0,_0.05)_0px_1px_1px_0px] dark:bg-black dark:shadow-none dark:ring-1 dark:ring-white/5">
|
||||
<div
|
||||
className={cn(
|
||||
"h-full w-full overflow-hidden overflow-y-auto rounded-xl bg-white shadow-[rgba(50,_50,_105,_0.15)_0px_2px_5px_0px,_rgba(0,_0,_0,_0.05)_0px_1px_1px_0px] dark:bg-black dark:shadow-none dark:ring-1 dark:ring-white/5",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -3,28 +3,32 @@ import { cn } from "@lume/utils";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export function EmptyFeed({
|
||||
text,
|
||||
subtext,
|
||||
className,
|
||||
}: { text?: string; subtext?: string; className?: string }) {
|
||||
const { t } = useTranslation();
|
||||
text,
|
||||
subtext,
|
||||
className,
|
||||
}: {
|
||||
text?: string;
|
||||
subtext?: string;
|
||||
className?: string;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"w-full py-5 flex items-center justify-center flex-col gap-2 rounded-xl bg-neutral-50 dark:bg-neutral-950",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<InfoIcon className="size-8 text-blue-500" />
|
||||
<div className="text-center">
|
||||
<p className="font-semibold text-lg">
|
||||
{text ? text : t("global.emptyFeedTitle")}
|
||||
</p>
|
||||
<p className="leading-tight text-sm">
|
||||
{subtext ? subtext : t("global.emptyFeedSubtitle")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"flex w-full flex-col items-center justify-center gap-2 rounded-xl bg-neutral-50 py-3 dark:bg-neutral-950",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<InfoIcon className="size-8 text-blue-500" />
|
||||
<div className="text-center">
|
||||
<p className="text-lg font-semibold">
|
||||
{text ? text : t("global.emptyFeedTitle")}
|
||||
</p>
|
||||
<p className="text-sm leading-tight">
|
||||
{subtext ? subtext : t("global.emptyFeedSubtitle")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ReplyIcon, ShareIcon } from "@lume/icons";
|
||||
import { LinkIcon, ReplyIcon } from "@lume/icons";
|
||||
import * as Tooltip from "@radix-ui/react-tooltip";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNoteContext } from "../provider";
|
||||
@ -41,8 +41,8 @@ export function NoteReply() {
|
||||
onClick={() => ark.open_thread(event.id)}
|
||||
className="inline-flex h-9 items-center gap-2 rounded-lg px-3 text-sm font-medium text-white hover:bg-neutral-900 focus:outline-none dark:text-black dark:hover:bg-neutral-100"
|
||||
>
|
||||
<ShareIcon className="size-4" />
|
||||
{t("note.buttons.view")}
|
||||
<LinkIcon className="size-4" />
|
||||
{t("note.buttons.open")}
|
||||
</button>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item asChild>
|
||||
|
@ -105,7 +105,7 @@ export function MentionNote({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="my-1 flex w-full cursor-default flex-col rounded-2xl border border-black/10 px-3 pt-1 dark:border-white/10">
|
||||
<div className="my-1 flex w-full cursor-default flex-col rounded-xl border border-black/10 px-3 pt-1 dark:border-white/10">
|
||||
<User.Provider pubkey={data.pubkey}>
|
||||
<User.Root className="flex h-10 items-center gap-2">
|
||||
<User.Avatar className="size-6 shrink-0 rounded-full object-cover" />
|
||||
|
@ -35,7 +35,10 @@ export function ImagePreview({ url }: { url: string }) {
|
||||
|
||||
return (
|
||||
// biome-ignore lint/a11y/useKeyWithClickEvents: <explanation>
|
||||
<div onClick={open} className="group relative my-1 rounded-2xl">
|
||||
<div
|
||||
onClick={open}
|
||||
className="group relative my-1 overflow-hidden rounded-xl"
|
||||
>
|
||||
<img
|
||||
src={url}
|
||||
alt={url}
|
||||
@ -43,7 +46,7 @@ export function ImagePreview({ url }: { url: string }) {
|
||||
decoding="async"
|
||||
style={{ contentVisibility: "auto" }}
|
||||
onError={fallback}
|
||||
className="h-auto w-full rounded-2xl object-cover"
|
||||
className="h-auto w-full object-cover"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
|
@ -9,4 +9,3 @@ export * from "./src/hooks/useNetworkStatus";
|
||||
export * from "./src/hooks/useOpenGraph";
|
||||
export * from "./src/cn";
|
||||
export * from "./src/image";
|
||||
export * from "./src/state";
|
||||
|
@ -8,10 +8,9 @@
|
||||
"access": "public"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tanstack/react-query": "^5.22.2",
|
||||
"@tanstack/react-query": "^5.24.1",
|
||||
"clsx": "^2.1.0",
|
||||
"dayjs": "^1.11.10",
|
||||
"jotai": "^2.6.5",
|
||||
"nostr-tools": "^2.3.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
@ -21,7 +20,7 @@
|
||||
"devDependencies": {
|
||||
"@lume/tsconfig": "workspace:^",
|
||||
"@lume/types": "workspace:^",
|
||||
"@types/react": "^18.2.58",
|
||||
"@types/react": "^18.2.60",
|
||||
"@types/react-dom": "^18.2.19",
|
||||
"tailwind-merge": "^2.2.1",
|
||||
"typescript": "^5.3.3"
|
||||
|
@ -1,27 +0,0 @@
|
||||
import { atom } from "jotai";
|
||||
import { atomWithStorage } from "jotai/utils";
|
||||
|
||||
// Editor
|
||||
export const editorAtom = atom(false);
|
||||
export const editorValueAtom = atom([
|
||||
{
|
||||
type: "paragraph",
|
||||
children: [{ text: "" }],
|
||||
},
|
||||
]);
|
||||
|
||||
// Onboarding
|
||||
export const onboardingAtom = atomWithStorage("onboarding", {
|
||||
open: true,
|
||||
newUser: false,
|
||||
});
|
||||
|
||||
// Activity
|
||||
export const activityAtom = atom(false);
|
||||
export const activityUnreadAtom = atom(0);
|
||||
|
||||
// Tutorial
|
||||
export const tutorialAtom = atomWithStorage("tutorial", true);
|
||||
|
||||
// Search
|
||||
export const searchAtom = atom(false);
|
1151
pnpm-lock.yaml
1151
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
441
src-tauri/Cargo.lock
generated
441
src-tauri/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
1
src-tauri/gen/schemas/acl-manifests.json
Normal file
1
src-tauri/gen/schemas/acl-manifests.json
Normal file
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
{"desktop-capability":{"identifier":"desktop-capability","description":"Capability for the desktop","remote":null,"local":true,"windows":["main","splash","editor","settings","event-*","user-*","column-*"],"permissions":["path:default","event:default","window:default","app:default","resources:default","menu:default","tray:default","notification:allow-is-permission-granted","notification:allow-request-permission","notification:default","os:allow-locale","os:allow-platform","updater:allow-check","updater:default","window:allow-start-dragging","store:allow-get","clipboard-manager:allow-write","clipboard-manager:allow-read","webview:allow-create-webview-window","webview:allow-create-webview","dialog:allow-open","fs:allow-read-file","shell:allow-open",{"identifier":"http:default","allow":[{"url":"http://**/"},{"url":"https://**/"}]},{"identifier":"fs:allow-read-text-file","allow":[{"path":"$RESOURCE/locales/*"}]}],"platforms":["linux","macOS","windows"]}}
|
||||
{"desktop-capability":{"identifier":"desktop-capability","description":"Capability for the desktop","local":true,"windows":["main","splash","editor","settings","event-*","user-*","column-*"],"permissions":["path:default","event:default","window:default","app:default","resources:default","menu:default","tray:default","notification:allow-is-permission-granted","notification:allow-request-permission","notification:default","os:allow-locale","os:allow-platform","updater:allow-check","updater:default","window:allow-start-dragging","store:allow-get","clipboard-manager:allow-write","clipboard-manager:allow-read","webview:allow-create-webview-window","webview:allow-create-webview","dialog:allow-open","fs:allow-read-file","shell:allow-open",{"identifier":"http:default","allow":[{"url":"http://**/"},{"url":"https://**/"}]},{"identifier":"fs:allow-read-text-file","allow":[{"path":"$RESOURCE/locales/*"}]}],"platforms":["linux","macOS","windows"]}}
|
@ -85,7 +85,7 @@
|
||||
}
|
||||
},
|
||||
"platforms": {
|
||||
"description": "Target platforms this capability applies. By default all platforms applies.",
|
||||
"description": "Target platforms this capability applies. By default all platforms are affected by this capability.",
|
||||
"default": [
|
||||
"linux",
|
||||
"macOS",
|
||||
|
@ -85,7 +85,7 @@
|
||||
}
|
||||
},
|
||||
"platforms": {
|
||||
"description": "Target platforms this capability applies. By default all platforms applies.",
|
||||
"description": "Target platforms this capability applies. By default all platforms are affected by this capability.",
|
||||
"default": [
|
||||
"linux",
|
||||
"macOS",
|
||||
|
@ -49,7 +49,9 @@
|
||||
"pinTooltip": "Pin Note",
|
||||
"repost": "Repost",
|
||||
"quote": "Quote",
|
||||
"viewProfile": "View profile"
|
||||
"viewProfile": "View profile",
|
||||
"reply": "Reply this note",
|
||||
"open": "Open in new window"
|
||||
},
|
||||
"zap": {
|
||||
"zap": "Zap",
|
||||
@ -130,8 +132,8 @@
|
||||
"providerMethod": "Managed by Provider",
|
||||
"providerMethodDescription": "A 3rd party provider will handle your sign in keys for you."
|
||||
},
|
||||
"signupWithSelfManage": {
|
||||
"title": "This is your new Account Key",
|
||||
"backup": {
|
||||
"title": "This is your new sign in key",
|
||||
"subtitle": "Keep your key in safe place. If you lose this key, you will lose access to your account.",
|
||||
"confirm1": "I understand the risk of lost private key.",
|
||||
"confirm2": "I will make sure keep it safe and not sharing with anyone.",
|
||||
|
Loading…
Reference in New Issue
Block a user