+
{isLoading ? (
-
+
) : !data.length ? (
-
+
) : (
- data.map((item) => renderItem(item))
+
+ {data.map((item) => renderItem(item))}
+
)}
{hasNextPage ? (
@@ -109,7 +108,7 @@ function Home() {
) : null}
-
+
);
diff --git a/packages/ark/src/ark.ts b/packages/ark/src/ark.ts
index 266efbe6..b95839bb 100644
--- a/packages/ark/src/ark.ts
+++ b/packages/ark/src/ark.ts
@@ -190,4 +190,16 @@ export class Ark {
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;
+ }
+ }
}
diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts
index 5a137cf9..54e1bf39 100644
--- a/packages/ui/src/index.ts
+++ b/packages/ui/src/index.ts
@@ -3,6 +3,10 @@ export * from "./user";
export * from "./note";
export * from "./column";
+// Note Primities
+export * from "./note/primitives/text";
+export * from "./note/primitives/repost";
+
// Deprecated
export * from "./routes/event";
export * from "./routes/user";
diff --git a/packages/ui/src/note/buttons/reply.tsx b/packages/ui/src/note/buttons/reply.tsx
index 1371e4ac..38fa7750 100644
--- a/packages/ui/src/note/buttons/reply.tsx
+++ b/packages/ui/src/note/buttons/reply.tsx
@@ -1,34 +1,30 @@
import { ReplyIcon } from "@lume/icons";
import * as Tooltip from "@radix-ui/react-tooltip";
import { useTranslation } from "react-i18next";
-import { useNavigate } from "react-router-dom";
import { useNoteContext } from "../provider";
export function NoteReply() {
- const event = useNoteContext();
- const navigate = useNavigate();
+ const event = useNoteContext();
+ const { t } = useTranslation();
- const { t } = useTranslation();
-
- return (
-
-
-
-
-
-
-
- {t("note.menu.viewThread")}
-
-
-
-
-
- );
+ return (
+
+
+
+
+
+
+
+ {t("note.menu.viewThread")}
+
+
+
+
+
+ );
}
diff --git a/packages/ui/src/note/child.tsx b/packages/ui/src/note/child.tsx
index 6882f4da..50bcc462 100644
--- a/packages/ui/src/note/child.tsx
+++ b/packages/ui/src/note/child.tsx
@@ -2,7 +2,6 @@ import { NOSTR_MENTIONS } from "@lume/utils";
import { nanoid } from "nanoid";
import { ReactNode, useMemo } from "react";
import { useTranslation } from "react-i18next";
-import { Link } from "react-router-dom";
import reactStringReplace from "react-string-replace";
import { User } from "../user";
import { Hashtag } from "./mentions/hashtag";
@@ -61,15 +60,15 @@ export function NoteChild({
(match, i) => {
const url = new URL(match);
return (
-
{url.toString()}
-
+
);
},
);
diff --git a/packages/ui/src/note/content.tsx b/packages/ui/src/note/content.tsx
index 0a70129c..67798091 100644
--- a/packages/ui/src/note/content.tsx
+++ b/packages/ui/src/note/content.tsx
@@ -1,20 +1,19 @@
import { useStorage } from "@lume/storage";
import { Kind } from "@lume/types";
import {
- AUDIOS,
- IMAGES,
- NOSTR_EVENTS,
- NOSTR_MENTIONS,
- VIDEOS,
- canPreview,
- cn,
- regionNames,
+ AUDIOS,
+ IMAGES,
+ NOSTR_EVENTS,
+ NOSTR_MENTIONS,
+ VIDEOS,
+ canPreview,
+ cn,
+ regionNames,
} from "@lume/utils";
import { fetch } from "@tauri-apps/plugin-http";
import getUrls from "get-urls";
import { nanoid } from "nanoid";
import { ReactNode, useMemo, useState } from "react";
-import { Link } from "react-router-dom";
import reactStringReplace from "react-string-replace";
import { toast } from "sonner";
import { stripHtml } from "string-strip-html";
@@ -27,230 +26,226 @@ import { LinkPreview } from "./preview/link";
import { VideoPreview } from "./preview/video";
import { useNoteContext } from "./provider";
-export function NoteContent({
- className,
-}: {
- className?: string;
-}) {
- const storage = useStorage();
- const event = useNoteContext();
+export function NoteContent({ className }: { className?: string }) {
+ const storage = useStorage();
+ const event = useNoteContext();
- const [content, setContent] = useState(event.content);
- const [translate, setTranslate] = useState({
- translatable: false,
- translated: false,
- });
+ const [content, setContent] = useState(event.content);
+ const [translate, setTranslate] = useState({
+ translatable: false,
+ translated: false,
+ });
- const richContent = useMemo(() => {
- if (event.kind !== Kind.Text) return content;
+ const richContent = useMemo(() => {
+ if (event.kind !== Kind.Text) return content;
- let parsedContent: string | ReactNode[] = stripHtml(
- content.replace(/\n{2,}\s*/g, "\n"),
- ).result;
- let linkPreview: string = undefined;
- let images: string[] = [];
- let videos: string[] = [];
- let audios: string[] = [];
- let events: string[] = [];
+ let parsedContent: string | ReactNode[] = stripHtml(
+ content.replace(/\n{2,}\s*/g, "\n"),
+ ).result;
+ let linkPreview: string = undefined;
+ let images: string[] = [];
+ let videos: string[] = [];
+ let audios: string[] = [];
+ let events: string[] = [];
- const text = parsedContent;
- const words = text.split(/( |\n)/);
- const urls = [...getUrls(text)];
+ const text = parsedContent;
+ const words = text.split(/( |\n)/);
+ const urls = [...getUrls(text)];
- if (storage.settings.media && !storage.settings.lowPower) {
- images = urls.filter((word) =>
- IMAGES.some((el) => {
- const url = new URL(word);
- const extension = url.pathname.split(".")[1];
- if (extension === el) return true;
- return false;
- }),
- );
- videos = urls.filter((word) =>
- VIDEOS.some((el) => {
- const url = new URL(word);
- const extension = url.pathname.split(".")[1];
- if (extension === el) return true;
- return false;
- }),
- );
- audios = urls.filter((word) =>
- AUDIOS.some((el) => {
- const url = new URL(word);
- const extension = url.pathname.split(".")[1];
- if (extension === el) return true;
- return false;
- }),
- );
- }
+ if (storage.settings.media && !storage.settings.lowPower) {
+ images = urls.filter((word) =>
+ IMAGES.some((el) => {
+ const url = new URL(word);
+ const extension = url.pathname.split(".")[1];
+ if (extension === el) return true;
+ return false;
+ }),
+ );
+ videos = urls.filter((word) =>
+ VIDEOS.some((el) => {
+ const url = new URL(word);
+ const extension = url.pathname.split(".")[1];
+ if (extension === el) return true;
+ return false;
+ }),
+ );
+ audios = urls.filter((word) =>
+ AUDIOS.some((el) => {
+ const url = new URL(word);
+ const extension = url.pathname.split(".")[1];
+ if (extension === el) return true;
+ return false;
+ }),
+ );
+ }
- events = words.filter((word) =>
- NOSTR_EVENTS.some((el) => word.startsWith(el)),
- );
+ events = words.filter((word) =>
+ NOSTR_EVENTS.some((el) => word.startsWith(el)),
+ );
- const hashtags = words.filter((word) => word.startsWith("#"));
- const mentions = words.filter((word) =>
- NOSTR_MENTIONS.some((el) => word.startsWith(el)),
- );
+ const hashtags = words.filter((word) => word.startsWith("#"));
+ const mentions = words.filter((word) =>
+ NOSTR_MENTIONS.some((el) => word.startsWith(el)),
+ );
- try {
- if (images.length) {
- for (const image of images) {
- parsedContent = reactStringReplace(
- parsedContent,
- image,
- (match, i) =>
,
- );
- }
- }
+ try {
+ if (images.length) {
+ for (const image of images) {
+ parsedContent = reactStringReplace(
+ parsedContent,
+ image,
+ (match, i) =>
,
+ );
+ }
+ }
- if (videos.length) {
- for (const video of videos) {
- parsedContent = reactStringReplace(
- parsedContent,
- video,
- (match, i) =>
,
- );
- }
- }
+ if (videos.length) {
+ for (const video of videos) {
+ parsedContent = reactStringReplace(
+ parsedContent,
+ video,
+ (match, i) =>
,
+ );
+ }
+ }
- if (audios.length) {
- for (const audio of audios) {
- parsedContent = reactStringReplace(
- parsedContent,
- audio,
- (match, i) =>
,
- );
- }
- }
+ if (audios.length) {
+ for (const audio of audios) {
+ parsedContent = reactStringReplace(
+ parsedContent,
+ audio,
+ (match, i) =>
,
+ );
+ }
+ }
- if (hashtags.length) {
- for (const hashtag of hashtags) {
- const regex = new RegExp(`(|^)${hashtag}\\b`, "g");
- parsedContent = reactStringReplace(parsedContent, regex, () => {
- if (storage.settings.hashtag)
- return
;
- return null;
- });
- }
- }
+ if (hashtags.length) {
+ for (const hashtag of hashtags) {
+ const regex = new RegExp(`(|^)${hashtag}\\b`, "g");
+ parsedContent = reactStringReplace(parsedContent, regex, () => {
+ if (storage.settings.hashtag)
+ return
;
+ return null;
+ });
+ }
+ }
- if (events.length) {
- for (const event of events) {
- parsedContent = reactStringReplace(
- parsedContent,
- event,
- (match, i) =>
,
- );
- }
- }
+ if (events.length) {
+ for (const event of events) {
+ parsedContent = reactStringReplace(
+ parsedContent,
+ event,
+ (match, i) =>
,
+ );
+ }
+ }
- if (mentions.length) {
- for (const mention of mentions) {
- parsedContent = reactStringReplace(
- parsedContent,
- mention,
- (match, i) =>
,
- );
- }
- }
+ if (mentions.length) {
+ for (const mention of mentions) {
+ parsedContent = reactStringReplace(
+ parsedContent,
+ mention,
+ (match, i) =>
,
+ );
+ }
+ }
- parsedContent = reactStringReplace(
- parsedContent,
- /(https?:\/\/\S+)/g,
- (match, i) => {
- const url = new URL(match);
+ parsedContent = reactStringReplace(
+ parsedContent,
+ /(https?:\/\/\S+)/g,
+ (match, i) => {
+ const url = new URL(match);
- if (!linkPreview && canPreview(match)) {
- linkPreview = match;
- return
;
- }
+ if (!linkPreview && canPreview(match)) {
+ linkPreview = match;
+ return
;
+ }
- return (
-
- {url.toString()}
-
- );
- },
- );
+ return (
+
+ {url.toString()}
+
+ );
+ },
+ );
- parsedContent = reactStringReplace(parsedContent, "\n", () => {
- return
;
- });
+ parsedContent = reactStringReplace(parsedContent, "\n", () => {
+ return
;
+ });
- if (typeof parsedContent[0] === "string") {
- parsedContent[0] = parsedContent[0].trimStart();
- }
+ if (typeof parsedContent[0] === "string") {
+ parsedContent[0] = parsedContent[0].trimStart();
+ }
- return parsedContent;
- } catch (e) {
- console.warn(event.id, `[parser] parse failed: ${e}`);
- return parsedContent;
- }
- }, [content]);
+ return parsedContent;
+ } catch (e) {
+ console.warn(event.id, `[parser] parse failed: ${e}`);
+ return parsedContent;
+ }
+ }, [content]);
- const translateContent = async () => {
- try {
- if (!translate.translatable) return;
+ const translateContent = async () => {
+ try {
+ if (!translate.translatable) return;
- const res = await fetch("https://translate.nostr.wine/translate", {
- method: "POST",
- body: JSON.stringify({
- q: event.content,
- target: storage.locale.slice(0, 2),
- api_key: storage.settings.translateApiKey,
- }),
- headers: { "Content-Type": "application/json" },
- });
+ const res = await fetch("https://translate.nostr.wine/translate", {
+ method: "POST",
+ body: JSON.stringify({
+ q: event.content,
+ target: storage.locale.slice(0, 2),
+ api_key: storage.settings.translateApiKey,
+ }),
+ headers: { "Content-Type": "application/json" },
+ });
- if (!res.ok)
- toast.error(
- "Cannot connect to translate service, please try again later.",
- );
+ if (!res.ok)
+ toast.error(
+ "Cannot connect to translate service, please try again later.",
+ );
- const data = await res.json();
+ const data = await res.json();
- setContent(data.translatedText);
- setTranslate((state) => ({ ...state, translated: true }));
- } catch (e) {
- console.error("translate api: ", String(e));
- }
- };
+ setContent(data.translatedText);
+ setTranslate((state) => ({ ...state, translated: true }));
+ } catch (e) {
+ console.error("translate api: ", String(e));
+ }
+ };
- if (event.kind !== Kind.Text) {
- return
;
- }
+ if (event.kind !== Kind.Text) {
+ return
;
+ }
- return (
-
-
- {richContent}
-
- {storage.settings.translation && translate.translatable ? (
- translate.translated ? (
-
- ) : (
-
- )
- ) : null}
-
- );
+ return (
+
+
+ {richContent}
+
+ {storage.settings.translation && translate.translatable ? (
+ translate.translated ? (
+
+ ) : (
+
+ )
+ ) : null}
+
+ );
}
diff --git a/packages/ui/src/note/mentions/note.tsx b/packages/ui/src/note/mentions/note.tsx
index 637bdbcf..dabf035f 100644
--- a/packages/ui/src/note/mentions/note.tsx
+++ b/packages/ui/src/note/mentions/note.tsx
@@ -2,7 +2,6 @@ import { PinIcon } from "@lume/icons";
import { NOSTR_MENTIONS } from "@lume/utils";
import { ReactNode, useMemo } from "react";
import { useTranslation } from "react-i18next";
-import { Link } from "react-router-dom";
import reactStringReplace from "react-string-replace";
import { User } from "../../user";
import { Hashtag } from "./hashtag";
@@ -64,15 +63,15 @@ export function MentionNote({
(match, i) => {
const url = new URL(match);
return (
-
{url.toString()}
-
+
);
},
);
@@ -126,12 +125,12 @@ export function MentionNote({
{openable ? (