feat: move nwc to settings

This commit is contained in:
2024-01-15 15:33:05 +07:00
parent 3f1218e7bc
commit 3301af5cbb
18 changed files with 378 additions and 448 deletions

View File

@@ -103,13 +103,35 @@ export class Ark {
}
}
public getCleanPubkey(pubkey: string) {
try {
let hexstring = pubkey.replace("nostr:", "").split("'")[0];
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;
}
return hexstring;
} catch (e) {
console.log(e);
}
}
public async getUserProfile(pubkey?: string) {
try {
const currentUserPubkey = this.account.pubkey;
// get clean pubkey without any special characters
let hexstring = pubkey
? pubkey.replace(/[^a-zA-Z0-9]/g, "").replace("nostr:", "")
? pubkey.replace("nostr:", "").split("'")[0]
: currentUserPubkey;
if (
@@ -135,7 +157,7 @@ export class Ark {
if (!profile) return null;
return profile;
} catch (e) {
throw new Error(e);
console.error(e);
}
}
@@ -145,7 +167,7 @@ export class Ark {
// get clean pubkey without any special characters
let hexstring = pubkey
? pubkey.replace(/[^a-zA-Z0-9]/g, "").replace("nostr:", "")
? pubkey.replace("nostr:", "").split("'")[0]
: currentUserPubkey;
if (
@@ -164,14 +186,16 @@ export class Ark {
pubkey: hexstring,
});
const contacts = [...(await user.follows())].map((user) => user.pubkey);
const contacts = [...(await user.follows(undefined, false))].map(
(user) => user.pubkey,
);
if (!pubkey || pubkey === this.account.pubkey)
this.account.contacts = contacts;
return contacts;
} catch (e) {
throw new Error(e);
console.error(e);
}
}
@@ -182,7 +206,7 @@ export class Ark {
});
return await user.relayList();
} catch (e) {
throw new Error(e);
console.error(e);
}
}

View File

@@ -1,4 +1,4 @@
import { ReplyIcon } from "@lume/icons";
import { ChatsIcon, ReplyIcon } from "@lume/icons";
import { editorAtom, editorValueAtom } from "@lume/utils";
import * as Tooltip from "@radix-ui/react-tooltip";
import { useSetAtom } from "jotai";
@@ -38,7 +38,7 @@ export function NoteReply() {
}}
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" />
<ChatsIcon className="size-5 group-hover:text-blue-500" />
</button>
</Tooltip.Trigger>
<Tooltip.Portal>

View File

@@ -30,66 +30,53 @@ export function NoteChild({
NOSTR_MENTIONS.some((el) => word.startsWith(el)),
);
if (hashtags.length) {
for (const hashtag of hashtags) {
parsedContent = reactStringReplace(
parsedContent,
hashtag,
(match, i) => {
return <Hashtag key={match + i} tag={hashtag} />;
},
);
}
}
if (mentions.length) {
for (const mention of mentions) {
const address = mention
.replace("nostr:", "")
.replace("@", "")
.replace(/[^a-zA-Z0-9]/g, "");
const decoded = nip19.decode(address);
if (decoded.type === "npub") {
try {
if (hashtags.length) {
for (const hashtag of hashtags) {
parsedContent = reactStringReplace(
parsedContent,
mention,
(match, i) => <MentionUser key={match + i} pubkey={decoded.data} />,
);
}
if (decoded.type === "nprofile" || decoded.type === "naddr") {
parsedContent = reactStringReplace(
parsedContent,
mention,
(match, i) => (
<MentionUser key={match + i} pubkey={decoded.data.pubkey} />
),
hashtag,
(match, i) => {
return <Hashtag key={match + i} tag={hashtag} />;
},
);
}
}
if (mentions.length) {
for (const mention of mentions) {
parsedContent = reactStringReplace(
parsedContent,
mention,
(match, i) => <MentionUser key={match + i} pubkey={mention} />,
);
}
}
parsedContent = reactStringReplace(
parsedContent,
/(https?:\/\/\S+)/g,
(match, i) => {
const url = new URL(match);
return (
<Link
key={match + i}
to={url.toString()}
target="_blank"
rel="noreferrer"
className="break-all font-normal text-blue-500 hover:text-blue-600"
>
{url.toString()}
</Link>
);
},
);
return parsedContent;
} catch (e) {
console.log(e);
return parsedContent;
}
parsedContent = reactStringReplace(
parsedContent,
/(https?:\/\/\S+)/g,
(match, i) => {
const url = new URL(match);
return (
<Link
key={match + i}
to={url.toString()}
target="_blank"
rel="noreferrer"
className="break-all font-normal text-blue-500 hover:text-blue-600"
>
{url.toString()}
</Link>
);
},
);
return parsedContent;
}, [data]);
if (isLoading) {

View File

@@ -140,60 +140,21 @@ export function NoteContent({
if (events.length) {
for (const event of events) {
const address = event
.replace("nostr:", "")
.replace(/[^a-zA-Z0-9]/g, "");
const decoded = nip19.decode(address);
if (decoded.type === "note") {
parsedContent = reactStringReplace(
parsedContent,
event,
(match, i) => (
<MentionNote key={match + i} eventId={decoded.data} />
),
);
}
if (decoded.type === "nevent") {
parsedContent = reactStringReplace(
parsedContent,
event,
(match, i) => (
<MentionNote key={match + i} eventId={decoded.data.id} />
),
);
}
parsedContent = reactStringReplace(
parsedContent,
event,
(match, i) => <MentionNote key={match + i} eventId={event} />,
);
}
}
if (mentions.length) {
for (const mention of mentions) {
const address = mention
.replace("nostr:", "")
.replace("@", "")
.replace(/[^a-zA-Z0-9]/g, "");
const decoded = nip19.decode(address);
if (decoded.type === "npub") {
parsedContent = reactStringReplace(
parsedContent,
mention,
(match, i) => (
<MentionUser key={match + i} pubkey={decoded.data} />
),
);
}
if (decoded.type === "nprofile" || decoded.type === "naddr") {
parsedContent = reactStringReplace(
parsedContent,
mention,
(match, i) => (
<MentionUser key={match + i} pubkey={decoded.data.pubkey} />
),
);
}
parsedContent = reactStringReplace(
parsedContent,
mention,
(match, i) => <MentionUser key={match + i} pubkey={mention} />,
);
}
}

View File

@@ -2,24 +2,30 @@ import { COL_TYPES } from "@lume/utils";
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
import { memo } from "react";
import { Link } from "react-router-dom";
import { useArk } from "../../../hooks/useArk";
import { useProfile } from "../../../hooks/useProfile";
import { useColumnContext } from "../../column/provider";
export const MentionUser = memo(function MentionUser({
pubkey,
}: { pubkey: string }) {
const { user } = useProfile(pubkey);
const ark = useArk();
const cleanPubkey = ark.getCleanPubkey(pubkey);
const { isLoading, user } = useProfile(pubkey);
const { addColumn } = useColumnContext();
return (
<DropdownMenu.Root>
<DropdownMenu.Trigger className="text-blue-500 break-words hover:text-blue-600">
{`@${user?.name || user?.displayName || user?.username || "anon"}`}
{isLoading
? "@anon"
: `@${user.name || user.display_name || 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>
<Link
to={`/users/${pubkey}`}
to={`/users/${cleanPubkey}`}
className="inline-flex items-center h-10 px-4 text-sm text-white hover:bg-neutral-900 focus:outline-none"
>
View profile
@@ -31,8 +37,8 @@ export const MentionUser = memo(function MentionUser({
onClick={async () =>
await addColumn({
kind: COL_TYPES.user,
title: user?.name || user?.displayName || "",
content: pubkey,
title: user?.name || user?.displayName || "Profile",
content: cleanPubkey,
})
}
className="inline-flex items-center h-10 px-4 text-sm text-white hover:bg-neutral-900 focus:outline-none"

View File

@@ -0,0 +1,37 @@
import { cn } from "@lume/utils";
import { useUserContext } from "./provider";
export function UserAbout({ className }: { className?: string }) {
const user = useUserContext();
if (!user) {
return (
<>
<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,
)}
/>
</>
);
}
return (
<div className={cn("select-text break-p", className)}>
{user.about || user.bio}
</div>
);
}

View File

@@ -1,3 +1,4 @@
import { LoaderIcon } from "@lume/icons";
import { cn } from "@lume/utils";
import { useEffect, useState } from "react";
import { useArk } from "../../hooks/useArk";
@@ -7,6 +8,8 @@ export function UserFollowButton({
className,
}: { target: string; className?: string }) {
const ark = useArk();
const [loading, setLoading] = useState(false);
const [followed, setFollowed] = useState(false);
const toggleFollow = async () => {
@@ -21,15 +24,27 @@ export function UserFollowButton({
useEffect(() => {
async function status() {
setLoading(true);
const contacts = await ark.getUserContacts();
if (contacts?.includes(target)) setFollowed(true);
if (contacts?.includes(target)) {
setFollowed(true);
}
setLoading(false);
}
status();
}, []);
return (
<button type="button" onClick={toggleFollow} className={cn("", className)}>
{followed ? "Unfollow" : "Follow"}
{loading ? (
<LoaderIcon className="size-4 animate-spin" />
) : followed ? (
"Unfollow"
) : (
"Follow"
)}
</button>
);
}

View File

@@ -1,3 +1,4 @@
import { UserAbout } from "./about";
import { UserAvatar } from "./avatar";
import { UserFollowButton } from "./followButton";
import { UserName } from "./name";
@@ -13,5 +14,6 @@ export const User = {
Name: UserName,
NIP05: UserNip05,
Time: UserTime,
About: UserAbout,
Button: UserFollowButton,
};

View File

@@ -12,6 +12,7 @@ import NDK, {
NDKUser,
} from "@nostr-dev-kit/ndk";
import { useQueryClient } from "@tanstack/react-query";
import { message } from "@tauri-apps/plugin-dialog";
import { fetch } from "@tauri-apps/plugin-http";
import Linkify from "linkify-react";
import { normalizeRelayUrlSet } from "nostr-fetch";
@@ -133,6 +134,8 @@ export const LumeProvider = ({ children }: PropsWithChildren<object>) => {
}
async function initArk() {
if (!ndk) await message("Something wrong!", { type: "error" });
// ark utils
const ark = new Ark({ ndk, account: storage.currentUser });