feat: polish

This commit is contained in:
2024-01-16 14:49:00 +07:00
parent 939dfd9cc1
commit 6581ffb92b
24 changed files with 944 additions and 397 deletions

View File

@@ -105,7 +105,7 @@ export class Ark {
public getCleanPubkey(pubkey: string) {
try {
let hexstring = pubkey.replace("nostr:", "").split("'")[0];
let hexstring = pubkey.replace("nostr:", "").split("'")[0].split(".")[0];
if (
hexstring.startsWith("npub1") ||
@@ -128,24 +128,10 @@ export class Ark {
public async getUserProfile(pubkey?: string) {
try {
const currentUserPubkey = this.account.pubkey;
// get clean pubkey without any special characters
let hexstring = pubkey
? pubkey.replace("nostr:", "").split("'")[0]
const hexstring = pubkey
? this.getCleanPubkey(pubkey)
: currentUserPubkey;
if (
hexstring.startsWith("npub1") ||
hexstring.startsWith("nprofile1") ||
hexstring.startsWith("naddr1")
) {
const decoded = nip19.decode(hexstring);
if (decoded.type === "nprofile") hexstring = decoded.data.pubkey;
if (decoded.type === "npub") hexstring = decoded.data;
if (decoded.type === "naddr") hexstring = decoded.data.pubkey;
}
const user = this.ndk.getUser({
pubkey: hexstring,
});
@@ -157,6 +143,7 @@ export class Ark {
if (!profile) return null;
return profile;
} catch (e) {
console.log(pubkey);
console.error(e);
}
}
@@ -164,24 +151,10 @@ export class Ark {
public async getUserContacts(pubkey?: string) {
try {
const currentUserPubkey = this.account.pubkey;
// get clean pubkey without any special characters
let hexstring = pubkey
? pubkey.replace("nostr:", "").split("'")[0]
const hexstring = pubkey
? this.getCleanPubkey(pubkey)
: currentUserPubkey;
if (
hexstring.startsWith("npub1") ||
hexstring.startsWith("nprofile1") ||
hexstring.startsWith("naddr1")
) {
const decoded = nip19.decode(hexstring);
if (decoded.type === "nprofile") hexstring = decoded.data.pubkey;
if (decoded.type === "npub") hexstring = decoded.data;
if (decoded.type === "naddr") hexstring = decoded.data.pubkey;
}
const user = this.ndk.getUser({
pubkey: hexstring,
});
@@ -252,8 +225,8 @@ export class Ark {
return [...events];
}
public async getEventById({ id }: { id: string }) {
let eventId: string = id;
public async getEventById(id: string) {
let eventId: string = id.replace("nostr:", "").split("'")[0].split(".")[0];
if (
eventId.startsWith("nevent1") ||
@@ -264,7 +237,6 @@ export class Ark {
if (decode.type === "nevent") eventId = decode.data.id;
if (decode.type === "note") eventId = decode.data;
if (decode.type === "naddr") {
return await this.ndk.fetchEvent({
kinds: [decode.data.kind],

View File

@@ -60,12 +60,12 @@ export function ColumnHeader({
</button>
</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownMenu.Content className="flex w-[200px] p-2 flex-col overflow-hidden rounded-xl border border-neutral-100 bg-neutral-50 dark:bg-neutral-950 focus:outline-none dark:border-neutral-900">
<DropdownMenu.Content className="flex w-[200px] p-2 flex-col overflow-hidden rounded-2xl bg-black/70 dark:bg-white/20 backdrop-blur-lg focus:outline-none">
<DropdownMenu.Item asChild>
<button
type="button"
onClick={refresh}
className="inline-flex items-center gap-2 px-3 text-sm font-medium rounded-lg h-9 text-neutral-700 hover:bg-blue-100 hover:text-blue-500 focus:outline-none dark:text-neutral-300 dark:hover:bg-neutral-900 dark:hover:text-neutral-50"
className="inline-flex items-center gap-2 px-3 text-sm font-medium rounded-lg h-9 text-white/50 hover:bg-black/10 hover:text-white focus:outline-none dark:text-white/50 dark:hover:bg-white/10 dark:hover:text-white"
>
<RefreshIcon className="size-5" />
Refresh
@@ -75,7 +75,7 @@ export function ColumnHeader({
<button
type="button"
onClick={moveLeft}
className="inline-flex items-center gap-2 px-3 text-sm font-medium rounded-lg h-9 text-neutral-700 hover:bg-blue-100 hover:text-blue-500 focus:outline-none dark:text-neutral-300 dark:hover:bg-neutral-900 dark:hover:text-neutral-50"
className="inline-flex items-center gap-2 px-3 text-sm font-medium rounded-lg h-9 text-white/50 hover:bg-black/10 hover:text-white focus:outline-none dark:text-white/50 dark:hover:bg-white/10 dark:hover:text-white"
>
<MoveLeftIcon className="size-5" />
Move left
@@ -85,18 +85,18 @@ export function ColumnHeader({
<button
type="button"
onClick={moveRight}
className="inline-flex items-center gap-2 px-3 text-sm font-medium rounded-lg h-9 text-neutral-700 hover:bg-blue-100 hover:text-blue-500 focus:outline-none dark:text-neutral-300 dark:hover:bg-neutral-900 dark:hover:text-neutral-50"
className="inline-flex items-center gap-2 px-3 text-sm font-medium rounded-lg h-9 text-white/50 hover:bg-black/10 hover:text-white focus:outline-none dark:text-white/50 dark:hover:bg-white/10 dark:hover:text-white"
>
<MoveRightIcon className="size-5" />
Move right
</button>
</DropdownMenu.Item>
<DropdownMenu.Separator className="h-px my-1 bg-neutral-100 dark:bg-neutral-900" />
<DropdownMenu.Separator className="h-px my-1 bg-white/10 dark:bg-black/10" />
<DropdownMenu.Item asChild>
<button
type="button"
onClick={deleteWidget}
className="inline-flex items-center gap-2 px-3 text-sm font-medium text-red-500 rounded-lg h-9 hover:bg-red-500 hover:text-red-50 focus:outline-none"
className="inline-flex items-center gap-2 px-3 text-sm font-medium text-red-300 rounded-lg h-9 hover:bg-red-500 hover:text-red-50 focus:outline-none"
>
<TrashIcon className="size-5" />
Delete

View File

@@ -1,15 +1,11 @@
import { ChatsIcon, ReplyIcon } from "@lume/icons";
import { editorAtom, editorValueAtom } from "@lume/utils";
import { ChatsIcon } from "@lume/icons";
import * as Tooltip from "@radix-ui/react-tooltip";
import { useSetAtom } from "jotai";
import { nip19 } from "nostr-tools";
import { useNavigate } from "react-router-dom";
import { useNoteContext } from "../provider";
export function NoteReply() {
const event = useNoteContext();
const setEditorValue = useSetAtom(editorValueAtom);
const setIsEditorOpen = useSetAtom(editorAtom);
const navigate = useNavigate();
return (
<Tooltip.Provider>
@@ -17,25 +13,7 @@ export function NoteReply() {
<Tooltip.Trigger asChild>
<button
type="button"
onClick={() => {
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);
}}
onClick={() => navigate(`/events/${event.id}`)}
className="inline-flex items-center justify-center group h-7 w-7 text-neutral-600 dark:text-neutral-400"
>
<ChatsIcon className="size-5 group-hover:text-blue-500" />

View File

@@ -1,5 +1,9 @@
import { LoaderIcon, RepostIcon } from "@lume/icons";
import { editorAtom, editorValueAtom } from "@lume/utils";
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
import * as Tooltip from "@radix-ui/react-tooltip";
import { useSetAtom } from "jotai";
import { nip19 } from "nostr-tools";
import { useState } from "react";
import { toast } from "sonner";
import { twMerge } from "tailwind-merge";
@@ -7,11 +11,14 @@ import { useNoteContext } from "../provider";
export function NoteRepost() {
const event = useNoteContext();
const setEditorValue = useSetAtom(editorValueAtom);
const setIsEditorOpen = useSetAtom(editorAtom);
const [loading, setLoading] = useState(false);
const [isRepost, setIsRepost] = useState(false);
const [open, setOpen] = useState(false);
const submit = async () => {
const repost = async () => {
try {
setLoading(true);
@@ -30,34 +37,79 @@ export function NoteRepost() {
}
};
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 (
<Tooltip.Provider>
<Tooltip.Root delayDuration={150}>
<Tooltip.Trigger asChild>
<button
type="button"
onClick={submit}
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={twMerge(
"size-5 group-hover:text-blue-600",
isRepost ? "text-blue-500" : "",
<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={twMerge(
"size-5 group-hover:text-blue-600",
isRepost ? "text-blue-500" : "",
)}
/>
)}
/>
)}
</button>
</Tooltip.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">
Repost
<Tooltip.Arrow className="fill-neutral-950 dark:fill-neutral-50" />
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
</Tooltip.Provider>
</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">
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-black/70 dark:bg-white/20 backdrop-blur-lg focus:outline-none">
<DropdownMenu.Item asChild>
<button
type="button"
onClick={repost}
className="inline-flex items-center gap-2 px-3 text-sm font-medium rounded-lg h-9 text-white/50 hover:bg-black/10 hover:text-white focus:outline-none dark:text-white/50 dark:hover:bg-white/10 dark:hover:text-white"
>
Repost
</button>
</DropdownMenu.Item>
<DropdownMenu.Item asChild>
<button
type="button"
onClick={quote}
className="inline-flex items-center gap-2 px-3 text-sm font-medium rounded-lg h-9 text-white/50 hover:bg-black/10 hover:text-white focus:outline-none dark:text-white/50 dark:hover:bg-white/10 dark:hover:text-white"
>
Quote
</button>
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu.Root>
);
}

View File

@@ -1,4 +1,4 @@
import { PinIcon } from "@lume/icons";
import { PinIcon, RefreshIcon } from "@lume/icons";
import { COL_TYPES } from "@lume/utils";
import { memo } from "react";
import { Link } from "react-router-dom";
@@ -18,14 +18,15 @@ export const MentionNote = memo(function MentionNote({
return (
<div
contentEditable={false}
className="w-full p-3 my-1 rounded-lg cursor-default bg-neutral-100 dark:bg-neutral-900"
className="flex items-center justify-between w-full p-3 my-1 rounded-lg cursor-default bg-neutral-100 dark:bg-neutral-900"
>
Loading
<p>Loading</p>
</div>
);
}
if (isError) {
if (isError || !data) {
console.log(eventId);
return (
<div
contentEditable={false}

View File

@@ -12,7 +12,7 @@ export const MentionUser = memo(function MentionUser({
const ark = useArk();
const cleanPubkey = ark.getCleanPubkey(pubkey);
const { isLoading, user } = useProfile(pubkey);
const { isLoading, isError, user } = useProfile(pubkey);
const { addColumn } = useColumnContext();
return (
@@ -20,7 +20,9 @@ export const MentionUser = memo(function MentionUser({
<DropdownMenu.Trigger className="text-blue-500 break-words hover:text-blue-600">
{isLoading
? "@anon"
: `@${user.name || user.display_name || user.username || "anon"}`}
: isError
? pubkey
: `@${user.name || user.displayName || user.username || "anon"}`}
</DropdownMenu.Trigger>
<DropdownMenu.Content className="left-[50px] z-50 relative flex w-[200px] flex-col overflow-hidden rounded-xl border border-neutral-200 bg-neutral-950 focus:outline-none dark:border-neutral-900">
<DropdownMenu.Item asChild>

View File

@@ -5,6 +5,7 @@ import { nip19 } from "nostr-tools";
import { type EventPointer } from "nostr-tools/lib/types/nip19";
import { useState } from "react";
import { Link } from "react-router-dom";
import { toast } from "sonner";
import { useNoteContext } from "./provider";
export function NoteMenu() {
@@ -25,6 +26,10 @@ export function NoteMenu() {
await writeText(JSON.stringify(await event.toNostrEvent()));
};
const copyNpub = async () => {
await writeText(nip19.npubEncode(event.pubkey));
};
const copyLink = async () => {
await writeText(
`https://njump.me/${nip19.neventEncode({
@@ -35,6 +40,11 @@ export function NoteMenu() {
setOpen(false);
};
const muteUser = async () => {
event.muted();
toast.info("You've muted this user");
};
return (
<DropdownMenu.Root open={open} onOpenChange={setOpen}>
<DropdownMenu.Trigger asChild>
@@ -46,12 +56,12 @@ export function NoteMenu() {
</button>
</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownMenu.Content className="flex w-[200px] p-2 flex-col overflow-hidden rounded-xl border border-neutral-200 bg-neutral-950 focus:outline-none dark:border-neutral-900">
<DropdownMenu.Content className="flex w-[200px] p-2 flex-col overflow-hidden rounded-2xl bg-black/70 dark:bg-white/20 backdrop-blur-lg focus:outline-none">
<DropdownMenu.Item asChild>
<button
type="button"
onClick={() => copyLink()}
className="inline-flex items-center h-10 px-4 text-sm text-white rounded-lg hover:bg-neutral-900 focus:outline-none"
className="inline-flex items-center gap-2 px-3 text-sm font-medium rounded-lg h-9 text-white/50 hover:bg-black/10 hover:text-white focus:outline-none dark:text-white/50 dark:hover:bg-white/10 dark:hover:text-white"
>
Copy shareable link
</button>
@@ -60,28 +70,47 @@ export function NoteMenu() {
<button
type="button"
onClick={() => copyID()}
className="inline-flex items-center h-10 px-4 text-sm text-white rounded-lg hover:bg-neutral-900 focus:outline-none"
className="inline-flex items-center gap-2 px-3 text-sm font-medium rounded-lg h-9 text-white/50 hover:bg-black/10 hover:text-white focus:outline-none dark:text-white/50 dark:hover:bg-white/10 dark:hover:text-white"
>
Copy note ID
Copy ID
</button>
</DropdownMenu.Item>
<DropdownMenu.Item asChild>
<button
type="button"
onClick={() => copyRaw()}
className="inline-flex items-center h-10 px-4 text-sm text-white rounded-lg hover:bg-neutral-900 focus:outline-none"
onClick={() => copyNpub()}
className="inline-flex items-center gap-2 px-3 text-sm font-medium rounded-lg h-9 text-white/50 hover:bg-black/10 hover:text-white focus:outline-none dark:text-white/50 dark:hover:bg-white/10 dark:hover:text-white"
>
Copy raw event
Copy npub
</button>
</DropdownMenu.Item>
<DropdownMenu.Item asChild>
<Link
to={`/users/${event.pubkey}`}
className="inline-flex items-center h-10 px-4 text-sm text-white rounded-lg hover:bg-neutral-900 focus:outline-none"
className="inline-flex items-center gap-2 px-3 text-sm font-medium rounded-lg h-9 text-white/50 hover:bg-black/10 hover:text-white focus:outline-none dark:text-white/50 dark:hover:bg-white/10 dark:hover:text-white"
>
View profile
</Link>
</DropdownMenu.Item>
<DropdownMenu.Separator className="h-px my-1 bg-white/10 dark:bg-black/10" />
<DropdownMenu.Item asChild>
<button
type="button"
onClick={() => copyRaw()}
className="inline-flex items-center gap-2 px-3 text-sm font-medium rounded-lg h-9 text-white/50 hover:bg-black/10 hover:text-white focus:outline-none dark:text-white/50 dark:hover:bg-white/10 dark:hover:text-white"
>
Copy raw event
</button>
</DropdownMenu.Item>
<DropdownMenu.Item asChild>
<button
type="button"
onClick={muteUser}
className="inline-flex items-center gap-2 px-3 text-sm font-medium text-red-300 rounded-lg h-9 hover:bg-red-500 hover:text-red-50 focus:outline-none"
>
Mute
</button>
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu.Root>

View File

@@ -54,12 +54,12 @@ export function LinkPreview({ url }: { url: string }) {
<div className="flex flex-col items-start px-3 py-3">
<div className="flex flex-col items-start gap-1 text-left">
{data.title ? (
<div className="text-base font-semibold break-all text-neutral-900 dark:text-neutral-100">
<div className="text-base font-semibold break-p text-neutral-900 dark:text-neutral-100">
{data.title}
</div>
) : null}
{data.description ? (
<div className="mb-2 text-sm break-all line-clamp-3 text-neutral-700 dark:text-neutral-400">
<div className="mb-2 text-sm break-p line-clamp-3 text-neutral-700 dark:text-neutral-400">
{data.description}
</div>
) : null}

View File

@@ -24,7 +24,7 @@ export function RepostNote({
return new NDKEvent(ark.ndk, embed);
}
const id = event.tags.find((el) => el[0] === "e")[1];
return await ark.getEventById({ id });
return await ark.getEventById(id);
} catch {
throw new Error("Failed to get repost event");
}

View File

@@ -6,7 +6,7 @@ export function useEvent(id: string) {
const { status, isLoading, isError, data } = useQuery({
queryKey: ["event", id],
queryFn: async () => {
const event = await ark.getEventById({ id });
const event = await ark.getEventById(id);
if (!event)
throw new Error(
`Cannot get event with ${id}, will be retry after 10 seconds`,

View File

@@ -90,9 +90,9 @@ export const LumeProvider = ({ children }: PropsWithChildren<object>) => {
: ["wss://brb.io/"],
);
const cacheAdapter = new NDKCacheAdapterTauri(storage);
const tauriCache = new NDKCacheAdapterTauri(storage);
const ndk = new NDK({
cacheAdapter,
cacheAdapter: tauriCache,
explicitRelayUrls,
outboxRelayUrls,
blacklistRelayUrls,