diff --git a/apps/desktop2/src/components/note/buttons/repost.tsx b/apps/desktop2/src/components/note/buttons/repost.tsx
index 66730cce..b8e840bf 100644
--- a/apps/desktop2/src/components/note/buttons/repost.tsx
+++ b/apps/desktop2/src/components/note/buttons/repost.tsx
@@ -1,24 +1,23 @@
-import { QuoteIcon, RepostIcon } from "@lume/icons";
+import { RepostIcon } from "@lume/icons";
import { cn } from "@lume/utils";
-import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
-import * as Tooltip from "@radix-ui/react-tooltip";
-import { useState } from "react";
+import { useCallback, useState } from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import { Spinner } from "@lume/ui";
import { useNoteContext } from "../provider";
import { LumeWindow } from "@lume/system";
+import { Menu, MenuItem } from "@tauri-apps/api/menu";
export function NoteRepost({ large = false }: { large?: boolean }) {
const event = useNoteContext();
- const [t] = useTranslation();
const [loading, setLoading] = useState(false);
const [isRepost, setIsRepost] = useState(false);
const repost = async () => {
+ if (isRepost) return;
+
try {
- if (isRepost) return;
setLoading(true);
// repost
@@ -30,71 +29,50 @@ export function NoteRepost({ large = false }: { large?: boolean }) {
// notify
toast.success("You've reposted this post successfully");
- } catch (e) {
+ } catch {
setLoading(false);
toast.error("Repost failed, try again later");
}
};
+ const showContextMenu = useCallback(async (e: React.MouseEvent) => {
+ e.preventDefault();
+
+ const menuItems = await Promise.all([
+ MenuItem.new({
+ text: "Quote",
+ action: async () => repost(),
+ }),
+ MenuItem.new({
+ text: "Repost",
+ action: () => LumeWindow.openEditor(null, event.id),
+ }),
+ ]);
+
+ const menu = await Menu.new({
+ items: menuItems,
+ });
+
+ await menu.popup().catch((e) => console.error(e));
+ }, []);
+
return (
-
-
-
-
-
-
-
-
-
-
- {t("note.buttons.repost")}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
);
}
diff --git a/apps/desktop2/src/components/note/menu.tsx b/apps/desktop2/src/components/note/menu.tsx
index a0728a41..4ab542cd 100644
--- a/apps/desktop2/src/components/note/menu.tsx
+++ b/apps/desktop2/src/components/note/menu.tsx
@@ -1,100 +1,62 @@
import { HorizontalDotsIcon } from "@lume/icons";
-import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
import { writeText } from "@tauri-apps/plugin-clipboard-manager";
-import { useTranslation } from "react-i18next";
import { useNoteContext } from "./provider";
-import { LumeWindow } from "@lume/system";
+import { useCallback } from "react";
+import { Menu, MenuItem, PredefinedMenuItem } from "@tauri-apps/api/menu";
export function NoteMenu() {
- const { t } = useTranslation();
const event = useNoteContext();
- const copyID = async () => {
- await writeText(await event.idAsBech32());
- };
+ const showContextMenu = useCallback(async (e: React.MouseEvent) => {
+ e.preventDefault();
- const copyRaw = async () => {
- await writeText(JSON.stringify(event));
- };
+ const menuItems = await Promise.all([
+ MenuItem.new({
+ text: "Copy Sharable Link",
+ action: async () => {
+ const eventId = await event.idAsBech32();
+ await writeText(`https://njump.me/${eventId}`);
+ },
+ }),
+ MenuItem.new({
+ text: "Copy Event ID",
+ action: async () => {
+ const eventId = await event.idAsBech32();
+ await writeText(eventId);
+ },
+ }),
+ MenuItem.new({
+ text: "Copy Public Key",
+ action: async () => {
+ const pubkey = await event.pubkeyAsBech32();
+ await writeText(pubkey);
+ },
+ }),
+ PredefinedMenuItem.new({ item: "Separator" }),
+ MenuItem.new({
+ text: "Copy Raw Event",
+ action: async () => {
+ event.meta = undefined;
+ const raw = JSON.stringify(event);
+ await writeText(raw);
+ },
+ }),
+ ]);
- const copyNpub = async () => {
- await writeText(await event.pubkeyAsBech32());
- };
+ const menu = await Menu.new({
+ items: menuItems,
+ });
- const copyLink = async () => {
- await writeText(`https://njump.me/${await event.idAsBech32()}`);
- };
+ await menu.popup().catch((e) => console.error(e));
+ }, []);
return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
);
}
diff --git a/apps/desktop2/src/routes/editor/-components/mention.tsx b/apps/desktop2/src/routes/editor/-components/mention.tsx
deleted file mode 100644
index 09c8b7b4..00000000
--- a/apps/desktop2/src/routes/editor/-components/mention.tsx
+++ /dev/null
@@ -1,88 +0,0 @@
-import { MentionIcon } from "@lume/icons";
-import { cn, insertMention } from "@lume/utils";
-import * as Tooltip from "@radix-ui/react-tooltip";
-import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
-import { useEffect, useState } from "react";
-import { useSlateStatic } from "slate-react";
-import type { Contact } from "@lume/types";
-import { toast } from "sonner";
-import { User } from "@/components/user";
-import { NostrAccount, NostrQuery } from "@lume/system";
-
-export function MentionButton({ className }: { className?: string }) {
- const editor = useSlateStatic();
- const [contacts, setContacts] = useState([]);
-
- const select = async (user: string) => {
- try {
- const metadata = await NostrQuery.getProfile(user);
- const contact: Contact = { pubkey: user, profile: metadata };
-
- insertMention(editor, contact);
- } catch (e) {
- toast.error(String(e));
- }
- };
-
- useEffect(() => {
- async function getContacts() {
- const data = await NostrAccount.getContactList();
- setContacts(data);
- }
-
- getContacts();
- }, []);
-
- return (
-
-
-
-
-
-
-
-
-
-
- Mention
-
-
-
-
-
-
-
- {contacts.length < 1 ? (
-
-
Contact List is empty.
-
- ) : (
- contacts.map((contact) => (
- select(contact)}
- className="flex items-center px-2 shrink-0 h-11 hover:bg-white/10"
- >
-
-
-
-
-
-
-
- ))
- )}
-
-
-
-
- );
-}
diff --git a/packages/system/src/commands.ts b/packages/system/src/commands.ts
index 01f2bd63..e7313866 100644
--- a/packages/system/src/commands.ts
+++ b/packages/system/src/commands.ts
@@ -100,22 +100,6 @@ try {
else return { status: "error", error: e as any };
}
},
-async eventToBech32(id: string, relays: string[]) : Promise> {
-try {
- return { status: "ok", data: await TAURI_INVOKE("event_to_bech32", { id, relays }) };
-} catch (e) {
- if(e instanceof Error) throw e;
- else return { status: "error", error: e as any };
-}
-},
-async userToBech32(key: string, relays: string[]) : Promise> {
-try {
- return { status: "ok", data: await TAURI_INVOKE("user_to_bech32", { key, relays }) };
-} catch (e) {
- if(e instanceof Error) throw e;
- else return { status: "error", error: e as any };
-}
-},
async verifyNip05(key: string, nip05: string) : Promise> {
try {
return { status: "ok", data: await TAURI_INVOKE("verify_nip05", { key, nip05 }) };
@@ -356,6 +340,22 @@ try {
else return { status: "error", error: e as any };
}
},
+async eventToBech32(id: string) : Promise> {
+try {
+ return { status: "ok", data: await TAURI_INVOKE("event_to_bech32", { id }) };
+} catch (e) {
+ if(e instanceof Error) throw e;
+ else return { status: "error", error: e as any };
+}
+},
+async userToBech32(user: string) : Promise> {
+try {
+ return { status: "ok", data: await TAURI_INVOKE("user_to_bech32", { user }) };
+} catch (e) {
+ if(e instanceof Error) throw e;
+ else return { status: "error", error: e as any };
+}
+},
async showInFolder(path: string) : Promise {
await TAURI_INVOKE("show_in_folder", { path });
},
diff --git a/packages/system/src/event.ts b/packages/system/src/event.ts
index e889e03c..a86f885b 100644
--- a/packages/system/src/event.ts
+++ b/packages/system/src/event.ts
@@ -150,7 +150,7 @@ export class LumeEvent {
}
public async idAsBech32() {
- const query = await commands.eventToBech32(this.id, []);
+ const query = await commands.eventToBech32(this.id);
if (query.status === "ok") {
return query.data;
@@ -160,7 +160,7 @@ export class LumeEvent {
}
public async pubkeyAsBech32() {
- const query = await commands.userToBech32(this.pubkey, []);
+ const query = await commands.userToBech32(this.pubkey);
if (query.status === "ok") {
return query.data;
diff --git a/src-tauri/capabilities/main.json b/src-tauri/capabilities/main.json
index 34462be5..534b8867 100644
--- a/src-tauri/capabilities/main.json
+++ b/src-tauri/capabilities/main.json
@@ -59,6 +59,8 @@
"fs:allow-read-file",
"theme:allow-set-theme",
"theme:allow-get-theme",
+ "menu:allow-new",
+ "menu:allow-popup",
"http:default",
"shell:allow-open",
{
diff --git a/src-tauri/gen/schemas/capabilities.json b/src-tauri/gen/schemas/capabilities.json
index d74af3be..fa5faed2 100644
--- a/src-tauri/gen/schemas/capabilities.json
+++ b/src-tauri/gen/schemas/capabilities.json
@@ -1 +1 @@
-{"desktop-capability":{"identifier":"desktop-capability","description":"Capability for the desktop","local":true,"windows":["main","panel","splash","settings","search","nwc","activity","zap-*","event-*","user-*","editor-*","column-*"],"permissions":["path:default","event:default","window:default","app:default","resources:default","menu:default","tray:default","notification:allow-is-permission-granted","notification:allow-request-permission","notification:default","os:allow-locale","os:allow-platform","os:allow-os-type","updater:default","updater:allow-check","updater:allow-download-and-install","window:allow-start-dragging","window:allow-create","window:allow-close","window:allow-set-focus","window:allow-center","window:allow-minimize","window:allow-maximize","window:allow-set-size","window:allow-set-focus","window:allow-start-dragging","decorum:allow-show-snap-overlay","clipboard-manager:allow-write-text","clipboard-manager:allow-read-text","webview:allow-create-webview-window","webview:allow-create-webview","webview:allow-set-webview-size","webview:allow-set-webview-position","webview:allow-webview-close","dialog:allow-open","dialog:allow-ask","dialog:allow-message","process:allow-restart","fs:allow-read-file","theme:allow-set-theme","theme:allow-get-theme","http:default","shell:allow-open",{"identifier":"http:default","allow":[{"url":"http://**/"},{"url":"https://**/"}]},{"identifier":"fs:allow-read-text-file","allow":[{"path":"$RESOURCE/locales/*"},{"path":"$RESOURCE/resources/*"}]}],"platforms":["linux","macOS","windows"]}}
\ No newline at end of file
+{"desktop-capability":{"identifier":"desktop-capability","description":"Capability for the desktop","local":true,"windows":["main","panel","splash","settings","search","nwc","activity","zap-*","event-*","user-*","editor-*","column-*"],"permissions":["path:default","event:default","window:default","app:default","resources:default","menu:default","tray:default","notification:allow-is-permission-granted","notification:allow-request-permission","notification:default","os:allow-locale","os:allow-platform","os:allow-os-type","updater:default","updater:allow-check","updater:allow-download-and-install","window:allow-start-dragging","window:allow-create","window:allow-close","window:allow-set-focus","window:allow-center","window:allow-minimize","window:allow-maximize","window:allow-set-size","window:allow-set-focus","window:allow-start-dragging","decorum:allow-show-snap-overlay","clipboard-manager:allow-write-text","clipboard-manager:allow-read-text","webview:allow-create-webview-window","webview:allow-create-webview","webview:allow-set-webview-size","webview:allow-set-webview-position","webview:allow-webview-close","dialog:allow-open","dialog:allow-ask","dialog:allow-message","process:allow-restart","fs:allow-read-file","theme:allow-set-theme","theme:allow-get-theme","menu:allow-new","menu:allow-popup","http:default","shell:allow-open",{"identifier":"http:default","allow":[{"url":"http://**/"},{"url":"https://**/"}]},{"identifier":"fs:allow-read-text-file","allow":[{"path":"$RESOURCE/locales/*"},{"path":"$RESOURCE/resources/*"}]}],"platforms":["linux","macOS","windows"]}}
\ No newline at end of file
diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs
index a9b27017..82c891e1 100644
--- a/src-tauri/src/main.rs
+++ b/src-tauri/src/main.rs
@@ -57,8 +57,6 @@ fn main() {
nostr::keys::get_private_key,
nostr::keys::connect_remote_account,
nostr::keys::load_account,
- nostr::keys::event_to_bech32,
- nostr::keys::user_to_bech32,
nostr::keys::verify_nip05,
nostr::metadata::get_current_user_profile,
nostr::metadata::get_profile,
@@ -89,6 +87,8 @@ fn main() {
nostr::event::publish,
nostr::event::reply,
nostr::event::repost,
+ nostr::event::event_to_bech32,
+ nostr::event::user_to_bech32,
commands::folder::show_in_folder,
commands::window::create_column,
commands::window::close_column,
diff --git a/src-tauri/src/nostr/event.rs b/src-tauri/src/nostr/event.rs
index d035b969..c8e4bb56 100644
--- a/src-tauri/src/nostr/event.rs
+++ b/src-tauri/src/nostr/event.rs
@@ -91,7 +91,7 @@ pub async fn get_event_from(
return Err(err.to_string());
}
- if (client.connect_relay(relay_hint).await).is_ok() {
+ if client.connect_relay(relay_hint).await.is_ok() {
match event_id {
Some(id) => {
match client
@@ -522,3 +522,79 @@ pub async fn repost(raw: &str, state: State<'_, Nostr>) -> Result Err(err.to_string()),
}
}
+
+#[tauri::command]
+#[specta::specta]
+pub async fn event_to_bech32(id: &str, state: State<'_, Nostr>) -> Result {
+ let client = &state.client;
+
+ let event_id = match EventId::from_hex(id) {
+ Ok(id) => id,
+ Err(_) => return Err("ID is not valid.".into()),
+ };
+
+ let seens = client
+ .database()
+ .event_seen_on_relays(event_id)
+ .await
+ .unwrap();
+
+ match seens {
+ Some(set) => {
+ let relays = set.into_iter().collect::>();
+ let event = Nip19Event::new(event_id, relays);
+
+ match event.to_bech32() {
+ Ok(id) => Ok(id),
+ Err(err) => Err(err.to_string()),
+ }
+ }
+ None => match event_id.to_bech32() {
+ Ok(id) => Ok(id),
+ Err(err) => Err(err.to_string()),
+ },
+ }
+}
+
+#[tauri::command]
+#[specta::specta]
+pub async fn user_to_bech32(user: &str, state: State<'_, Nostr>) -> Result {
+ let client = &state.client;
+
+ let public_key = match PublicKey::from_str(user) {
+ Ok(pk) => pk,
+ Err(_) => return Err("Public Key is not valid.".into()),
+ };
+
+ match client
+ .get_events_of(
+ vec![Filter::new()
+ .author(public_key)
+ .kind(Kind::RelayList)
+ .limit(1)],
+ Some(Duration::from_secs(10)),
+ )
+ .await
+ {
+ Ok(events) => match events.first() {
+ Some(event) => {
+ let relay_list = nip65::extract_relay_list(event);
+ let relays = relay_list
+ .into_iter()
+ .map(|i| i.0.to_string())
+ .collect::>();
+ let profile = Nip19Profile::new(public_key, relays).unwrap();
+
+ Ok(profile.to_bech32().unwrap())
+ }
+ None => match public_key.to_bech32() {
+ Ok(pk) => Ok(pk),
+ Err(err) => Err(err.to_string()),
+ },
+ },
+ Err(_) => match public_key.to_bech32() {
+ Ok(pk) => Ok(pk),
+ Err(err) => Err(err.to_string()),
+ },
+ }
+}
diff --git a/src-tauri/src/nostr/keys.rs b/src-tauri/src/nostr/keys.rs
index 75538e2a..36f4a0a7 100644
--- a/src-tauri/src/nostr/keys.rs
+++ b/src-tauri/src/nostr/keys.rs
@@ -383,24 +383,6 @@ pub fn get_private_key(npub: &str) -> Result {
}
}
-#[tauri::command]
-#[specta::specta]
-pub fn event_to_bech32(id: &str, relays: Vec) -> Result {
- let event_id = EventId::from_hex(id).unwrap();
- let event = Nip19Event::new(event_id, relays);
-
- Ok(event.to_bech32().unwrap())
-}
-
-#[tauri::command]
-#[specta::specta]
-pub fn user_to_bech32(key: &str, relays: Vec) -> Result {
- let pubkey = PublicKey::from_str(key).unwrap();
- let profile = Nip19Profile::new(pubkey, relays).unwrap();
-
- Ok(profile.to_bech32().unwrap())
-}
-
#[tauri::command]
#[specta::specta]
pub async fn verify_nip05(key: &str, nip05: &str) -> Result {