mirror of
https://github.com/lumehq/lume.git
synced 2025-03-18 05:41:53 +01:00
wip: desktop2
This commit is contained in:
parent
70126ef1b3
commit
1de8c7240d
@ -15,31 +15,31 @@
|
||||
"@lume/ui": "workspace:^",
|
||||
"@lume/utils": "workspace:^",
|
||||
"@radix-ui/react-checkbox": "^1.0.4",
|
||||
"@tanstack/react-query": "^5.18.1",
|
||||
"@tanstack/react-router": "^1.16.0",
|
||||
"@tanstack/react-query": "^5.20.5",
|
||||
"@tanstack/react-router": "^1.16.2",
|
||||
"i18next": "^23.8.2",
|
||||
"i18next-resources-to-backend": "^1.2.0",
|
||||
"jotai": "^2.6.4",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-i18next": "^14.0.2",
|
||||
"react-i18next": "^14.0.5",
|
||||
"sonner": "^1.4.0",
|
||||
"virtua": "^0.23.3"
|
||||
"virtua": "^0.27.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lume/tailwindcss": "workspace:^",
|
||||
"@lume/tsconfig": "workspace:^",
|
||||
"@lume/types": "workspace:^",
|
||||
"@tanstack/router-devtools": "^1.16.0",
|
||||
"@tanstack/router-vite-plugin": "^1.16.1",
|
||||
"@tanstack/router-devtools": "^1.16.2",
|
||||
"@tanstack/router-vite-plugin": "^1.16.3",
|
||||
"@types/react": "^18.2.55",
|
||||
"@types/react-dom": "^18.2.19",
|
||||
"@vitejs/plugin-react-swc": "^3.5.0",
|
||||
"@vitejs/plugin-react-swc": "^3.6.0",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"postcss": "^8.4.33",
|
||||
"postcss": "^8.4.35",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5.2.2",
|
||||
"vite": "^5.1.0",
|
||||
"typescript": "^5.3.3",
|
||||
"vite": "^5.1.2",
|
||||
"vite-plugin-top-level-await": "^1.4.1",
|
||||
"vite-tsconfig-paths": "^4.3.1"
|
||||
}
|
||||
|
@ -5,8 +5,7 @@ import { EmptyFeed, TextNote } from "@lume/ui";
|
||||
import { FETCH_LIMIT } from "@lume/utils";
|
||||
import { useInfiniteQuery } from "@tanstack/react-query";
|
||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||
import { useEffect, useMemo, useRef } from "react";
|
||||
import { CacheSnapshot, Virtualizer, VListHandle } from "virtua";
|
||||
import { Virtualizer } from "virtua";
|
||||
|
||||
export const Route = createLazyFileRoute("/app/home")({
|
||||
component: Home,
|
||||
@ -14,15 +13,6 @@ export const Route = createLazyFileRoute("/app/home")({
|
||||
|
||||
function Home() {
|
||||
const ark = useArk();
|
||||
const ref = useRef<VListHandle>();
|
||||
const cacheKey = "timeline-vlist";
|
||||
|
||||
const [offset, cache] = useMemo(() => {
|
||||
const serialized = sessionStorage.getItem(cacheKey);
|
||||
if (!serialized) return [];
|
||||
return JSON.parse(serialized) as [number, CacheSnapshot];
|
||||
}, []);
|
||||
|
||||
const { data, hasNextPage, isLoading, isFetchingNextPage, fetchNextPage } =
|
||||
useInfiniteQuery({
|
||||
queryKey: ["timeline"],
|
||||
@ -49,22 +39,6 @@ function Home() {
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!ref.current) return;
|
||||
const handle = ref.current;
|
||||
|
||||
if (offset) {
|
||||
handle.scrollTo(offset);
|
||||
}
|
||||
|
||||
return () => {
|
||||
sessionStorage.setItem(
|
||||
cacheKey,
|
||||
JSON.stringify([handle.scrollOffset, handle.cache]),
|
||||
);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="h-full w-full overflow-hidden 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="h-full w-full overflow-y-auto pt-10">
|
||||
@ -85,7 +59,7 @@ function Home() {
|
||||
</a>
|
||||
</div>
|
||||
) : (
|
||||
<Virtualizer ref={ref} cache={cache} overscan={2}>
|
||||
<Virtualizer overscan={2}>
|
||||
{data.map((item) => renderItem(item))}
|
||||
</Virtualizer>
|
||||
)}
|
||||
|
@ -4,7 +4,6 @@ import { Keys } from "@lume/types";
|
||||
import { onboardingAtom } from "@lume/utils";
|
||||
import * as Checkbox from "@radix-ui/react-checkbox";
|
||||
import { createLazyFileRoute, useNavigate } from "@tanstack/react-router";
|
||||
import { useSetAtom } from "jotai";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { toast } from "sonner";
|
||||
@ -15,7 +14,6 @@ export const Route = createLazyFileRoute("/auth/create/self")({
|
||||
|
||||
function Create() {
|
||||
const ark = useArk();
|
||||
const setOnboarding = useSetAtom(onboardingAtom);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [t] = useTranslation();
|
||||
@ -25,8 +23,9 @@ function Create() {
|
||||
const [keys, setKeys] = useState<Keys>(null);
|
||||
|
||||
const submit = async () => {
|
||||
const save = await ark.save_account(keys);
|
||||
setLoading(true);
|
||||
|
||||
const save = await ark.save_account(keys);
|
||||
if (!save) {
|
||||
setLoading(false);
|
||||
toast.error("Save account keys failed, please try again later.");
|
||||
@ -34,7 +33,6 @@ function Create() {
|
||||
|
||||
// update state
|
||||
setLoading(false);
|
||||
setOnboarding({ open: true, newUser: true });
|
||||
|
||||
// next step
|
||||
navigate({ to: "/app/space", replace: true });
|
||||
|
@ -1,55 +1,93 @@
|
||||
import { useArk } from "@lume/ark";
|
||||
import { EyeOffIcon, EyeOnIcon, LoaderIcon } from "@lume/icons";
|
||||
import { createLazyFileRoute, useNavigate } from "@tanstack/react-router";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export const Route = createLazyFileRoute("/auth/import")({
|
||||
component: Import,
|
||||
component: Import,
|
||||
});
|
||||
|
||||
function Import() {
|
||||
const ark = useArk();
|
||||
const navigate = useNavigate();
|
||||
const ark = useArk();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [key, setKey] = useState("");
|
||||
const [t] = useTranslation();
|
||||
const [key, setKey] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [showKey, setShowKey] = useState(false);
|
||||
|
||||
const submit = async () => {
|
||||
if (!key.startsWith("nsec1")) return;
|
||||
if (key.length < 30) return;
|
||||
const submit = async () => {
|
||||
if (!key.startsWith("nsec1")) return;
|
||||
if (key.length < 30) return;
|
||||
|
||||
const npub: string = await invoke("get_public_key", { nsec: key });
|
||||
const keys = {
|
||||
npub,
|
||||
nsec: key,
|
||||
};
|
||||
setLoading(true);
|
||||
|
||||
const save = await ark.save_account(keys);
|
||||
if (save) {
|
||||
navigate({ to: "/" });
|
||||
} else {
|
||||
console.log("import failed");
|
||||
}
|
||||
};
|
||||
const npub: string = await invoke("get_public_key", { nsec: key });
|
||||
const keys = {
|
||||
npub,
|
||||
nsec: key,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center w-screen h-screen">
|
||||
<div>
|
||||
<h3>Import your key</h3>
|
||||
<div className="flex flex-col gap-2">
|
||||
<input
|
||||
name="nsec"
|
||||
value={key}
|
||||
onChange={(e) => setKey(e.target.value)}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={submit}
|
||||
className="w-full h-11 bg-gray-3 hover:bg-gray-4"
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
const save = await ark.save_account(keys);
|
||||
if (save) {
|
||||
navigate({ to: "/" });
|
||||
} else {
|
||||
console.log("import failed");
|
||||
}
|
||||
|
||||
// update state
|
||||
setLoading(false);
|
||||
|
||||
// next step
|
||||
navigate({ to: "/app/space", replace: true });
|
||||
};
|
||||
|
||||
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-2 text-center">
|
||||
<h1 className="text-2xl font-semibold">{t("login.title")}</h1>
|
||||
<p className="text-lg leading-snug text-neutral-600 dark:text-neutral-500">
|
||||
{t("login.subtitle")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="mb-0 flex flex-col gap-6">
|
||||
<div className="flex flex-col gap-6">
|
||||
<div className="relative">
|
||||
<input
|
||||
value={key}
|
||||
type={showKey ? "text" : "password"}
|
||||
onChange={(e) => setKey(e.target.value)}
|
||||
className="h-11 w-full resize-none rounded-xl border-transparent bg-neutral-100 pl-3 pr-10 placeholder:text-neutral-600 focus:border-blue-500 focus:ring focus:ring-blue-100 dark:bg-neutral-900 dark:focus:ring-blue-900"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowKey((state) => !state)}
|
||||
className="absolute right-2 top-2 inline-flex size-7 items-center justify-center rounded-lg bg-neutral-200 hover:bg-neutral-300 dark:bg-neutral-800 dark:hover:bg-neutral-700"
|
||||
>
|
||||
{showKey ? (
|
||||
<EyeOnIcon className="size-4" />
|
||||
) : (
|
||||
<EyeOffIcon className="size-4" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={submit}
|
||||
className="inline-flex h-11 w-full items-center justify-center rounded-xl bg-blue-500 text-lg font-medium text-white hover:bg-blue-600 disabled:opacity-50"
|
||||
>
|
||||
{loading ? (
|
||||
<LoaderIcon className="size-5 animate-spin" />
|
||||
) : (
|
||||
"Import"
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -13,7 +13,7 @@
|
||||
"@astrojs/check": "^0.4.1",
|
||||
"@astrojs/tailwind": "^5.1.0",
|
||||
"@fontsource/geist-mono": "^5.0.1",
|
||||
"astro": "^4.3.2",
|
||||
"astro": "^4.3.7",
|
||||
"astro-seo-meta": "^4.1.0",
|
||||
"astro-seo-schema": "^4.0.0",
|
||||
"schema-dts": "^1.1.2",
|
||||
|
30
package.json
30
package.json
@ -11,26 +11,26 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^1.5.3",
|
||||
"@tauri-apps/cli": "^2.0.0-beta.1",
|
||||
"turbo": "^1.12.2"
|
||||
"@tauri-apps/cli": "2.0.0-beta.1",
|
||||
"turbo": "^1.12.4"
|
||||
},
|
||||
"packageManager": "pnpm@8.9.0",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2.0.0-beta.0",
|
||||
"@tauri-apps/plugin-autostart": "^2.0.0-beta.0",
|
||||
"@tauri-apps/plugin-clipboard-manager": "^2.0.0-beta.0",
|
||||
"@tauri-apps/plugin-dialog": "^2.0.0-beta.0",
|
||||
"@tauri-apps/plugin-fs": "^2.0.0-beta.0",
|
||||
"@tauri-apps/plugin-http": "^2.0.0-beta.0",
|
||||
"@tauri-apps/plugin-notification": "^2.0.0-beta.0",
|
||||
"@tauri-apps/plugin-os": "^2.0.0-beta.0",
|
||||
"@tauri-apps/plugin-process": "^2.0.0-beta.0",
|
||||
"@tauri-apps/plugin-shell": "^2.0.0-beta.0",
|
||||
"@tauri-apps/plugin-sql": "^2.0.0-beta.0",
|
||||
"@tauri-apps/plugin-updater": "^2.0.0-beta.0",
|
||||
"@tauri-apps/plugin-upload": "^2.0.0-beta.0"
|
||||
"@tauri-apps/api": "2.0.0-beta.0",
|
||||
"@tauri-apps/plugin-autostart": "2.0.0-beta.0",
|
||||
"@tauri-apps/plugin-clipboard-manager": "2.0.0-beta.0",
|
||||
"@tauri-apps/plugin-dialog": "2.0.0-beta.0",
|
||||
"@tauri-apps/plugin-fs": "2.0.0-beta.0",
|
||||
"@tauri-apps/plugin-http": "2.0.0-beta.0",
|
||||
"@tauri-apps/plugin-notification": "2.0.0-beta.0",
|
||||
"@tauri-apps/plugin-os": "2.0.0-beta.0",
|
||||
"@tauri-apps/plugin-process": "2.0.0-beta.0",
|
||||
"@tauri-apps/plugin-shell": "2.0.0-beta.0",
|
||||
"@tauri-apps/plugin-sql": "2.0.0-beta.0",
|
||||
"@tauri-apps/plugin-updater": "2.0.0-beta.0",
|
||||
"@tauri-apps/plugin-upload": "2.0.0-beta.0"
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
"private": true,
|
||||
"main": "./src/index.ts",
|
||||
"dependencies": {
|
||||
"@getalby/sdk": "^3.2.3",
|
||||
"@getalby/sdk": "^3.3.0",
|
||||
"@lume/icons": "workspace:^",
|
||||
"@lume/storage": "workspace:^",
|
||||
"@lume/utils": "workspace:^",
|
||||
@ -15,27 +15,27 @@
|
||||
"@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.18.1",
|
||||
"@tanstack/react-query": "^5.20.5",
|
||||
"get-urls": "^12.1.0",
|
||||
"jotai": "^2.6.4",
|
||||
"media-chrome": "^2.1.0",
|
||||
"media-chrome": "^2.2.4",
|
||||
"minidenticons": "^4.2.0",
|
||||
"nanoid": "^5.0.5",
|
||||
"qrcode.react": "^3.1.0",
|
||||
"re-resizable": "^6.9.11",
|
||||
"react": "^18.2.0",
|
||||
"react-currency-input-field": "^3.6.14",
|
||||
"react-i18next": "^14.0.2",
|
||||
"react-currency-input-field": "^3.7.0",
|
||||
"react-i18next": "^14.0.5",
|
||||
"react-string-replace": "^1.1.1",
|
||||
"sonner": "^1.4.0",
|
||||
"string-strip-html": "^13.4.6",
|
||||
"virtua": "^0.23.3"
|
||||
"virtua": "^0.27.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lume/tailwindcss": "workspace:^",
|
||||
"@lume/tsconfig": "workspace:^",
|
||||
"@lume/types": "workspace:^",
|
||||
"@types/react": "^18.2.52",
|
||||
"@types/react": "^18.2.55",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
|
@ -114,3 +114,4 @@ export * from "./src/newColumn";
|
||||
export * from "./src/searchFilled";
|
||||
export * from "./src/arrowUp";
|
||||
export * from "./src/arrowUpSquare";
|
||||
export * from "./src/arrowDown";
|
||||
|
@ -8,7 +8,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lume/tsconfig": "workspace:*",
|
||||
"@types/react": "^18.2.52",
|
||||
"@types/react": "^18.2.55",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
}
|
24
packages/icons/src/arrowDown.tsx
Normal file
24
packages/icons/src/arrowDown.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
import { SVGProps } from "react";
|
||||
|
||||
export function ArrowDownIcon(
|
||||
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="M6.5 14.17a30.23 30.23 0 005.406 5.62c.174.14.384.21.594.21m6-5.83a30.232 30.232 0 01-5.406 5.62.949.949 0 01-.594.21m0 0V4"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
@ -8,16 +8,16 @@
|
||||
"@lume/icons": "workspace:^",
|
||||
"@lume/ui": "workspace:^",
|
||||
"@lume/utils": "workspace:^",
|
||||
"@tanstack/react-query": "^5.18.1",
|
||||
"@tanstack/react-query": "^5.20.5",
|
||||
"react": "^18.2.0",
|
||||
"sonner": "^1.4.0",
|
||||
"virtua": "^0.23.3"
|
||||
"virtua": "^0.27.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lume/tailwindcss": "workspace:^",
|
||||
"@lume/tsconfig": "workspace:^",
|
||||
"@lume/types": "workspace:^",
|
||||
"@types/react": "^18.2.52",
|
||||
"@types/react": "^18.2.55",
|
||||
"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.18.1",
|
||||
"@tanstack/react-query": "^5.20.5",
|
||||
"react": "^18.2.0",
|
||||
"sonner": "^1.4.0",
|
||||
"virtua": "^0.23.3"
|
||||
"virtua": "^0.27.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lume/tailwindcss": "workspace:^",
|
||||
"@lume/tsconfig": "workspace:^",
|
||||
"@lume/types": "workspace:^",
|
||||
"@types/react": "^18.2.52",
|
||||
"@types/react": "^18.2.55",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
|
@ -9,16 +9,16 @@
|
||||
"@lume/storage": "workspace:^",
|
||||
"@lume/ui": "workspace:^",
|
||||
"@lume/utils": "workspace:^",
|
||||
"@tanstack/react-query": "^5.18.1",
|
||||
"@tanstack/react-query": "^5.20.5",
|
||||
"react": "^18.2.0",
|
||||
"sonner": "^1.4.0",
|
||||
"virtua": "^0.23.3"
|
||||
"virtua": "^0.27.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lume/tailwindcss": "workspace:^",
|
||||
"@lume/tsconfig": "workspace:^",
|
||||
"@lume/types": "workspace:^",
|
||||
"@types/react": "^18.2.52",
|
||||
"@types/react": "^18.2.55",
|
||||
"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.18.1",
|
||||
"@tanstack/react-query": "^5.20.5",
|
||||
"react": "^18.2.0",
|
||||
"sonner": "^1.4.0",
|
||||
"virtua": "^0.23.3"
|
||||
"virtua": "^0.27.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lume/tailwindcss": "workspace:^",
|
||||
"@lume/tsconfig": "workspace:^",
|
||||
"@lume/types": "workspace:^",
|
||||
"@types/react": "^18.2.52",
|
||||
"@types/react": "^18.2.55",
|
||||
"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.18.1",
|
||||
"@tanstack/react-query": "^5.20.5",
|
||||
"react": "^18.2.0",
|
||||
"sonner": "^1.4.0",
|
||||
"virtua": "^0.23.3"
|
||||
"virtua": "^0.27.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lume/tailwindcss": "workspace:^",
|
||||
"@lume/tsconfig": "workspace:^",
|
||||
"@lume/types": "workspace:^",
|
||||
"@types/react": "^18.2.52",
|
||||
"@types/react": "^18.2.55",
|
||||
"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.18.1",
|
||||
"@tanstack/react-query": "^5.20.5",
|
||||
"react": "^18.2.0",
|
||||
"sonner": "^1.4.0",
|
||||
"virtua": "^0.23.3"
|
||||
"virtua": "^0.27.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lume/tailwindcss": "workspace:^",
|
||||
"@lume/tsconfig": "workspace:^",
|
||||
"@lume/types": "workspace:^",
|
||||
"@types/react": "^18.2.52",
|
||||
"@types/react": "^18.2.55",
|
||||
"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.18.1",
|
||||
"@tanstack/react-query": "^5.20.5",
|
||||
"react": "^18.2.0",
|
||||
"sonner": "^1.4.0",
|
||||
"virtua": "^0.23.3"
|
||||
"virtua": "^0.27.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lume/tailwindcss": "workspace:^",
|
||||
"@lume/tsconfig": "workspace:^",
|
||||
"@lume/types": "workspace:^",
|
||||
"@types/react": "^18.2.52",
|
||||
"@types/react": "^18.2.55",
|
||||
"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.18.1",
|
||||
"@tanstack/react-query": "^5.20.5",
|
||||
"react": "^18.2.0",
|
||||
"sonner": "^1.4.0",
|
||||
"virtua": "^0.23.3"
|
||||
"virtua": "^0.27.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lume/tailwindcss": "workspace:^",
|
||||
"@lume/tsconfig": "workspace:^",
|
||||
"@lume/types": "workspace:^",
|
||||
"@types/react": "^18.2.52",
|
||||
"@types/react": "^18.2.55",
|
||||
"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.18.1",
|
||||
"@tanstack/react-query": "^5.20.5",
|
||||
"react": "^18.2.0",
|
||||
"sonner": "^1.4.0",
|
||||
"virtua": "^0.23.3"
|
||||
"virtua": "^0.27.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lume/tailwindcss": "workspace:^",
|
||||
"@lume/tsconfig": "workspace:^",
|
||||
"@lume/types": "workspace:^",
|
||||
"@types/react": "^18.2.52",
|
||||
"@types/react": "^18.2.55",
|
||||
"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.18.1",
|
||||
"@tanstack/react-query": "^5.20.5",
|
||||
"react": "^18.2.0",
|
||||
"sonner": "^1.4.0",
|
||||
"virtua": "^0.23.3"
|
||||
"virtua": "^0.27.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lume/tailwindcss": "workspace:^",
|
||||
"@lume/tsconfig": "workspace:^",
|
||||
"@lume/types": "workspace:^",
|
||||
"@types/react": "^18.2.52",
|
||||
"@types/react": "^18.2.55",
|
||||
"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.18.1",
|
||||
"@tanstack/react-query": "^5.20.5",
|
||||
"react": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lume/tailwindcss": "workspace:^",
|
||||
"@lume/tsconfig": "workspace:^",
|
||||
"@lume/types": "workspace:^",
|
||||
"@types/react": "^18.2.52",
|
||||
"@types/react": "^18.2.55",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
|
@ -12,14 +12,14 @@
|
||||
"react": "^18.2.0",
|
||||
"scheduler": "^0.23.0",
|
||||
"use-context-selector": "^1.4.1",
|
||||
"virtua": "^0.23.3",
|
||||
"virtua": "^0.27.0",
|
||||
"zustand": "^4.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lume/tsconfig": "workspace:*",
|
||||
"@lume/types": "workspace:*",
|
||||
"@lume/utils": "workspace:^",
|
||||
"@types/react": "^18.2.52",
|
||||
"@types/react": "^18.2.55",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@
|
||||
"@evilmartians/harmony": "^1.2.0",
|
||||
"@tailwindcss/forms": "^0.5.7",
|
||||
"@tailwindcss/typography": "^0.5.10",
|
||||
"tailwind-scrollbar": "^3.0.5",
|
||||
"tailwind-scrollbar": "^3.1.0",
|
||||
"tailwindcss": "^3.4.1"
|
||||
}
|
||||
}
|
||||
|
@ -4,12 +4,12 @@
|
||||
"private": true,
|
||||
"main": "./src/index.ts",
|
||||
"dependencies": {
|
||||
"@getalby/sdk": "^3.2.3",
|
||||
"@getalby/sdk": "^3.3.0",
|
||||
"@lume/ark": "workspace:^",
|
||||
"@lume/icons": "workspace:^",
|
||||
"@lume/storage": "workspace:^",
|
||||
"@lume/utils": "workspace:^",
|
||||
"@nostr-dev-kit/ndk": "^2.4.0",
|
||||
"@nostr-dev-kit/ndk": "^2.4.1",
|
||||
"@radix-ui/react-accordion": "^1.1.2",
|
||||
"@radix-ui/react-alert-dialog": "^1.0.5",
|
||||
"@radix-ui/react-avatar": "^1.0.4",
|
||||
@ -19,22 +19,22 @@
|
||||
"@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.18.1",
|
||||
"@tanstack/react-router": "^1.16.0",
|
||||
"framer-motion": "^11.0.3",
|
||||
"@tanstack/react-query": "^5.20.5",
|
||||
"@tanstack/react-router": "^1.16.2",
|
||||
"framer-motion": "^11.0.5",
|
||||
"get-urls": "^12.1.0",
|
||||
"jotai": "^2.6.4",
|
||||
"media-chrome": "^2.1.0",
|
||||
"media-chrome": "^2.2.4",
|
||||
"minidenticons": "^4.2.0",
|
||||
"nanoid": "^5.0.5",
|
||||
"qrcode.react": "^3.1.0",
|
||||
"re-resizable": "^6.9.11",
|
||||
"react": "^18.2.0",
|
||||
"react-currency-input-field": "^3.6.14",
|
||||
"react-currency-input-field": "^3.7.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-hook-form": "^7.50.0",
|
||||
"react-hook-form": "^7.50.1",
|
||||
"react-hotkeys-hook": "^4.5.0",
|
||||
"react-i18next": "^14.0.2",
|
||||
"react-i18next": "^14.0.5",
|
||||
"react-router-dom": "^6.22.0",
|
||||
"react-string-replace": "^1.1.1",
|
||||
"slate": "^0.101.5",
|
||||
@ -43,13 +43,13 @@
|
||||
"string-strip-html": "^13.4.6",
|
||||
"uqr": "^0.1.2",
|
||||
"use-debounce": "^10.0.0",
|
||||
"virtua": "^0.23.3"
|
||||
"virtua": "^0.27.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lume/tailwindcss": "workspace:^",
|
||||
"@lume/tsconfig": "workspace:^",
|
||||
"@lume/types": "workspace:^",
|
||||
"@types/react": "^18.2.52",
|
||||
"@types/react": "^18.2.55",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ export function ActiveAccount() {
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
style={{ contentVisibility: "auto" }}
|
||||
className="aspect-square h-auto w-7 rounded-lg object-cover"
|
||||
className="aspect-square h-auto w-7 rounded-full object-cover"
|
||||
/>
|
||||
<Avatar.Fallback delayMs={150}>
|
||||
<img
|
||||
|
@ -1,137 +1,25 @@
|
||||
import { ReactionIcon } from "@lume/icons";
|
||||
import * as HoverCard from "@radix-ui/react-hover-card";
|
||||
import { ArrowDownIcon, ArrowUpIcon } from "@lume/icons";
|
||||
import { useState } from "react";
|
||||
import { useNoteContext } from "../provider";
|
||||
|
||||
const REACTIONS = [
|
||||
{
|
||||
content: "👏",
|
||||
img: "/clapping_hands.png",
|
||||
},
|
||||
{
|
||||
content: "🤪",
|
||||
img: "/face_with_tongue.png",
|
||||
},
|
||||
{
|
||||
content: "😮",
|
||||
img: "/face_with_open_mouth.png",
|
||||
},
|
||||
{
|
||||
content: "😢",
|
||||
img: "/crying_face.png",
|
||||
},
|
||||
{
|
||||
content: "🤡",
|
||||
img: "/clown_face.png",
|
||||
},
|
||||
];
|
||||
|
||||
export function NoteReaction() {
|
||||
const event = useNoteContext();
|
||||
const event = useNoteContext();
|
||||
const [reaction, setReaction] = useState<"+" | "-">(null);
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
const [reaction, setReaction] = useState<string | null>(null);
|
||||
|
||||
const getReactionImage = (content: string) => {
|
||||
const reaction: { img: string } = REACTIONS.find(
|
||||
(el) => el.content === content,
|
||||
);
|
||||
return reaction.img;
|
||||
};
|
||||
|
||||
const react = async (content: string) => {
|
||||
try {
|
||||
setReaction(content);
|
||||
|
||||
// react
|
||||
await event.react(content);
|
||||
|
||||
setOpen(false);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<HoverCard.Root open={open} onOpenChange={setOpen}>
|
||||
<HoverCard.Trigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex items-center justify-center group h-7 w-7 text-neutral-600 dark:text-neutral-400"
|
||||
>
|
||||
{reaction ? (
|
||||
<img
|
||||
src={getReactionImage(reaction)}
|
||||
alt={reaction}
|
||||
className="size-6"
|
||||
/>
|
||||
) : (
|
||||
<ReactionIcon className="size-5 group-hover:text-blue-500" />
|
||||
)}
|
||||
</button>
|
||||
</HoverCard.Trigger>
|
||||
<HoverCard.Portal>
|
||||
<HoverCard.Content
|
||||
className="select-none rounded-lg bg-neutral-950 dark:bg-neutral-50 px-1 py-1 text-sm will-change-[transform,opacity] data-[state=open]:data-[side=bottom]:animate-slideUpAndFade data-[state=open]:data-[side=left]:animate-slideRightAndFade data-[state=open]:data-[side=right]:animate-slideLeftAndFade data-[state=open]:data-[side=top]:animate-slideDownAndFade"
|
||||
sideOffset={0}
|
||||
side="top"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => react("👏")}
|
||||
className="inline-flex items-center justify-center w-8 h-8 rounded-md backdrop-blur-xl hover:bg-white/10 dark:hover:bg-black/10"
|
||||
>
|
||||
<img
|
||||
src="/clapping_hands.png"
|
||||
alt="Clapping Hands"
|
||||
className="size-6"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => react("🤪")}
|
||||
className="inline-flex items-center justify-center w-8 h-8 rounded-md backdrop-blur-xl hover:bg-white/10 dark:hover:bg-black/10"
|
||||
>
|
||||
<img
|
||||
src="/face_with_tongue.png"
|
||||
alt="Face with Tongue"
|
||||
className="size-6"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => react("😮")}
|
||||
className="inline-flex items-center justify-center w-8 h-8 rounded-md backdrop-blur-xl hover:bg-white/10 dark:hover:bg-black/10"
|
||||
>
|
||||
<img
|
||||
src="/face_with_open_mouth.png"
|
||||
alt="Face with Open Mouth"
|
||||
className="size-6"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => react("😢")}
|
||||
className="inline-flex items-center justify-center w-8 h-8 rounded-md backdrop-blur-xl hover:bg-white/10 dark:hover:bg-black/10"
|
||||
>
|
||||
<img
|
||||
src="/crying_face.png"
|
||||
alt="Crying Face"
|
||||
className="size-6"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => react("🤡")}
|
||||
className="inline-flex items-center justify-center w-8 h-8 rounded-md backdrop-blur-xl hover:bg-white/10 dark:hover:bg-black/10"
|
||||
>
|
||||
<img src="/clown_face.png" alt="Clown Face" className="size-6" />
|
||||
</button>
|
||||
</div>
|
||||
<HoverCard.Arrow className="fill-neutral-950 dark:fill-neutral-50" />
|
||||
</HoverCard.Content>
|
||||
</HoverCard.Portal>
|
||||
</HoverCard.Root>
|
||||
);
|
||||
return (
|
||||
<div className="inline-flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex size-7 items-center justify-center rounded-full bg-neutral-100 text-neutral-700 hover:bg-blue-500 hover:text-white dark:bg-neutral-900 dark:text-neutral-300"
|
||||
>
|
||||
<ArrowUpIcon className="size-4" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex size-7 items-center justify-center rounded-full bg-neutral-100 text-neutral-700 hover:bg-blue-500 hover:text-white dark:bg-neutral-900 dark:text-neutral-300"
|
||||
>
|
||||
<ArrowDownIcon className="size-4" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ export function NoteReply() {
|
||||
<Tooltip.Trigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="group inline-flex h-7 w-7 items-center justify-center text-neutral-600 dark:text-neutral-400"
|
||||
className="group inline-flex h-7 w-7 items-center justify-center text-neutral-800 dark:text-neutral-200"
|
||||
>
|
||||
<ReplyIcon className="size-5 group-hover:text-blue-500" />
|
||||
</button>
|
||||
|
@ -9,109 +9,109 @@ import { toast } from "sonner";
|
||||
import { useNoteContext } from "../provider";
|
||||
|
||||
export function NoteRepost() {
|
||||
const event = useNoteContext();
|
||||
const setEditorValue = useSetAtom(editorValueAtom);
|
||||
const setIsEditorOpen = useSetAtom(editorAtom);
|
||||
const event = useNoteContext();
|
||||
const setEditorValue = useSetAtom(editorValueAtom);
|
||||
const setIsEditorOpen = useSetAtom(editorAtom);
|
||||
|
||||
const [t] = useTranslation();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [isRepost, setIsRepost] = useState(false);
|
||||
const [open, setOpen] = useState(false);
|
||||
const [t] = useTranslation();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [isRepost, setIsRepost] = useState(false);
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const repost = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const repost = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
// repost
|
||||
await event.repost(true);
|
||||
// repost
|
||||
await event.repost(true);
|
||||
|
||||
// update state
|
||||
setLoading(false);
|
||||
setIsRepost(true);
|
||||
// update state
|
||||
setLoading(false);
|
||||
setIsRepost(true);
|
||||
|
||||
// notify
|
||||
toast.success("You've reposted this post successfully");
|
||||
} catch (e) {
|
||||
setLoading(false);
|
||||
toast.error("Repost failed, try again later");
|
||||
}
|
||||
};
|
||||
// notify
|
||||
toast.success("You've reposted this post successfully");
|
||||
} catch (e) {
|
||||
setLoading(false);
|
||||
toast.error("Repost failed, try again later");
|
||||
}
|
||||
};
|
||||
|
||||
const quote = () => {
|
||||
setEditorValue([
|
||||
{
|
||||
type: "paragraph",
|
||||
children: [{ text: "" }],
|
||||
},
|
||||
{
|
||||
type: "event",
|
||||
// @ts-expect-error, useless
|
||||
eventId: `nostr:${nip19.noteEncode(event.id)}`,
|
||||
children: [{ text: "" }],
|
||||
},
|
||||
{
|
||||
type: "paragraph",
|
||||
children: [{ text: "" }],
|
||||
},
|
||||
]);
|
||||
setIsEditorOpen(true);
|
||||
};
|
||||
const quote = () => {
|
||||
setEditorValue([
|
||||
{
|
||||
type: "paragraph",
|
||||
children: [{ text: "" }],
|
||||
},
|
||||
{
|
||||
type: "event",
|
||||
// @ts-expect-error, useless
|
||||
eventId: `nostr:${nip19.noteEncode(event.id)}`,
|
||||
children: [{ text: "" }],
|
||||
},
|
||||
{
|
||||
type: "paragraph",
|
||||
children: [{ text: "" }],
|
||||
},
|
||||
]);
|
||||
setIsEditorOpen(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<DropdownMenu.Root open={open} onOpenChange={setOpen}>
|
||||
<Tooltip.Provider>
|
||||
<Tooltip.Root delayDuration={150}>
|
||||
<DropdownMenu.Trigger asChild>
|
||||
<Tooltip.Trigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex items-center justify-center group h-7 w-7 text-neutral-600 dark:text-neutral-400"
|
||||
>
|
||||
{loading ? (
|
||||
<LoaderIcon className="size-4 animate-spin" />
|
||||
) : (
|
||||
<RepostIcon
|
||||
className={cn(
|
||||
"size-5 group-hover:text-blue-600",
|
||||
isRepost ? "text-blue-500" : "",
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
</Tooltip.Trigger>
|
||||
</DropdownMenu.Trigger>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content className="inline-flex h-7 select-none text-neutral-50 dark:text-neutral-950 items-center justify-center rounded-md bg-neutral-950 dark:bg-neutral-50 px-3.5 text-sm will-change-[transform,opacity] data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade">
|
||||
{t("note.buttons.repost")}
|
||||
<Tooltip.Arrow className="fill-neutral-950 dark:fill-neutral-50" />
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
</Tooltip.Provider>
|
||||
<DropdownMenu.Portal>
|
||||
<DropdownMenu.Content className="flex w-[200px] p-2 flex-col overflow-hidden rounded-2xl bg-white/50 dark:bg-black/50 ring-1 ring-black/10 dark:ring-white/10 backdrop-blur-2xl focus:outline-none">
|
||||
<DropdownMenu.Item asChild>
|
||||
<button
|
||||
type="button"
|
||||
onClick={repost}
|
||||
className="inline-flex items-center gap-3 px-3 text-sm font-medium rounded-lg h-9 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"
|
||||
>
|
||||
<RepostIcon className="size-4" />
|
||||
{t("note.buttons.repost")}
|
||||
</button>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item asChild>
|
||||
<button
|
||||
type="button"
|
||||
onClick={quote}
|
||||
className="inline-flex items-center gap-3 px-3 text-sm font-medium rounded-lg h-9 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"
|
||||
>
|
||||
<ReplyIcon className="size-4" />
|
||||
{t("note.buttons.quote")}
|
||||
</button>
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Portal>
|
||||
</DropdownMenu.Root>
|
||||
);
|
||||
return (
|
||||
<DropdownMenu.Root open={open} onOpenChange={setOpen}>
|
||||
<Tooltip.Provider>
|
||||
<Tooltip.Root delayDuration={150}>
|
||||
<DropdownMenu.Trigger asChild>
|
||||
<Tooltip.Trigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="group inline-flex h-7 w-7 items-center justify-center text-neutral-800 dark:text-neutral-200"
|
||||
>
|
||||
{loading ? (
|
||||
<LoaderIcon className="size-4 animate-spin" />
|
||||
) : (
|
||||
<RepostIcon
|
||||
className={cn(
|
||||
"size-5 group-hover:text-blue-600",
|
||||
isRepost ? "text-blue-500" : "",
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
</Tooltip.Trigger>
|
||||
</DropdownMenu.Trigger>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content className="data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade inline-flex h-7 select-none items-center justify-center rounded-md bg-neutral-950 px-3.5 text-sm text-neutral-50 will-change-[transform,opacity] dark:bg-neutral-50 dark:text-neutral-950">
|
||||
{t("note.buttons.repost")}
|
||||
<Tooltip.Arrow className="fill-neutral-950 dark:fill-neutral-50" />
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
</Tooltip.Provider>
|
||||
<DropdownMenu.Portal>
|
||||
<DropdownMenu.Content className="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>
|
||||
<button
|
||||
type="button"
|
||||
onClick={repost}
|
||||
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"
|
||||
>
|
||||
<RepostIcon className="size-4" />
|
||||
{t("note.buttons.repost")}
|
||||
</button>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item asChild>
|
||||
<button
|
||||
type="button"
|
||||
onClick={quote}
|
||||
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"
|
||||
>
|
||||
<ReplyIcon className="size-4" />
|
||||
{t("note.buttons.quote")}
|
||||
</button>
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Portal>
|
||||
</DropdownMenu.Root>
|
||||
);
|
||||
}
|
||||
|
@ -85,7 +85,7 @@ export function NoteZap() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => createZapRequest(true)}
|
||||
className="group inline-flex size-7 items-center justify-center text-neutral-600 dark:text-neutral-400"
|
||||
className="group inline-flex h-7 w-7 items-center justify-center text-neutral-800 dark:text-neutral-200"
|
||||
>
|
||||
{isLoading ? (
|
||||
<LoaderIcon className="size-4 animate-spin" />
|
||||
@ -118,7 +118,7 @@ export function NoteZap() {
|
||||
<Tooltip.Trigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="group inline-flex size-7 items-center justify-center text-neutral-600 dark:text-neutral-400"
|
||||
className="group inline-flex h-7 w-7 items-center justify-center text-neutral-800 dark:text-neutral-200"
|
||||
>
|
||||
<ZapIcon className="size-5 group-hover:text-blue-500" />
|
||||
</button>
|
||||
|
@ -65,7 +65,7 @@ export function NoteChild({
|
||||
href={url.toString()}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="break-p font-normal text-blue-500 hover:text-blue-600"
|
||||
className="content-break font-normal text-blue-500 hover:text-blue-600"
|
||||
>
|
||||
{url.toString()}
|
||||
</a>
|
||||
@ -104,7 +104,7 @@ export function NoteChild({
|
||||
<div className="relative flex gap-3">
|
||||
<div className="relative flex-1 rounded-md bg-neutral-200 px-2 py-2 dark:bg-neutral-800">
|
||||
<div className="absolute right-0 top-[18px] h-3 w-3 -translate-y-1/2 translate-x-1/2 rotate-45 transform bg-neutral-200 dark:bg-neutral-800" />
|
||||
<div className="break-p mt-6 line-clamp-3 select-text leading-normal text-neutral-900 dark:text-neutral-100">
|
||||
<div className="content-break mt-6 line-clamp-3 select-text leading-normal text-neutral-900 dark:text-neutral-100">
|
||||
{richContent}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -167,7 +167,7 @@ export function NoteContent({ className }: { className?: string }) {
|
||||
href={url.toString()}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="break-p inline-block w-full truncate font-normal text-blue-500 hover:text-blue-600"
|
||||
className="content-break inline-block w-full truncate font-normal text-blue-500 hover:text-blue-600"
|
||||
>
|
||||
{url.toString()}
|
||||
</a>
|
||||
@ -224,7 +224,7 @@ export function NoteContent({ className }: { className?: string }) {
|
||||
|
||||
return (
|
||||
<div className={cn(className)}>
|
||||
<div className="break-p select-text whitespace-pre-line text-balance leading-normal">
|
||||
<div className="content-break select-text whitespace-pre-line text-balance leading-normal">
|
||||
{richContent}
|
||||
</div>
|
||||
{storage.settings.translation && translate.translatable ? (
|
||||
|
@ -68,7 +68,7 @@ export function MentionNote({
|
||||
href={url.toString()}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="break-p inline-block w-full truncate font-normal text-blue-500 hover:text-blue-600"
|
||||
className="content-break inline-block w-full truncate font-normal text-blue-500 hover:text-blue-600"
|
||||
>
|
||||
{url.toString()}
|
||||
</a>
|
||||
|
@ -68,12 +68,12 @@ export function LinkPreview({ url }: { url: string }) {
|
||||
<div className="flex flex-col items-start p-3">
|
||||
<div className="flex flex-col items-start text-left">
|
||||
{data.title ? (
|
||||
<div className="break-p text-base font-semibold text-neutral-900 dark:text-neutral-100">
|
||||
<div className="content-break text-base font-semibold text-neutral-900 dark:text-neutral-100">
|
||||
{data.title}
|
||||
</div>
|
||||
) : null}
|
||||
{data.description ? (
|
||||
<div className="break-p mb-2 line-clamp-3 text-balance text-sm text-neutral-700 dark:text-neutral-400">
|
||||
<div className="content-break mb-2 line-clamp-3 text-balance text-sm text-neutral-700 dark:text-neutral-400">
|
||||
{data.description}
|
||||
</div>
|
||||
) : null}
|
||||
|
@ -11,18 +11,23 @@ export function TextNote({
|
||||
}) {
|
||||
return (
|
||||
<Note.Provider event={event}>
|
||||
<Note.Root className={cn("flex flex-col", className)}>
|
||||
<div className="flex h-14 items-center justify-between px-3">
|
||||
<Note.Root
|
||||
className={cn(
|
||||
"mb-3 flex flex-col gap-2 border-b border-neutral-100 pb-3 dark:border-neutral-900",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<div className="flex items-start justify-between">
|
||||
<Note.User className="flex-1 pr-2" />
|
||||
<Note.Menu />
|
||||
</div>
|
||||
<div className="flex gap-3">
|
||||
<div className="size-10 shrink-0" />
|
||||
<div className="flex-1">
|
||||
<div className="min-w-0 flex-1">
|
||||
<Note.Content className="mb-2" />
|
||||
<Note.Thread className="mb-2" />
|
||||
<Note.Content className="min-w-0 px-3" />
|
||||
<div className="flex h-14 items-center justify-between px-3">
|
||||
<Note.Pin />
|
||||
<div className="mt-5 flex items-center justify-between">
|
||||
<Note.Reaction />
|
||||
<div className="inline-flex items-center gap-4">
|
||||
<Note.Reply />
|
||||
<Note.Repost />
|
||||
|
@ -18,14 +18,14 @@ export function NoteThread({ className }: { className?: string }) {
|
||||
if (!thread) return null;
|
||||
|
||||
return (
|
||||
<div className={cn("w-full px-3", className)}>
|
||||
<div className={cn("w-full", className)}>
|
||||
<div className="flex h-min w-full flex-col gap-3 rounded-lg bg-neutral-100 p-3 dark:bg-neutral-900">
|
||||
{thread.rootEventId ? (
|
||||
<Note.Child eventId={thread.rootEventId} isRoot />
|
||||
) : null}
|
||||
{thread.replyEventId ? (
|
||||
<Note.Child eventId={thread.replyEventId} />
|
||||
) : null}
|
||||
{thread.rootEventId ? (
|
||||
<Note.Child eventId={thread.rootEventId} isRoot />
|
||||
) : null}
|
||||
<div className="inline-flex items-center justify-between">
|
||||
<a
|
||||
href={`/events/${thread?.rootEventId || thread?.replyEventId}`}
|
||||
|
@ -10,15 +10,15 @@ export function NoteUser({ className }: { className?: string }) {
|
||||
<User.Provider pubkey={event.pubkey}>
|
||||
<HoverCard.Root>
|
||||
<User.Root
|
||||
className={cn("flex items-center justify-between", className)}
|
||||
className={cn("flex items-start justify-between", className)}
|
||||
>
|
||||
<div className="flex gap-3">
|
||||
<HoverCard.Trigger>
|
||||
<User.Avatar className="size-11 shrink-0 rounded-xl object-cover ring-1 ring-neutral-200/50 dark:ring-neutral-800/50" />
|
||||
<User.Avatar className="size-10 shrink-0 rounded-full object-cover ring-1 ring-neutral-200/50 dark:ring-neutral-800/50" />
|
||||
</HoverCard.Trigger>
|
||||
<div>
|
||||
<User.Name className="font-semibold text-neutral-950 dark:text-neutral-50" />
|
||||
<User.NIP05 className="text-neutral-600 dark:text-neutral-400" />
|
||||
<User.Name className="font-semibold leading-tight text-neutral-950 dark:text-neutral-50" />
|
||||
<User.NIP05 className="leading-tight text-neutral-600 dark:text-neutral-400" />
|
||||
</div>
|
||||
</div>
|
||||
<User.Time
|
||||
|
@ -2,36 +2,36 @@ import { cn } from "@lume/utils";
|
||||
import { useUserContext } from "./provider";
|
||||
|
||||
export function UserAbout({ className }: { className?: string }) {
|
||||
const user = useUserContext();
|
||||
const user = useUserContext();
|
||||
|
||||
if (!user.profile) {
|
||||
return (
|
||||
<div className="flex flex-col gap-1">
|
||||
<div
|
||||
className={cn(
|
||||
"h-4 w-20 bg-black/20 dark:bg-white/20 rounded animate-pulse",
|
||||
className,
|
||||
)}
|
||||
/>
|
||||
<div
|
||||
className={cn(
|
||||
"h-4 w-full bg-black/20 dark:bg-white/20 rounded animate-pulse",
|
||||
className,
|
||||
)}
|
||||
/>
|
||||
<div
|
||||
className={cn(
|
||||
"h-4 w-24 bg-black/20 dark:bg-white/20 rounded animate-pulse",
|
||||
className,
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (!user.profile) {
|
||||
return (
|
||||
<div className="flex flex-col gap-1">
|
||||
<div
|
||||
className={cn(
|
||||
"h-4 w-20 animate-pulse rounded bg-black/20 dark:bg-white/20",
|
||||
className,
|
||||
)}
|
||||
/>
|
||||
<div
|
||||
className={cn(
|
||||
"h-4 w-full animate-pulse rounded bg-black/20 dark:bg-white/20",
|
||||
className,
|
||||
)}
|
||||
/>
|
||||
<div
|
||||
className={cn(
|
||||
"h-4 w-24 animate-pulse rounded bg-black/20 dark:bg-white/20",
|
||||
className,
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn("select-text break-p", className)}>
|
||||
{user.profile.about?.trim() || "No bio"}
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className={cn("content-break select-text", className)}>
|
||||
{user.profile.about?.trim() || "No bio"}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -9,11 +9,11 @@ export function UserNip05({ className }: { className?: string }) {
|
||||
const user = useUserContext();
|
||||
|
||||
const { isLoading, data: verified } = useQuery({
|
||||
queryKey: ["nip05", user?.profile.nip05],
|
||||
queryKey: ["nip05", user?.pubkey],
|
||||
queryFn: async () => {
|
||||
if (!user.profile?.nip05) return false;
|
||||
|
||||
const verify = await ark.verify_nip05(user.pubkey, user.profile?.nip05);
|
||||
console.log(verify);
|
||||
return verify;
|
||||
},
|
||||
enabled: !!user.profile,
|
||||
|
@ -8,17 +8,17 @@
|
||||
"access": "public"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tanstack/react-query": "^5.18.1",
|
||||
"@tanstack/react-query": "^5.20.5",
|
||||
"clsx": "^2.1.0",
|
||||
"dayjs": "^1.11.10",
|
||||
"jotai": "^2.6.4",
|
||||
"nostr-tools": "^2.1.7",
|
||||
"nostr-tools": "^2.1.9",
|
||||
"react": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lume/tsconfig": "workspace:^",
|
||||
"@lume/types": "workspace:^",
|
||||
"@types/react": "^18.2.52",
|
||||
"@types/react": "^18.2.55",
|
||||
"tailwind-merge": "^2.2.1",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
|
2534
pnpm-lock.yaml
generated
2534
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -152,7 +152,9 @@ fn main() {
|
||||
nostr::keys::load_account,
|
||||
nostr::keys::event_to_bech32,
|
||||
nostr::keys::user_to_bech32,
|
||||
nostr::keys::verify_nip05,
|
||||
nostr::metadata::get_profile,
|
||||
nostr::metadata::create_profile,
|
||||
nostr::event::get_event,
|
||||
nostr::event::get_text_events,
|
||||
nostr::event::get_event_thread,
|
||||
|
Loading…
x
Reference in New Issue
Block a user