mirror of
https://github.com/luminous-devs/lume.git
synced 2024-10-01 09:21:07 +00:00
wip: update
This commit is contained in:
parent
6171b9bed1
commit
cdf29f8a54
@ -1,12 +1,12 @@
|
|||||||
import { useArk } from "@lume/ark";
|
import { useArk } from "@lume/ark";
|
||||||
import { ArrowRightCircleIcon, LoaderIcon, SearchIcon } from "@lume/icons";
|
import { ArrowRightCircleIcon, LoaderIcon, SearchIcon } from "@lume/icons";
|
||||||
import { Event, Kind } from "@lume/types";
|
import { Event, Kind } from "@lume/types";
|
||||||
import { EmptyFeed } from "@lume/ui";
|
import { EmptyFeed, TextNote } from "@lume/ui";
|
||||||
import { FETCH_LIMIT } from "@lume/utils";
|
import { FETCH_LIMIT } from "@lume/utils";
|
||||||
import { useInfiniteQuery } from "@tanstack/react-query";
|
import { useInfiniteQuery } from "@tanstack/react-query";
|
||||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||||
import { useEffect, useMemo, useRef } from "react";
|
import { useEffect, useMemo, useRef } from "react";
|
||||||
import { CacheSnapshot, VList, VListHandle } from "virtua";
|
import { CacheSnapshot, Virtualizer, VListHandle } from "virtua";
|
||||||
|
|
||||||
export const Route = createLazyFileRoute("/app/home")({
|
export const Route = createLazyFileRoute("/app/home")({
|
||||||
component: Home,
|
component: Home,
|
||||||
@ -34,21 +34,18 @@ function Home() {
|
|||||||
getNextPageParam: (lastPage) => {
|
getNextPageParam: (lastPage) => {
|
||||||
const lastEvent = lastPage.at(-1);
|
const lastEvent = lastPage.at(-1);
|
||||||
if (!lastEvent) return;
|
if (!lastEvent) return;
|
||||||
return lastEvent.created_at - 1;
|
return lastEvent.created_at;
|
||||||
},
|
},
|
||||||
select: (data) => data?.pages.flatMap((page) => page),
|
select: (data) => data?.pages.flatMap((page) => page),
|
||||||
refetchOnWindowFocus: false,
|
refetchOnWindowFocus: false,
|
||||||
refetchOnMount: false,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const renderItem = (event: Event) => {
|
const renderItem = (event: Event) => {
|
||||||
switch (event.kind) {
|
switch (event.kind) {
|
||||||
case Kind.Text:
|
case Kind.Text:
|
||||||
return <p key={event.id}>{event.content}</p>;
|
return <TextNote key={event.id} event={event} />;
|
||||||
case Kind.Repost:
|
|
||||||
return <p key={event.id}>{event.content}</p>;
|
|
||||||
default:
|
default:
|
||||||
return <p key={event.id}>{event.content}</p>;
|
return <TextNote key={event.id} event={event} />;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -70,25 +67,27 @@ function Home() {
|
|||||||
|
|
||||||
return (
|
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-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="mx-auto h-full w-full max-w-2xl pt-10">
|
<div className="h-full w-full overflow-y-auto pt-10">
|
||||||
<VList ref={ref} cache={cache} overscan={2}>
|
<div className="mx-auto w-full max-w-xl">
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="flex h-16 w-full items-center justify-center gap-2 px-3 py-1.5">
|
<div className="flex h-20 w-full items-center justify-center">
|
||||||
<LoaderIcon className="size-5 animate-spin" />
|
<LoaderIcon className="size-5 animate-spin" />
|
||||||
</div>
|
</div>
|
||||||
) : !data.length ? (
|
) : !data.length ? (
|
||||||
<div className="mt-3 px-3">
|
<div className="flex flex-col gap-3">
|
||||||
<EmptyFeed />
|
<EmptyFeed />
|
||||||
<a
|
<a
|
||||||
href="/suggest"
|
href="/suggest"
|
||||||
className="mt-3 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"
|
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" />
|
<SearchIcon className="size-5" />
|
||||||
Find accounts to follow
|
Find accounts to follow
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
data.map((item) => renderItem(item))
|
<Virtualizer ref={ref} cache={cache} overscan={2}>
|
||||||
|
{data.map((item) => renderItem(item))}
|
||||||
|
</Virtualizer>
|
||||||
)}
|
)}
|
||||||
<div className="flex h-16 items-center justify-center">
|
<div className="flex h-16 items-center justify-center">
|
||||||
{hasNextPage ? (
|
{hasNextPage ? (
|
||||||
@ -109,7 +108,7 @@ function Home() {
|
|||||||
</button>
|
</button>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</VList>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -190,4 +190,16 @@ export class Ark {
|
|||||||
console.error(String(e));
|
console.error(String(e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async verify_nip05(pubkey: string, nip05: string) {
|
||||||
|
try {
|
||||||
|
const cmd: boolean = await invoke("verify_nip05", {
|
||||||
|
key: pubkey,
|
||||||
|
nip05,
|
||||||
|
});
|
||||||
|
return cmd;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,10 @@ export * from "./user";
|
|||||||
export * from "./note";
|
export * from "./note";
|
||||||
export * from "./column";
|
export * from "./column";
|
||||||
|
|
||||||
|
// Note Primities
|
||||||
|
export * from "./note/primitives/text";
|
||||||
|
export * from "./note/primitives/repost";
|
||||||
|
|
||||||
// Deprecated
|
// Deprecated
|
||||||
export * from "./routes/event";
|
export * from "./routes/event";
|
||||||
export * from "./routes/user";
|
export * from "./routes/user";
|
||||||
|
@ -1,13 +1,10 @@
|
|||||||
import { ReplyIcon } from "@lume/icons";
|
import { ReplyIcon } from "@lume/icons";
|
||||||
import * as Tooltip from "@radix-ui/react-tooltip";
|
import * as Tooltip from "@radix-ui/react-tooltip";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useNavigate } from "react-router-dom";
|
|
||||||
import { useNoteContext } from "../provider";
|
import { useNoteContext } from "../provider";
|
||||||
|
|
||||||
export function NoteReply() {
|
export function NoteReply() {
|
||||||
const event = useNoteContext();
|
const event = useNoteContext();
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -16,14 +13,13 @@ export function NoteReply() {
|
|||||||
<Tooltip.Trigger asChild>
|
<Tooltip.Trigger asChild>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => navigate(`/events/${event.id}`)}
|
className="group inline-flex h-7 w-7 items-center justify-center text-neutral-600 dark:text-neutral-400"
|
||||||
className="inline-flex items-center justify-center group h-7 w-7 text-neutral-600 dark:text-neutral-400"
|
|
||||||
>
|
>
|
||||||
<ReplyIcon className="size-5 group-hover:text-blue-500" />
|
<ReplyIcon className="size-5 group-hover:text-blue-500" />
|
||||||
</button>
|
</button>
|
||||||
</Tooltip.Trigger>
|
</Tooltip.Trigger>
|
||||||
<Tooltip.Portal>
|
<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">
|
<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.menu.viewThread")}
|
{t("note.menu.viewThread")}
|
||||||
<Tooltip.Arrow className="fill-neutral-950 dark:fill-neutral-50" />
|
<Tooltip.Arrow className="fill-neutral-950 dark:fill-neutral-50" />
|
||||||
</Tooltip.Content>
|
</Tooltip.Content>
|
||||||
|
@ -2,7 +2,6 @@ import { NOSTR_MENTIONS } from "@lume/utils";
|
|||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { ReactNode, useMemo } from "react";
|
import { ReactNode, useMemo } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
import reactStringReplace from "react-string-replace";
|
import reactStringReplace from "react-string-replace";
|
||||||
import { User } from "../user";
|
import { User } from "../user";
|
||||||
import { Hashtag } from "./mentions/hashtag";
|
import { Hashtag } from "./mentions/hashtag";
|
||||||
@ -61,15 +60,15 @@ export function NoteChild({
|
|||||||
(match, i) => {
|
(match, i) => {
|
||||||
const url = new URL(match);
|
const url = new URL(match);
|
||||||
return (
|
return (
|
||||||
<Link
|
<a
|
||||||
key={match + i}
|
key={match + i}
|
||||||
to={url.toString()}
|
href={url.toString()}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
className="break-p font-normal text-blue-500 hover:text-blue-600"
|
className="break-p font-normal text-blue-500 hover:text-blue-600"
|
||||||
>
|
>
|
||||||
{url.toString()}
|
{url.toString()}
|
||||||
</Link>
|
</a>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -14,7 +14,6 @@ import { fetch } from "@tauri-apps/plugin-http";
|
|||||||
import getUrls from "get-urls";
|
import getUrls from "get-urls";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { ReactNode, useMemo, useState } from "react";
|
import { ReactNode, useMemo, useState } from "react";
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
import reactStringReplace from "react-string-replace";
|
import reactStringReplace from "react-string-replace";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { stripHtml } from "string-strip-html";
|
import { stripHtml } from "string-strip-html";
|
||||||
@ -27,11 +26,7 @@ import { LinkPreview } from "./preview/link";
|
|||||||
import { VideoPreview } from "./preview/video";
|
import { VideoPreview } from "./preview/video";
|
||||||
import { useNoteContext } from "./provider";
|
import { useNoteContext } from "./provider";
|
||||||
|
|
||||||
export function NoteContent({
|
export function NoteContent({ className }: { className?: string }) {
|
||||||
className,
|
|
||||||
}: {
|
|
||||||
className?: string;
|
|
||||||
}) {
|
|
||||||
const storage = useStorage();
|
const storage = useStorage();
|
||||||
const event = useNoteContext();
|
const event = useNoteContext();
|
||||||
|
|
||||||
@ -167,15 +162,15 @@ export function NoteContent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link
|
<a
|
||||||
key={match + i}
|
key={match + i}
|
||||||
to={url.toString()}
|
href={url.toString()}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
className="break-p truncate inline-block w-full font-normal text-blue-500 hover:text-blue-600"
|
className="break-p inline-block w-full truncate font-normal text-blue-500 hover:text-blue-600"
|
||||||
>
|
>
|
||||||
{url.toString()}
|
{url.toString()}
|
||||||
</Link>
|
</a>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -229,7 +224,7 @@ export function NoteContent({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn(className)}>
|
<div className={cn(className)}>
|
||||||
<div className="break-p select-text text-balance leading-normal whitespace-pre-line">
|
<div className="break-p select-text whitespace-pre-line text-balance leading-normal">
|
||||||
{richContent}
|
{richContent}
|
||||||
</div>
|
</div>
|
||||||
{storage.settings.translation && translate.translatable ? (
|
{storage.settings.translation && translate.translatable ? (
|
||||||
@ -237,7 +232,7 @@ export function NoteContent({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setContent(event.content)}
|
onClick={() => setContent(event.content)}
|
||||||
className="mt-3 text-sm text-blue-500 hover:text-blue-600 border-none shadow-none focus:outline-none"
|
className="mt-3 border-none text-sm text-blue-500 shadow-none hover:text-blue-600 focus:outline-none"
|
||||||
>
|
>
|
||||||
Show original content
|
Show original content
|
||||||
</button>
|
</button>
|
||||||
@ -245,7 +240,7 @@ export function NoteContent({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={translateContent}
|
onClick={translateContent}
|
||||||
className="mt-3 text-sm text-blue-500 hover:text-blue-600 border-none shadow-none focus:outline-none"
|
className="mt-3 border-none text-sm text-blue-500 shadow-none hover:text-blue-600 focus:outline-none"
|
||||||
>
|
>
|
||||||
Translate to {regionNames.of(storage.locale)}
|
Translate to {regionNames.of(storage.locale)}
|
||||||
</button>
|
</button>
|
||||||
|
@ -2,7 +2,6 @@ import { PinIcon } from "@lume/icons";
|
|||||||
import { NOSTR_MENTIONS } from "@lume/utils";
|
import { NOSTR_MENTIONS } from "@lume/utils";
|
||||||
import { ReactNode, useMemo } from "react";
|
import { ReactNode, useMemo } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
import reactStringReplace from "react-string-replace";
|
import reactStringReplace from "react-string-replace";
|
||||||
import { User } from "../../user";
|
import { User } from "../../user";
|
||||||
import { Hashtag } from "./hashtag";
|
import { Hashtag } from "./hashtag";
|
||||||
@ -64,15 +63,15 @@ export function MentionNote({
|
|||||||
(match, i) => {
|
(match, i) => {
|
||||||
const url = new URL(match);
|
const url = new URL(match);
|
||||||
return (
|
return (
|
||||||
<Link
|
<a
|
||||||
key={match + i}
|
key={match + i}
|
||||||
to={url.toString()}
|
href={url.toString()}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
className="break-p inline-block w-full truncate font-normal text-blue-500 hover:text-blue-600"
|
className="break-p inline-block w-full truncate font-normal text-blue-500 hover:text-blue-600"
|
||||||
>
|
>
|
||||||
{url.toString()}
|
{url.toString()}
|
||||||
</Link>
|
</a>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -126,12 +125,12 @@ export function MentionNote({
|
|||||||
</div>
|
</div>
|
||||||
{openable ? (
|
{openable ? (
|
||||||
<div className="flex h-10 items-center justify-between px-3">
|
<div className="flex h-10 items-center justify-between px-3">
|
||||||
<Link
|
<a
|
||||||
to={`/events/${data.id}`}
|
href={`/events/${data.id}`}
|
||||||
className="text-sm text-blue-500 hover:text-blue-600"
|
className="text-sm text-blue-500 hover:text-blue-600"
|
||||||
>
|
>
|
||||||
{t("note.showMore")}
|
{t("note.showMore")}
|
||||||
</Link>
|
</a>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="inline-flex size-6 items-center justify-center rounded-md bg-neutral-200 text-neutral-600 hover:bg-neutral-300 dark:bg-neutral-800 dark:text-neutral-400 dark:hover:bg-neutral-700"
|
className="inline-flex size-6 items-center justify-center rounded-md bg-neutral-200 text-neutral-600 hover:bg-neutral-300 dark:bg-neutral-800 dark:text-neutral-400 dark:hover:bg-neutral-700"
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { useProfile } from "@lume/ark";
|
import { useProfile } from "@lume/ark";
|
||||||
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
|
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
|
|
||||||
export function MentionUser({ pubkey }: { pubkey: string }) {
|
export function MentionUser({ pubkey }: { pubkey: string }) {
|
||||||
const { isLoading, isError, user } = useProfile(pubkey);
|
const { isLoading, isError, user } = useProfile(pubkey);
|
||||||
@ -18,12 +17,12 @@ export function MentionUser({ pubkey }: { pubkey: string }) {
|
|||||||
</DropdownMenu.Trigger>
|
</DropdownMenu.Trigger>
|
||||||
<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.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>
|
<DropdownMenu.Item asChild>
|
||||||
<Link
|
<a
|
||||||
to={`/users/${pubkey}`}
|
href={`/users/${pubkey}`}
|
||||||
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"
|
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"
|
||||||
>
|
>
|
||||||
{t("note.buttons.viewProfile")}
|
{t("note.buttons.viewProfile")}
|
||||||
</Link>
|
</a>
|
||||||
</DropdownMenu.Item>
|
</DropdownMenu.Item>
|
||||||
<DropdownMenu.Item asChild>
|
<DropdownMenu.Item asChild>
|
||||||
<button
|
<button
|
||||||
|
@ -2,14 +2,12 @@ import { HorizontalDotsIcon } from "@lume/icons";
|
|||||||
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
|
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
|
||||||
import { writeText } from "@tauri-apps/plugin-clipboard-manager";
|
import { writeText } from "@tauri-apps/plugin-clipboard-manager";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link, useNavigate } from "react-router-dom";
|
|
||||||
import { useNoteContext } from "./provider";
|
import { useNoteContext } from "./provider";
|
||||||
import { useArk } from "@lume/ark";
|
import { useArk } from "@lume/ark";
|
||||||
|
|
||||||
export function NoteMenu() {
|
export function NoteMenu() {
|
||||||
const ark = useArk();
|
const ark = useArk();
|
||||||
const event = useNoteContext();
|
const event = useNoteContext();
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@ -55,7 +53,6 @@ export function NoteMenu() {
|
|||||||
<DropdownMenu.Item asChild>
|
<DropdownMenu.Item asChild>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => navigate(`/events/${event.id}`)}
|
|
||||||
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"
|
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"
|
||||||
>
|
>
|
||||||
{t("note.menu.copyLink")}
|
{t("note.menu.copyLink")}
|
||||||
@ -80,12 +77,12 @@ export function NoteMenu() {
|
|||||||
</button>
|
</button>
|
||||||
</DropdownMenu.Item>
|
</DropdownMenu.Item>
|
||||||
<DropdownMenu.Item asChild>
|
<DropdownMenu.Item asChild>
|
||||||
<Link
|
<a
|
||||||
to={`/users/${event.pubkey}`}
|
href={`/users/${event.pubkey}`}
|
||||||
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"
|
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"
|
||||||
>
|
>
|
||||||
{t("note.menu.viewAuthor")}
|
{t("note.menu.viewAuthor")}
|
||||||
</Link>
|
</a>
|
||||||
</DropdownMenu.Item>
|
</DropdownMenu.Item>
|
||||||
<DropdownMenu.Item asChild>
|
<DropdownMenu.Item asChild>
|
||||||
<button
|
<button
|
||||||
|
@ -5,22 +5,23 @@ import { Note } from "..";
|
|||||||
export function TextNote({
|
export function TextNote({
|
||||||
event,
|
event,
|
||||||
className,
|
className,
|
||||||
}: { event: Event; className?: string }) {
|
}: {
|
||||||
|
event: Event;
|
||||||
|
className?: string;
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<Note.Provider event={event}>
|
<Note.Provider event={event}>
|
||||||
<Note.Root
|
<Note.Root className={cn("flex flex-col", className)}>
|
||||||
className={cn(
|
<div className="flex h-14 items-center justify-between px-3">
|
||||||
"flex flex-col rounded-xl bg-neutral-50 dark:bg-neutral-950",
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div className="flex items-center justify-between px-3 h-14">
|
|
||||||
<Note.User className="flex-1 pr-2" />
|
<Note.User className="flex-1 pr-2" />
|
||||||
<Note.Menu />
|
<Note.Menu />
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex gap-3">
|
||||||
|
<div className="size-10 shrink-0" />
|
||||||
|
<div className="flex-1">
|
||||||
<Note.Thread className="mb-2" />
|
<Note.Thread className="mb-2" />
|
||||||
<Note.Content className="min-w-0 px-3" />
|
<Note.Content className="min-w-0 px-3" />
|
||||||
<div className="flex items-center justify-between px-3 h-14">
|
<div className="flex h-14 items-center justify-between px-3">
|
||||||
<Note.Pin />
|
<Note.Pin />
|
||||||
<div className="inline-flex items-center gap-4">
|
<div className="inline-flex items-center gap-4">
|
||||||
<Note.Reply />
|
<Note.Reply />
|
||||||
@ -28,6 +29,8 @@ export function TextNote({
|
|||||||
<Note.Zap />
|
<Note.Zap />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</Note.Root>
|
</Note.Root>
|
||||||
</Note.Provider>
|
</Note.Provider>
|
||||||
);
|
);
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { PinIcon } from "@lume/icons";
|
import { PinIcon } from "@lume/icons";
|
||||||
import { cn } from "@lume/utils";
|
import { cn } from "@lume/utils";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
import { Note } from ".";
|
import { Note } from ".";
|
||||||
import { useNoteContext } from "./provider";
|
import { useNoteContext } from "./provider";
|
||||||
import { useArk } from "@lume/ark";
|
import { useArk } from "@lume/ark";
|
||||||
@ -28,12 +27,12 @@ export function NoteThread({ className }: { className?: string }) {
|
|||||||
<Note.Child eventId={thread.replyEventId} />
|
<Note.Child eventId={thread.replyEventId} />
|
||||||
) : null}
|
) : null}
|
||||||
<div className="inline-flex items-center justify-between">
|
<div className="inline-flex items-center justify-between">
|
||||||
<Link
|
<a
|
||||||
to={`/events/${thread?.rootEventId || thread?.replyEventId}`}
|
href={`/events/${thread?.rootEventId || thread?.replyEventId}`}
|
||||||
className="self-start text-blue-500 hover:text-blue-600"
|
className="self-start text-blue-500 hover:text-blue-600"
|
||||||
>
|
>
|
||||||
{t("note.showThread")}
|
{t("note.showThread")}
|
||||||
</Link>
|
</a>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="inline-flex size-6 items-center justify-center rounded-md bg-neutral-200 text-neutral-600 hover:bg-neutral-300 dark:bg-neutral-800 dark:text-neutral-400 dark:hover:bg-neutral-700"
|
className="inline-flex size-6 items-center justify-center rounded-md bg-neutral-200 text-neutral-600 hover:bg-neutral-300 dark:bg-neutral-800 dark:text-neutral-400 dark:hover:bg-neutral-700"
|
||||||
|
@ -9,17 +9,22 @@ export function NoteUser({ className }: { className?: string }) {
|
|||||||
return (
|
return (
|
||||||
<User.Provider pubkey={event.pubkey}>
|
<User.Provider pubkey={event.pubkey}>
|
||||||
<HoverCard.Root>
|
<HoverCard.Root>
|
||||||
<User.Root className={cn("flex items-center gap-3", className)}>
|
<User.Root
|
||||||
|
className={cn("flex items-center justify-between", className)}
|
||||||
|
>
|
||||||
|
<div className="flex gap-3">
|
||||||
<HoverCard.Trigger>
|
<HoverCard.Trigger>
|
||||||
<User.Avatar className="size-9 shrink-0 rounded-lg object-cover ring-1 ring-neutral-200/50 dark:ring-neutral-800/50" />
|
<User.Avatar className="size-11 shrink-0 rounded-xl object-cover ring-1 ring-neutral-200/50 dark:ring-neutral-800/50" />
|
||||||
</HoverCard.Trigger>
|
</HoverCard.Trigger>
|
||||||
<div className="flex h-6 flex-1 items-start justify-between gap-2">
|
<div>
|
||||||
<User.Name className="font-semibold text-neutral-950 dark:text-neutral-50" />
|
<User.Name className="font-semibold text-neutral-950 dark:text-neutral-50" />
|
||||||
|
<User.NIP05 className="text-neutral-600 dark:text-neutral-400" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<User.Time
|
<User.Time
|
||||||
time={event.created_at}
|
time={event.created_at}
|
||||||
className="text-neutral-500 dark:text-neutral-400"
|
className="text-neutral-500 dark:text-neutral-400"
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</User.Root>
|
</User.Root>
|
||||||
<HoverCard.Portal>
|
<HoverCard.Portal>
|
||||||
<HoverCard.Content
|
<HoverCard.Content
|
||||||
|
@ -2,25 +2,28 @@ import { VerifiedIcon } from "@lume/icons";
|
|||||||
import { cn, displayNpub } from "@lume/utils";
|
import { cn, displayNpub } from "@lume/utils";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { useUserContext } from "./provider";
|
import { useUserContext } from "./provider";
|
||||||
|
import { useArk } from "@lume/ark";
|
||||||
|
|
||||||
export function UserNip05({ className }: { className?: string }) {
|
export function UserNip05({ className }: { className?: string }) {
|
||||||
|
const ark = useArk();
|
||||||
const user = useUserContext();
|
const user = useUserContext();
|
||||||
|
|
||||||
const { isLoading, data: verified } = useQuery({
|
const { isLoading, data: verified } = useQuery({
|
||||||
queryKey: ["nip05", user?.profile.nip05],
|
queryKey: ["nip05", user?.profile.nip05],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
if (!user) return false;
|
if (!user.profile?.nip05) return false;
|
||||||
if (!user.profile.nip05) return false;
|
|
||||||
return false;
|
const verify = await ark.verify_nip05(user.pubkey, user.profile?.nip05);
|
||||||
|
return verify;
|
||||||
},
|
},
|
||||||
enabled: !!user,
|
enabled: !!user.profile,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!user.profile) {
|
if (!user.profile) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"h-4 w-20 bg-black/20 dark:bg-white/20 rounded animate-pulse",
|
"h-4 w-20 animate-pulse rounded bg-black/20 dark:bg-white/20",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@ -30,14 +33,14 @@ export function UserNip05({ className }: { className?: string }) {
|
|||||||
return (
|
return (
|
||||||
<div className="inline-flex items-center gap-1">
|
<div className="inline-flex items-center gap-1">
|
||||||
<p className={cn("text-sm", className)}>
|
<p className={cn("text-sm", className)}>
|
||||||
{!user?.profile.nip05
|
{!user?.profile?.nip05
|
||||||
? displayNpub(user.pubkey, 16)
|
? displayNpub(user.pubkey, 16)
|
||||||
: user?.profile.nip05?.startsWith("_@")
|
: user?.profile?.nip05?.startsWith("_@")
|
||||||
? user?.profile.nip05?.replace("_@", "")
|
? user?.profile?.nip05?.replace("_@", "")
|
||||||
: user?.profile.nip05}
|
: user?.profile?.nip05}
|
||||||
</p>
|
</p>
|
||||||
{!isLoading && verified ? (
|
{!isLoading && verified ? (
|
||||||
<VerifiedIcon className="text-teal-500 size-4" />
|
<VerifiedIcon className="size-4 text-teal-500" />
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -126,3 +126,15 @@ pub fn user_to_bech32(key: &str, relays: Vec<String>) -> Result<String, ()> {
|
|||||||
|
|
||||||
Ok(profile.to_bech32().unwrap())
|
Ok(profile.to_bech32().unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command(async)]
|
||||||
|
pub async fn verify_nip05(key: &str, nip05: &str) -> Result<bool, ()> {
|
||||||
|
let public_key = XOnlyPublicKey::from_str(key).unwrap();
|
||||||
|
let status = nip05::verify(public_key, nip05, None).await;
|
||||||
|
|
||||||
|
if let Ok(_) = status {
|
||||||
|
Ok(true)
|
||||||
|
} else {
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user