Compare commits

..
18 Commits
Author SHA1 Message Date
reya 6996e30889 chore: update github ci 2024-06-07 11:26:20 +07:00
reya 7ba793fad8 chore: bump version 2024-06-07 10:03:04 +07:00
reya f11f836518 chore: update tray icon 2024-06-07 09:56:57 +07:00
reya 04fe0fcec8 feat: respect the relay hint 2024-06-07 09:07:33 +07:00
799835a629 Notification Panel (#200)
* feat: add tauri nspanel

* feat: add notification panel

* feat: move notification service to backend

* feat: add sync notification job

* feat: enable panel to join all spaces including fullscreen (#203)

* feat: fetch notification

* feat: listen for new notification

* feat: finish panel

---------

Co-authored-by: Victor Aremu <me@victorare.mu>
2024-06-06 14:32:30 +07:00
XIAO YUandGitHub 4e7da4108b feat: Add get user following function (#202)
* feat: Add get user following function

* refactor: Refactor get_following function to use state and string public key

* feat: Update get_following function to use timeout duration

* feat: Fix connect_remote_account function to return remote_npub without conversion

* feat: Refactor get_following function to handle public key parsing errors

* Refactor get_followers function to handle public key parsing errors and use timeout duration
2024-06-05 13:24:32 +07:00
reya 7c7b082b3a fix: memory leak in image component 2024-06-03 07:32:34 +07:00
reya 38d6c51921 feat: use nostrdb for unix and rocksdb for windows 2024-06-02 08:16:59 +07:00
reya 1738cbdd97 chore: upgrade tauri 2024-06-01 14:54:35 +07:00
reya 2e885b76a1 feat: improve text wrap 2024-06-01 08:27:22 +07:00
reya f94680e487 fix: column overlapped after change account 2024-05-31 15:25:47 +07:00
XIAO YUandGitHub c682a58842 chore: Remove unused modules and update metadata.rs (#199) 2024-05-31 12:53:35 +07:00
reya 921cf871ee chore: bump version 2024-05-31 08:54:53 +07:00
reya d5b1593aca feat: improve nostr connect flow 2024-05-31 08:54:17 +07:00
reya 6676b4e2a4 Revert "chore: Update dependencies and add thiserror crate (#196)"
This reverts commit e254ee3203.
2024-05-30 15:40:44 +07:00
reya 5f30ddcfca Merge branch 'main' of github.com:lumehq/lume 2024-05-30 15:23:11 +07:00
reya 41d0de539d feat: revamp nostr connect flow 2024-05-30 15:21:33 +07:00
XIAO YUandGitHub e254ee3203 chore: Update dependencies and add thiserror crate (#196) 2024-05-30 07:12:46 +07:00
50 changed files with 2191 additions and 1901 deletions
+2
View File
@@ -16,6 +16,8 @@ jobs:
args: "--target aarch64-apple-darwin" args: "--target aarch64-apple-darwin"
- platform: "macos-latest" # for Intel based macs. - platform: "macos-latest" # for Intel based macs.
args: "--target x86_64-apple-darwin" args: "--target x86_64-apple-darwin"
- platform: "macos-latest" # for Intel based macs.
args: "--target universal-apple-darwin"
#- platform: 'ubuntu-22.04' #- platform: 'ubuntu-22.04'
# args: '' # args: ''
#- platform: 'windows-latest' #- platform: 'windows-latest'
+4 -4
View File
@@ -21,16 +21,16 @@
"@radix-ui/react-hover-card": "^1.0.7", "@radix-ui/react-hover-card": "^1.0.7",
"@radix-ui/react-popover": "^1.0.7", "@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-switch": "^1.0.3", "@radix-ui/react-switch": "^1.0.3",
"@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-tooltip": "^1.0.7", "@radix-ui/react-tooltip": "^1.0.7",
"@tanstack/query-sync-storage-persister": "^5.40.0", "@tanstack/query-sync-storage-persister": "^5.40.0",
"@tanstack/react-query": "^5.40.0", "@tanstack/react-query": "^5.40.0",
"@tanstack/react-query-persist-client": "^5.40.0", "@tanstack/react-query-persist-client": "^5.40.0",
"@tanstack/react-router": "^1.34.5", "@tanstack/react-router": "^1.34.9",
"i18next": "^23.11.5", "i18next": "^23.11.5",
"i18next-resources-to-backend": "^1.2.1", "i18next-resources-to-backend": "^1.2.1",
"minidenticons": "^4.2.1", "minidenticons": "^4.2.1",
"nanoid": "^5.0.7", "nanoid": "^5.0.7",
"nostr-tools": "^2.6.0",
"react": "^18.3.1", "react": "^18.3.1",
"react-currency-input-field": "^3.8.0", "react-currency-input-field": "^3.8.0",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
@@ -48,8 +48,8 @@
"@lume/tailwindcss": "workspace:^", "@lume/tailwindcss": "workspace:^",
"@lume/tsconfig": "workspace:^", "@lume/tsconfig": "workspace:^",
"@lume/types": "workspace:^", "@lume/types": "workspace:^",
"@tanstack/router-devtools": "^1.34.5", "@tanstack/router-devtools": "^1.34.9",
"@tanstack/router-vite-plugin": "^1.34.1", "@tanstack/router-vite-plugin": "^1.34.8",
"@types/react": "^18.3.3", "@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0", "@types/react-dom": "^18.3.0",
"@vitejs/plugin-react-swc": "^3.7.0", "@vitejs/plugin-react-swc": "^3.7.0",
Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

+14 -4
View File
@@ -3,7 +3,7 @@ import type { LumeColumn } from "@lume/types";
import { cn } from "@lume/utils"; import { cn } from "@lume/utils";
import { invoke } from "@tauri-apps/api/core"; import { invoke } from "@tauri-apps/api/core";
import { getCurrent } from "@tauri-apps/api/webviewWindow"; import { getCurrent } from "@tauri-apps/api/webviewWindow";
import { useEffect, useRef, useState } from "react"; import { useEffect, useMemo, useRef, useState } from "react";
export function Column({ export function Column({
column, column,
@@ -17,7 +17,10 @@ export function Column({
isResize: boolean; isResize: boolean;
}) { }) {
const container = useRef<HTMLDivElement>(null); const container = useRef<HTMLDivElement>(null);
const webviewLabel = `column-${account}_${column.label}`; const webviewLabel = useMemo(
() => `column-${account}_${column.label}`,
[account],
);
const [isCreated, setIsCreated] = useState(false); const [isCreated, setIsCreated] = useState(false);
@@ -48,6 +51,8 @@ export function Column({
}, [isScroll]); }, [isScroll]);
useEffect(() => { useEffect(() => {
if (!container?.current) return;
const rect = container.current.getBoundingClientRect(); const rect = container.current.getBoundingClientRect();
const url = `${column.content}?account=${account}&label=${column.label}&name=${column.name}`; const url = `${column.content}?account=${account}&label=${column.label}&name=${column.name}`;
@@ -59,11 +64,16 @@ export function Column({
width: rect.width, width: rect.width,
height: rect.height, height: rect.height,
url, url,
}).then(() => setIsCreated(true)); }).then(() => {
console.log("created: ", webviewLabel);
setIsCreated(true);
});
// close webview when unmounted // close webview when unmounted
return () => { return () => {
invoke("close_column", { label: webviewLabel }); invoke("close_column", { label: webviewLabel }).then(() => {
console.log("closed: ", webviewLabel);
});
}; };
}, [account]); }, [account]);
@@ -102,7 +102,7 @@ export function NoteContent({
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<div <div
className={cn( className={cn(
"select-text text-[15px] text-balance break-words overflow-hidden", "select-text text-[15px] text-pretty content-break overflow-hidden",
event.content.length > 500 ? "max-h-[300px] gradient-mask-b-0" : "", event.content.length > 500 ? "max-h-[300px] gradient-mask-b-0" : "",
className, className,
)} )}
@@ -145,7 +145,7 @@ export function NoteContentLarge({
return ( return (
<div className={cn("select-text", className)}> <div className={cn("select-text", className)}>
<div className="text-[15px] text-balance content-break leading-normal"> <div className="text-[15px] text-pretty content-break leading-normal">
{content} {content}
</div> </div>
</div> </div>
@@ -2,7 +2,7 @@ export function Hashtag({ tag }: { tag: string }) {
return ( return (
<button <button
type="button" type="button"
className="break-all cursor-default leading-normal group" className="break-all cursor-default leading-normal group text-start"
> >
<span className="text-blue-500">#</span> <span className="text-blue-500">#</span>
<span className="underline-offset-1 underline decoration-2 decoration-blue-200 dark:decoration-blue-800 group-hover:decoration-blue-500"> <span className="underline-offset-1 underline decoration-2 decoration-blue-200 dark:decoration-blue-800 group-hover:decoration-blue-500">
@@ -48,7 +48,7 @@ export function MentionNote({
</User.Provider> </User.Provider>
<div <div
className={cn( className={cn(
"px-3 select-text content-break whitespace-normal text-balance leading-normal", "px-3 select-text whitespace-normal text-pretty content-break leading-normal",
data.content.length > 100 ? "max-h-[150px] gradient-mask-b-0" : "", data.content.length > 100 ? "max-h-[150px] gradient-mask-b-0" : "",
)} )}
> >
@@ -1,61 +1,44 @@
import { CheckCircleIcon, DownloadIcon } from "@lume/icons";
import { downloadDir } from "@tauri-apps/api/path";
import { WebviewWindow } from "@tauri-apps/api/webviewWindow"; import { WebviewWindow } from "@tauri-apps/api/webviewWindow";
import { download } from "@tauri-apps/plugin-upload";
import { type SyntheticEvent, useState } from "react";
export function ImagePreview({ url }: { url: string }) { export function ImagePreview({ url }: { url: string }) {
const [downloaded, setDownloaded] = useState(false); const open = async (url: string) => {
const name = new URL(url).pathname
.split("/")
.pop()
.replace(/[^a-zA-Z ]/g, "");
const label = `viewer-${name}`;
const window = WebviewWindow.getByLabel(label);
const downloadImage = async (e: { stopPropagation: () => void }) => { if (!window) {
try { const newWindow = new WebviewWindow(label, {
e.stopPropagation(); url,
title: "Image Viewer",
width: 800,
height: 800,
titleBarStyle: "overlay",
});
const downloadDirPath = await downloadDir(); return newWindow;
const filename = url.substring(url.lastIndexOf("/") + 1);
await download(url, `${downloadDirPath}/${filename}`);
setDownloaded(true);
} catch (e) {
console.error(e);
} }
};
const open = async () => { return await window.setFocus();
const name = new URL(url).pathname.split("/").pop();
return new WebviewWindow("image-viewer", {
url,
title: name,
});
};
const fallback = (event: SyntheticEvent<HTMLImageElement, Event>) => {
event.currentTarget.src = "/fallback-image.jpg";
}; };
return ( return (
// biome-ignore lint/a11y/useKeyWithClickEvents: <explanation> <div className="group relative my-1">
<div onClick={() => open()} className="group relative my-1">
<img <img
src={url} src={url}
alt={url} alt={url}
loading="lazy" loading="lazy"
decoding="async" decoding="async"
style={{ contentVisibility: "auto" }} style={{ contentVisibility: "auto" }}
onError={fallback}
className="max-h-[600px] w-auto object-cover rounded-lg outline outline-1 -outline-offset-1 outline-black/15" className="max-h-[600px] w-auto object-cover rounded-lg outline outline-1 -outline-offset-1 outline-black/15"
onClick={() => open(url)}
onError={({ currentTarget }) => {
currentTarget.onerror = null;
currentTarget.src = "/404.jpg";
}}
/> />
<button
type="button"
onClick={(e) => downloadImage(e)}
className="absolute right-2 top-2 z-20 hidden size-8 items-center justify-center rounded-md bg-white/10 text-white/70 backdrop-blur-2xl hover:bg-blue-500 hover:text-white group-hover:inline-flex"
>
{downloaded ? (
<CheckCircleIcon className="size-4" />
) : (
<DownloadIcon className="size-4" />
)}
</button>
</div> </div>
); );
} }
@@ -36,6 +36,10 @@ export function Images({ urls }: { urls: string[] }) {
style={{ contentVisibility: "auto" }} style={{ contentVisibility: "auto" }}
className="max-h-[400px] w-auto object-cover rounded-lg outline outline-1 -outline-offset-1 outline-black/15" className="max-h-[400px] w-auto object-cover rounded-lg outline outline-1 -outline-offset-1 outline-black/15"
onClick={() => open(urls[0])} onClick={() => open(urls[0])}
onError={({ currentTarget }) => {
currentTarget.onerror = null;
currentTarget.src = "/404.jpg";
}}
/> />
</div> </div>
); );
@@ -54,6 +58,10 @@ export function Images({ urls }: { urls: string[] }) {
style={{ contentVisibility: "auto" }} style={{ contentVisibility: "auto" }}
className="w-full h-full object-cover rounded-lg outline outline-1 -outline-offset-1 outline-black/15" className="w-full h-full object-cover rounded-lg outline outline-1 -outline-offset-1 outline-black/15"
onClick={() => open(item)} onClick={() => open(item)}
onError={({ currentTarget }) => {
currentTarget.onerror = null;
currentTarget.src = "/404.jpg";
}}
/> />
</CarouselItem> </CarouselItem>
)} )}
+1 -1
View File
@@ -23,7 +23,7 @@ export function UserAvatar({ className }: { className?: string }) {
alt={user.pubkey} alt={user.pubkey}
loading="eager" loading="eager"
decoding="async" decoding="async"
className={cn("outline-[.5px] outline-black/5", className)} className={cn("outline-[.5px] outline-black/5 object-cover", className)}
/> />
<Avatar.Fallback delayMs={120}> <Avatar.Fallback delayMs={120}>
<img <img
+17 -7
View File
@@ -13,9 +13,11 @@ import { VList, type VListHandle } from "virtua";
export const Route = createFileRoute("/$account/home")({ export const Route = createFileRoute("/$account/home")({
loader: async () => { loader: async () => {
const columns = NostrQuery.getColumns(); const columns = await NostrQuery.getColumns();
return columns; return columns;
}, },
gcTime: 0,
shouldReload: false,
component: Screen, component: Screen,
}); });
@@ -24,11 +26,16 @@ function Screen() {
const initialColumnList = Route.useLoaderData(); const initialColumnList = Route.useLoaderData();
const vlistRef = useRef<VListHandle>(null); const vlistRef = useRef<VListHandle>(null);
const [columns, setColumns] = useState<LumeColumn[]>([]);
const [selectedIndex, setSelectedIndex] = useState(-1); const [selectedIndex, setSelectedIndex] = useState(-1);
const [columns, setColumns] = useState([]);
const [isScroll, setIsScroll] = useState(false); const [isScroll, setIsScroll] = useState(false);
const [isResize, setIsResize] = useState(false); const [isResize, setIsResize] = useState(false);
const reset = () => {
setColumns(null);
setSelectedIndex(-1);
};
const goLeft = () => { const goLeft = () => {
const prevIndex = Math.max(selectedIndex - 1, 0); const prevIndex = Math.max(selectedIndex - 1, 0);
setSelectedIndex(prevIndex); setSelectedIndex(prevIndex);
@@ -64,7 +71,7 @@ function Screen() {
// scroll to the newest column // scroll to the newest column
vlistRef.current.scrollToIndex(newCols.length - 1, { vlistRef.current.scrollToIndex(newCols.length - 1, {
align: "end", align: "center",
}); });
}, 150); }, 150);
@@ -103,12 +110,15 @@ function Screen() {
}, [initialColumnList]); }, [initialColumnList]);
useEffect(() => { useEffect(() => {
// save state // save current state
NostrQuery.setColumns(columns); if (columns?.length) {
NostrQuery.setColumns(columns).then(() => console.log("saved"));
}
}, [columns]); }, [columns]);
useEffect(() => { useEffect(() => {
const unlistenColEvent = listen<EventColumns>("columns", (data) => { const unlistenColEvent = listen<EventColumns>("columns", (data) => {
if (data.payload.type === "reset") reset();
if (data.payload.type === "add") add(data.payload.column); if (data.payload.type === "add") add(data.payload.column);
if (data.payload.type === "remove") remove(data.payload.label); if (data.payload.type === "remove") remove(data.payload.label);
if (data.payload.type === "set_title") if (data.payload.type === "set_title")
@@ -137,9 +147,9 @@ function Screen() {
onScrollEnd={() => setIsScroll(false)} onScrollEnd={() => setIsScroll(false)}
className="scrollbar-none h-full w-full overflow-x-auto focus:outline-none" className="scrollbar-none h-full w-full overflow-x-auto focus:outline-none"
> >
{columns.map((column) => ( {columns?.map((column) => (
<Column <Column
key={column.label} key={account + column.label}
column={column} column={column}
account={account} account={account}
isScroll={isScroll} isScroll={isScroll}
+18 -84
View File
@@ -1,36 +1,23 @@
import { import {
BellIcon,
ComposeFilledIcon, ComposeFilledIcon,
HorizontalDotsIcon, HorizontalDotsIcon,
PlusIcon, PlusIcon,
SearchIcon, SearchIcon,
} from "@lume/icons"; } from "@lume/icons";
import { type NostrEvent, Kind } from "@lume/types";
import { User } from "@/components/user"; import { User } from "@/components/user";
import { import { cn } from "@lume/utils";
cn,
decodeZapInvoice,
displayNpub,
sendNativeNotification,
} from "@lume/utils";
import { Outlet, createFileRoute } from "@tanstack/react-router"; import { Outlet, createFileRoute } from "@tanstack/react-router";
import { invoke } from "@tauri-apps/api/core";
import { getCurrent } from "@tauri-apps/api/window"; import { getCurrent } from "@tauri-apps/api/window";
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import * as Popover from "@radix-ui/react-popover"; import * as Popover from "@radix-ui/react-popover";
import { LumeWindow, NostrAccount, NostrQuery } from "@lume/system"; import { LumeWindow, NostrAccount } from "@lume/system";
import { Link } from "@tanstack/react-router"; import { Link } from "@tanstack/react-router";
type AccountSearch = {
accounts?: string[];
};
export const Route = createFileRoute("/$account")({ export const Route = createFileRoute("/$account")({
validateSearch: (search: Record<string, unknown>): AccountSearch => { beforeLoad: async () => {
return { const accounts = await NostrAccount.getAccounts();
accounts: (search?.accounts as string[]) || [], return { accounts };
};
}, },
component: Screen, component: Screen,
}); });
@@ -50,7 +37,7 @@ function Screen() {
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Accounts /> <Accounts />
<Link <Link
to="/landing/" to="/landing"
className="inline-flex size-8 shrink-0 items-center justify-center rounded-full bg-black/10 text-neutral-800 hover:bg-black/20 dark:bg-white/10 dark:text-neutral-200 dark:hover:bg-white/20" className="inline-flex size-8 shrink-0 items-center justify-center rounded-full bg-black/10 text-neutral-800 hover:bg-black/20 dark:bg-white/10 dark:text-neutral-200 dark:hover:bg-white/20"
> >
<PlusIcon className="size-5" /> <PlusIcon className="size-5" />
@@ -65,7 +52,6 @@ function Screen() {
<ComposeFilledIcon className="size-4" /> <ComposeFilledIcon className="size-4" />
New Post New Post
</button> </button>
<Bell />
<button <button
type="button" type="button"
onClick={() => LumeWindow.openSearch()} onClick={() => LumeWindow.openSearch()}
@@ -84,7 +70,7 @@ function Screen() {
} }
function Accounts() { function Accounts() {
const { accounts } = Route.useSearch(); const { accounts } = Route.useRouteContext();
const { account } = Route.useParams(); const { account } = Route.useParams();
const [windowWidth, setWindowWidth] = useState<number>(null); const [windowWidth, setWindowWidth] = useState<number>(null);
@@ -108,11 +94,20 @@ function Accounts() {
return await LumeWindow.openProfile(account); return await LumeWindow.openProfile(account);
} }
// change current account and update signer // Change current account and update signer
const select = await NostrAccount.loadAccount(npub); const select = await NostrAccount.loadAccount(npub);
if (select) { if (select) {
return navigate({ to: "/$account/home", params: { account: npub } }); // Reset current columns
await getCurrent().emit("columns", { type: "reset" });
// Redirect to new account
return navigate({
to: "/$account/home",
params: { account: npub },
resetScroll: true,
replace: true,
});
} else { } else {
toast.warning("Something wrong."); toast.warning("Something wrong.");
} }
@@ -191,64 +186,3 @@ function Accounts() {
</div> </div>
); );
} }
function Bell() {
const { account } = Route.useParams();
const [count, setCount] = useState(0);
useEffect(() => {
const unlisten = getCurrent().listen<string>(
"activity",
async (payload) => {
setCount((prevCount) => prevCount + 1);
await invoke("set_badge", { count });
const event: NostrEvent = JSON.parse(payload.payload);
const user = await NostrQuery.getProfile(event.pubkey);
const userName =
user.display_name || user.name || displayNpub(event.pubkey, 16);
switch (event.kind) {
case Kind.Text: {
sendNativeNotification("Mentioned you in a note", userName);
break;
}
case Kind.Repost: {
sendNativeNotification("Reposted your note", userName);
break;
}
case Kind.ZapReceipt: {
const amount = decodeZapInvoice(event.tags);
sendNativeNotification(
`Zapped ₿ ${amount.bitcoinFormatted}`,
userName,
);
break;
}
default:
break;
}
},
);
return () => {
unlisten.then((f) => f());
};
}, []);
return (
<button
type="button"
onClick={() => {
setCount(0);
LumeWindow.openActivity(account);
}}
className="relative inline-flex size-8 items-center justify-center rounded-full text-neutral-800 hover:bg-black/10 dark:text-neutral-200 dark:hover:bg-white/10"
>
<BellIcon className="size-5" />
{count > 0 ? (
<span className="absolute right-0 top-0 block size-2 rounded-full bg-teal-500 ring-1 ring-black/5" />
) : null}
</button>
);
}
@@ -1,61 +0,0 @@
import { Spinner } from "@lume/ui";
import { Note } from "@/components/note";
import { Await, createFileRoute, defer } from "@tanstack/react-router";
import { Suspense } from "react";
import { Virtualizer } from "virtua";
import { NostrQuery } from "@lume/system";
export const Route = createFileRoute("/activity/$account/texts")({
loader: async ({ params }) => {
return { data: defer(NostrQuery.getUserActivities(params.account, "1")) };
},
component: Screen,
});
function Screen() {
const { data } = Route.useLoaderData();
return (
<Virtualizer overscan={3}>
<Suspense
fallback={
<div className="flex h-20 w-full flex-col items-center justify-center gap-1">
<button
type="button"
className="inline-flex items-center gap-2 text-sm font-medium"
disabled
>
<Spinner className="size-5" />
Loading...
</button>
</div>
}
>
<Await promise={data}>
{(events) =>
events.map((event) => (
<div
key={event.id}
className="flex flex-col gap-2 mb-3 bg-white dark:bg-black/20 backdrop-blur-lg rounded-xl shadow-primary dark:ring-1 ring-neutral-800/50"
>
<Note.Provider event={event}>
<Note.Root>
<div className="px-3 h-14 flex items-center justify-between">
<Note.User />
<Note.Menu />
</div>
<Note.Activity className="px-3" />
<Note.Content className="px-3" quote={false} clean />
<div className="mt-3 flex items-center gap-4 h-14 px-3">
<Note.Open />
</div>
</Note.Root>
</Note.Provider>
</div>
))
}
</Await>
</Suspense>
</Virtualizer>
);
}
@@ -1,50 +0,0 @@
import { Box, Container } from "@lume/ui";
import { cn } from "@lume/utils";
import { Link, Outlet } from "@tanstack/react-router";
import { createFileRoute } from "@tanstack/react-router";
export const Route = createFileRoute("/activity/$account")({
component: Screen,
});
function Screen() {
const { account } = Route.useParams();
return (
<Container withDrag>
<Box className="scrollbar-none shadow-none bg-black/5 dark:bg-white/5 backdrop-blur-sm flex flex-col overflow-y-auto">
<div className="h-14 shrink-0 flex w-full items-center gap-1 px-3">
<div className="inline-flex h-full w-full items-center gap-1">
<Link to="/activity/$account/texts" params={{ account }}>
{({ isActive }) => (
<div
className={cn(
"inline-flex h-7 w-max items-center justify-center gap-2 rounded-full px-3 text-sm font-medium",
isActive ? "bg-neutral-50 dark:bg-white/10" : "opacity-50",
)}
>
Notes
</div>
)}
</Link>
<Link to="/activity/$account/zaps" params={{ account }}>
{({ isActive }) => (
<div
className={cn(
"inline-flex h-7 w-max items-center justify-center gap-2 rounded-full px-3 text-sm font-medium",
isActive ? "bg-neutral-50 dark:bg-white/10" : "opacity-50",
)}
>
Zaps
</div>
)}
</Link>
</div>
</div>
<div className="px-2 flex-1 overflow-y-auto w-full h-full scrollbar-none">
<Outlet />
</div>
</Box>
</Container>
);
}
@@ -1,67 +0,0 @@
import { User } from "@/components/user";
import { NostrQuery } from "@lume/system";
import { Spinner } from "@lume/ui";
import { decodeZapInvoice } from "@lume/utils";
import { Await, createFileRoute, defer } from "@tanstack/react-router";
import { Suspense } from "react";
import { Virtualizer } from "virtua";
export const Route = createFileRoute("/activity/$account/zaps")({
loader: async ({ params }) => {
return {
data: defer(NostrQuery.getUserActivities(params.account, "9735")),
};
},
component: Screen,
});
function Screen() {
const { data } = Route.useLoaderData();
return (
<Virtualizer overscan={3}>
<Suspense
fallback={
<div className="flex h-20 w-full flex-col items-center justify-center gap-1">
<button
type="button"
className="inline-flex items-center gap-2 text-sm font-medium"
disabled
>
<Spinner className="size-5" />
Loading...
</button>
</div>
}
>
<Await promise={data}>
{(events) =>
events.map((event) => (
<div
key={event.id}
className="flex flex-col gap-2 mb-3 bg-white dark:bg-black/20 backdrop-blur-lg rounded-xl shadow-primary dark:ring-1 ring-neutral-800/50"
>
<User.Provider pubkey={event.pubkey}>
<User.Root className="flex flex-col">
<div className="text-lg h-20 font-medium leading-tight flex w-full items-center justify-center">
{decodeZapInvoice(event.tags).bitcoinFormatted}
</div>
<div className="h-11 border-t border-neutral-100 dark:border-neutral-900 flex items-center gap-1 px-2">
<div className="inline-flex items-center gap-2">
<User.Avatar className="size-7 rounded-full shrink-0" />
<User.Name className="text-sm font-medium" />
</div>
<div className="text-sm text-neutral-700 dark:text-neutral-300">
zapped you
</div>
</div>
</User.Root>
</User.Provider>
</div>
))
}
</Await>
</Suspense>
</Virtualizer>
);
}
@@ -33,7 +33,8 @@ function Screen() {
} }
return navigate({ return navigate({
to: "/auth/settings", to: "/auth/$account/settings",
params: { account },
}); });
} }
@@ -3,12 +3,11 @@ import { NostrQuery } from "@lume/system";
import { Spinner } from "@lume/ui"; import { Spinner } from "@lume/ui";
import * as Switch from "@radix-ui/react-switch"; import * as Switch from "@radix-ui/react-switch";
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute } from "@tanstack/react-router";
import { requestPermission } from "@tauri-apps/plugin-notification";
import { useState } from "react"; import { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { toast } from "sonner"; import { toast } from "sonner";
export const Route = createFileRoute("/auth/settings")({ export const Route = createFileRoute("/auth/$account/settings")({
beforeLoad: async () => { beforeLoad: async () => {
const settings = await NostrQuery.getSettings(); const settings = await NostrQuery.getSettings();
return { settings }; return { settings };
@@ -19,6 +18,7 @@ export const Route = createFileRoute("/auth/settings")({
function Screen() { function Screen() {
const { settings } = Route.useRouteContext(); const { settings } = Route.useRouteContext();
const { account } = Route.useParams();
const { t } = useTranslation(); const { t } = useTranslation();
const [newSettings, setNewSettings] = useState(settings); const [newSettings, setNewSettings] = useState(settings);
@@ -26,21 +26,6 @@ function Screen() {
const navigate = Route.useNavigate(); const navigate = Route.useNavigate();
const toggleNofitication = async () => {
await requestPermission();
setNewSettings((prev) => ({
...prev,
notification: !newSettings.notification,
}));
};
const toggleAutoUpdate = () => {
setNewSettings((prev) => ({
...prev,
autoUpdate: !newSettings.autoUpdate,
}));
};
const toggleEnhancedPrivacy = () => { const toggleEnhancedPrivacy = () => {
setNewSettings((prev) => ({ setNewSettings((prev) => ({
...prev, ...prev,
@@ -72,7 +57,8 @@ function Screen() {
if (eventId) { if (eventId) {
return navigate({ return navigate({
to: "/", to: "/$account/home",
params: { account },
replace: true, replace: true,
}); });
} }
@@ -99,22 +85,6 @@ function Screen() {
</div> </div>
<div className="flex flex-col gap-5"> <div className="flex flex-col gap-5">
<div className="flex flex-col gap-3"> <div className="flex flex-col gap-3">
<div className="flex w-full items-start justify-between gap-4 rounded-lg bg-neutral-100 px-5 py-4 dark:bg-white/10">
<div className="flex-1">
<h3 className="font-semibold">Push Notification</h3>
<p className="text-sm text-neutral-700 dark:text-neutral-300">
Enabling push notifications will allow you to receive
notifications from Lume.
</p>
</div>
<Switch.Root
checked={newSettings.notification}
onClick={() => toggleNofitication()}
className="relative mt-1 h-7 w-12 shrink-0 cursor-default rounded-full bg-neutral-200 outline-none data-[state=checked]:bg-blue-500 dark:bg-white/20"
>
<Switch.Thumb className="block size-6 translate-x-0.5 rounded-full bg-white transition-transform duration-100 will-change-transform data-[state=checked]:translate-x-[19px]" />
</Switch.Root>
</div>
<div className="flex w-full items-start justify-between gap-4 rounded-lg bg-neutral-100 px-5 py-4 dark:bg-white/10"> <div className="flex w-full items-start justify-between gap-4 rounded-lg bg-neutral-100 px-5 py-4 dark:bg-white/10">
<div className="flex-1"> <div className="flex-1">
<h3 className="font-semibold">Enhanced Privacy</h3> <h3 className="font-semibold">Enhanced Privacy</h3>
@@ -131,21 +101,6 @@ function Screen() {
<Switch.Thumb className="block size-6 translate-x-0.5 rounded-full bg-white transition-transform duration-100 will-change-transform data-[state=checked]:translate-x-[19px]" /> <Switch.Thumb className="block size-6 translate-x-0.5 rounded-full bg-white transition-transform duration-100 will-change-transform data-[state=checked]:translate-x-[19px]" />
</Switch.Root> </Switch.Root>
</div> </div>
<div className="flex w-full items-start justify-between gap-4 rounded-lg bg-neutral-100 px-5 py-4 dark:bg-white/10">
<div className="flex-1">
<h3 className="font-semibold">Auto Update</h3>
<p className="text-sm text-neutral-700 dark:text-neutral-300">
Automatically download and install new version.
</p>
</div>
<Switch.Root
checked={newSettings.autoUpdate}
onClick={() => toggleAutoUpdate()}
className="relative mt-1 h-7 w-12 shrink-0 cursor-default rounded-full bg-neutral-200 outline-none data-[state=checked]:bg-blue-500 dark:bg-white/20"
>
<Switch.Thumb className="block size-6 translate-x-0.5 rounded-full bg-white transition-transform duration-100 will-change-transform data-[state=checked]:translate-x-[19px]" />
</Switch.Root>
</div>
<div className="flex w-full items-start justify-between gap-4 rounded-lg bg-neutral-100 px-5 py-4 dark:bg-white/10"> <div className="flex w-full items-start justify-between gap-4 rounded-lg bg-neutral-100 px-5 py-4 dark:bg-white/10">
<div className="flex-1"> <div className="flex-1">
<h3 className="font-semibold">Zap</h3> <h3 className="font-semibold">Zap</h3>
@@ -185,7 +140,7 @@ function Screen() {
disabled={loading} disabled={loading}
className="mb-1 inline-flex h-11 w-full shrink-0 items-center justify-center rounded-lg bg-blue-500 font-semibold text-white hover:bg-blue-600 disabled:opacity-50" className="mb-1 inline-flex h-11 w-full shrink-0 items-center justify-center rounded-lg bg-blue-500 font-semibold text-white hover:bg-blue-600 disabled:opacity-50"
> >
{t("global.continue")} {loading ? <Spinner /> : t("global.continue")}
</button> </button>
</div> </div>
</div> </div>
@@ -28,7 +28,8 @@ function Screen() {
if (npub) { if (npub) {
navigate({ navigate({
to: "/auth/settings", to: "/auth/$account/settings",
params: { account: npub },
replace: true, replace: true,
}); });
} }
@@ -23,11 +23,12 @@ function Screen() {
try { try {
setLoading(true); setLoading(true);
const npub = await NostrAccount.connectRemoteAccount(uri); const remoteAccount = await NostrAccount.connectRemoteAccount(uri);
if (npub) { if (remoteAccount?.length) {
navigate({ return navigate({
to: "/auth/settings", to: "/auth/$account/settings",
params: { account: remoteAccount },
replace: true, replace: true,
}); });
} }
+4 -4
View File
@@ -41,9 +41,6 @@ function Screen() {
return navigate({ return navigate({
to: "/$account/home", to: "/$account/home",
params: { account: npub }, params: { account: npub },
search: {
accounts: context.accounts,
},
replace: true, replace: true,
}); });
} }
@@ -60,7 +57,10 @@ function Screen() {
}); });
return ( return (
<div className="relative flex h-full w-full items-center justify-center"> <div
data-tauri-drag-region
className="relative flex h-full w-full items-center justify-center"
>
<div className="relative z-20 flex flex-col items-center gap-16"> <div className="relative z-20 flex flex-col items-center gap-16">
<div className="text-center"> <div className="text-center">
<h2 className="text-xl text-neutral-700 dark:text-neutral-300"> <h2 className="text-xl text-neutral-700 dark:text-neutral-300">
+314
View File
@@ -0,0 +1,314 @@
import { Note } from "@/components/note";
import { User } from "@/components/user";
import { LumeWindow, NostrQuery, useEvent } from "@lume/system";
import { Kind, NostrEvent } from "@lume/types";
import { createFileRoute } from "@tanstack/react-router";
import { getCurrent } from "@tauri-apps/api/window";
import { useEffect, useMemo, useState } from "react";
import * as Tabs from "@radix-ui/react-tabs";
import { InfoIcon, RepostIcon, SettingsIcon } from "@lume/icons";
import { decodeZapInvoice, formatCreatedAt } from "@lume/utils";
interface EmitAccount {
account: string;
}
export const Route = createFileRoute("/panel")({
component: Screen,
});
function Screen() {
const [account, setAccount] = useState<string>(null);
const [events, setEvents] = useState<NostrEvent[]>([]);
const texts = useMemo(
() => events.filter((ev) => ev.kind === Kind.Text),
[events],
);
const zaps = useMemo(() => {
const groups = new Map<string, NostrEvent[]>();
const list = events.filter((ev) => ev.kind === Kind.ZapReceipt);
for (const event of list) {
const rootId = event.tags.filter((tag) => tag[0] === "e")[0]?.[1];
if (rootId) {
if (groups.has(rootId)) {
groups.get(rootId).push(event);
} else {
groups.set(rootId, [event]);
}
}
}
return groups;
}, [events]);
const reactions = useMemo(() => {
const groups = new Map<string, NostrEvent[]>();
const list = events.filter(
(ev) => ev.kind === Kind.Repost || ev.kind === Kind.Reaction,
);
for (const event of list) {
const rootId = event.tags.filter((tag) => tag[0] === "e")[0]?.[1];
if (rootId) {
if (groups.has(rootId)) {
groups.get(rootId).push(event);
} else {
groups.set(rootId, [event]);
}
}
}
return groups;
}, [events]);
useEffect(() => {
if (account?.length && account?.startsWith("npub1")) {
NostrQuery.getNotifications()
.then((data) => {
const sorted = data.sort((a, b) => b.created_at - a.created_at);
setEvents(sorted);
})
.catch((e) => console.log(e));
}
}, [account]);
useEffect(() => {
const unlistenLoad = getCurrent().listen<EmitAccount>(
"load-notification",
(data) => {
setAccount(data.payload.account);
},
);
const unlistenNewEvent = getCurrent().listen("notification", (data) => {
const event: NostrEvent = JSON.parse(data.payload as string);
setEvents((prev) => [event, ...prev]);
});
return () => {
unlistenLoad.then((f) => f());
unlistenNewEvent.then((f) => f());
};
}, []);
if (!account) {
return (
<div className="w-full h-full flex items-center justify-center text-sm">
Please log in.
</div>
);
}
return (
<div className="w-full h-full flex flex-col">
<div className="h-11 shrink-0 flex items-center justify-between border-b border-black/5 px-4">
<div>
<h1 className="text-sm font-semibold">Notifications</h1>
</div>
<div className="inline-flex items-center gap-2">
<User.Provider pubkey={account}>
<User.Root>
<User.Avatar className="size-7 rounded-full" />
</User.Root>
</User.Provider>
<button
type="button"
onClick={() => LumeWindow.openSettings()}
className="size-7 inline-flex items-center justify-center bg-black/5 dark:bg-white/5 rounded-full"
>
<SettingsIcon className="size-4" />
</button>
</div>
</div>
<Tabs.Root
defaultValue="replies"
className="flex-1 overflow-y-auto overflow-x-hidden scrollbar-none"
>
<Tabs.List className="flex items-center">
<Tabs.Trigger
className="flex-1 inline-flex h-8 items-center justify-center gap-2 px-2 text-sm font-medium border-b border-black/10 data-[state=active]:border-black/30 dark:data-[state=active] data-[state=inactive]:opacity-50"
value="replies"
>
Replies
</Tabs.Trigger>
<Tabs.Trigger
className="flex-1 inline-flex h-8 items-center justify-center gap-2 px-2 text-sm font-medium border-b border-black/10 data-[state=active]:border-black/30 dark:data-[state=active] data-[state=inactive]:opacity-50"
value="reactions"
>
Reactions
</Tabs.Trigger>
<Tabs.Trigger
className="flex-1 inline-flex h-8 items-center justify-center gap-2 px-2 text-sm font-medium border-b border-black/10 data-[state=active]:border-black/30 dark:data-[state=active] data-[state=inactive]:opacity-50"
value="zaps"
>
Zaps
</Tabs.Trigger>
</Tabs.List>
<div className="p-2">
<Tabs.Content value="replies" className="flex flex-col gap-2">
{texts.map((event) => (
<TextNote event={event} />
))}
</Tabs.Content>
<Tabs.Content value="reactions" className="flex flex-col gap-2">
{[...reactions.entries()].map(([root, events]) => (
<div
key={root}
className="shrink-0 flex flex-col gap-1 rounded-lg p-2 backdrop-blur-md bg-black/10 dark:bg-white/10"
>
<div className="min-w-0 flex-1 flex flex-col gap-2">
<div className="pb-2 border-b border-black/5 dark:border-white/5 flex items-center gap-2">
<RootNote id={root} />
</div>
<div className="flex flex-wrap items-center gap-3">
{events.map((event) => (
<User.Provider pubkey={event.pubkey}>
<User.Root className="shrink-0 flex rounded-full h-8 bg-black/10 dark:bg-white/10 backdrop-blur-md p-[2px]">
<User.Avatar className="flex-1 size-7 rounded-full" />
<div className="flex-1 size-7 rounded-full inline-flex items-center justify-center text-xs truncate">
{event.kind === Kind.Reaction ? (
event.content === "+" ? (
"👍"
) : (
event.content
)
) : (
<RepostIcon className="size-4 dark:text-teal-600 text-teal-400" />
)}
</div>
</User.Root>
</User.Provider>
))}
</div>
</div>
</div>
))}
</Tabs.Content>
<Tabs.Content value="zaps" className="flex flex-col gap-2">
{[...zaps.entries()].map(([root, events]) => (
<div
key={root}
className="shrink-0 flex flex-col gap-1 rounded-lg p-2 backdrop-blur-md bg-black/10 dark:bg-white/10"
>
<div className="min-w-0 flex-1 flex flex-col gap-2">
<div className="pb-2 border-b border-black/5 dark:border-white/5 flex items-center gap-2">
<RootNote id={root} />
</div>
<div className="flex flex-wrap items-center gap-3">
{events.map((event) => (
<User.Provider
pubkey={event.tags.find((tag) => tag[0] == "P")[1]}
>
<User.Root className="shrink-0 flex gap-1.5 rounded-full h-8 bg-black/10 dark:bg-white/10 backdrop-blur-md p-[2px]">
<User.Avatar className="flex-1 size-7 rounded-full" />
<div className="flex-1 h-7 w-max pr-1.5 rounded-full inline-flex items-center justify-center text-sm truncate">
{decodeZapInvoice(event.tags).bitcoinFormatted}
</div>
</User.Root>
</User.Provider>
))}
</div>
</div>
</div>
))}
</Tabs.Content>
</div>
</Tabs.Root>
</div>
);
}
function RootNote({ id }: { id: string }) {
const { isLoading, isError, data } = useEvent(id);
if (isLoading) {
return (
<div className="pb-2 mb-2 flex items-center">
<div className="size-8 shrink-0 rounded-full bg-black/20 dark:bg-white/20 animate-pulse" />
<div className="animate-pulse rounded-md h-4 w-2/3 bg-black/20 dark:bg-white/20" />
</div>
);
}
if (isError || !data) {
return (
<div className="flex items-center gap-2">
<div className="size-8 shrink-0 rounded-full bg-red-500 text-white inline-flex items-center justify-center">
<InfoIcon className="size-5" />
</div>
<p className="text-red-500 text-sm">
Event not found with your current relay set
</p>
</div>
);
}
return (
<Note.Provider event={data}>
<Note.Root className="flex items-center gap-2">
<User.Provider pubkey={data.pubkey}>
<User.Root className="shrink-0">
<User.Avatar className="size-8 shrink-0 rounded-full" />
</User.Root>
</User.Provider>
<div className="line-clamp-1">{data.content}</div>
</Note.Root>
</Note.Provider>
);
}
function TextNote({ event }: { event: NostrEvent }) {
const pTags = event.tags
.filter((tag) => tag[0] === "p")
.map((tag) => tag[1])
.slice(0, 3);
return (
<button
type="button"
key={event.id}
onClick={() => LumeWindow.openEvent(event)}
>
<Note.Provider event={event}>
<Note.Root className="shrink-0 flex flex-col gap-1 rounded-lg p-2 backdrop-blur-md bg-black/10 dark:bg-white/10">
<User.Provider pubkey={event.pubkey}>
<User.Root className="inline-flex items-center gap-2">
<User.Avatar className="size-9 shrink-0 rounded-full" />
<div className="flex-1 flex flex-col">
<div className="w-full flex items-baseline justify-between">
<User.Name className="leading-tight text-sm font-semibold" />
<span className="leading-tight text-sm text-black/50 dark:text-white/50">
{formatCreatedAt(event.created_at)}
</span>
</div>
<div className="inline-flex items-baseline gap-1 text-xs">
<span className="leading-tight text-black/50 dark:text-white/50">
Reply to:
</span>
<div className="inline-flex items-baseline gap-1">
{pTags.map((replyTo) => (
<User.Provider pubkey={replyTo}>
<User.Root>
<User.Name className="leading-tight font-medium" />
</User.Root>
</User.Provider>
))}
</div>
</div>
</div>
</User.Root>
</User.Provider>
<div className="flex gap-2">
<div className="w-9 shrink-0" />
<div className="line-clamp-1">{event.content}</div>
</div>
</Note.Root>
</Note.Provider>
</button>
);
}
+33 -34
View File
@@ -1,36 +1,35 @@
{ {
"name": "lume", "name": "lume",
"private": true, "private": true,
"version": "4.0.0", "version": "4.0.0",
"scripts": { "scripts": {
"build": "turbo run build", "build": "turbo run build",
"dev": "turbo run dev", "dev": "turbo run dev",
"web:dev": "turbo run dev --filter web", "web:dev": "turbo run dev --filter web",
"desktop:dev": "turbo run dev --filter desktop2", "desktop:dev": "turbo run dev --filter desktop2",
"desktop:build": "turbo run build --filter desktop2", "desktop:build": "turbo run build --filter desktop2",
"tauri": "tauri" "tauri": "tauri"
}, },
"devDependencies": { "devDependencies": {
"@biomejs/biome": "1.7.3", "@biomejs/biome": "1.7.3",
"@tauri-apps/cli": "2.0.0-beta.12", "@tauri-apps/cli": "2.0.0-beta.20",
"turbo": "^1.13.3" "turbo": "^1.13.3"
}, },
"packageManager": "pnpm@8.9.0", "packageManager": "pnpm@8.9.0",
"engines": { "engines": {
"node": ">=18" "node": ">=18"
}, },
"dependencies": { "dependencies": {
"@tauri-apps/api": "2.0.0-beta.13", "@tauri-apps/api": "2.0.0-beta.13",
"@tauri-apps/plugin-autostart": "2.0.0-beta.3", "@tauri-apps/plugin-clipboard-manager": "2.1.0-beta.3",
"@tauri-apps/plugin-clipboard-manager": "2.1.0-beta.1", "@tauri-apps/plugin-dialog": "2.0.0-beta.5",
"@tauri-apps/plugin-dialog": "2.0.0-beta.3", "@tauri-apps/plugin-fs": "2.0.0-beta.5",
"@tauri-apps/plugin-fs": "2.0.0-beta.3", "@tauri-apps/plugin-http": "2.0.0-beta.5",
"@tauri-apps/plugin-http": "2.0.0-beta.3", "@tauri-apps/plugin-notification": "2.0.0-beta.5",
"@tauri-apps/plugin-notification": "2.0.0-beta.3", "@tauri-apps/plugin-os": "2.0.0-beta.5",
"@tauri-apps/plugin-os": "2.0.0-beta.3", "@tauri-apps/plugin-process": "2.0.0-beta.5",
"@tauri-apps/plugin-process": "2.0.0-beta.3", "@tauri-apps/plugin-shell": "2.0.0-beta.6",
"@tauri-apps/plugin-shell": "2.0.0-beta.4", "@tauri-apps/plugin-updater": "2.0.0-beta.5",
"@tauri-apps/plugin-updater": "2.0.0-beta.3", "@tauri-apps/plugin-upload": "2.0.0-beta.6"
"@tauri-apps/plugin-upload": "2.0.0-beta.4" }
}
} }
+23 -12
View File
@@ -1,5 +1,6 @@
import { Metadata } from "@lume/types"; import { Metadata } from "@lume/types";
import { commands } from "./commands"; import { Result, commands } from "./commands";
import { Window } from "@tauri-apps/api/window";
export class NostrAccount { export class NostrAccount {
static async getAccounts() { static async getAccounts() {
@@ -13,9 +14,19 @@ export class NostrAccount {
} }
static async loadAccount(npub: string) { static async loadAccount(npub: string) {
const query = await commands.loadAccount(npub); const bunker: string = localStorage.getItem(`${npub}_bunker`);
let query: Result<boolean, string>;
if (bunker?.length && bunker?.startsWith("bunker://")) {
query = await commands.loadAccount(npub, bunker);
} else {
query = await commands.loadAccount(npub, null);
}
if (query.status === "ok") { if (query.status === "ok") {
const panel = Window.getByLabel("panel");
panel.emit("load-notification", { account: npub }); // trigger load notification
return query.data; return query.data;
} else { } else {
throw new Error(query.error); throw new Error(query.error);
@@ -62,19 +73,19 @@ export class NostrAccount {
} }
static async connectRemoteAccount(uri: string) { static async connectRemoteAccount(uri: string) {
const remoteKey = uri.replace("bunker://", "").split("?")[0]; const connect = await commands.connectRemoteAccount(uri);
const npub = await commands.toNpub(remoteKey);
if (npub.status === "ok") { if (connect.status === "ok") {
const connect = await commands.nostrConnect(npub.data, uri); const npub = connect.data;
const parsed = new URL(uri);
parsed.searchParams.delete("secret");
if (connect.status === "ok") { // save connection string
return connect.data; localStorage.setItem(`${npub}_bunker`, parsed.toString());
} else {
throw new Error(connect.error); return npub;
}
} else { } else {
throw new Error(npub.error); throw new Error(connect.error);
} }
} }
+21 -20
View File
@@ -1,5 +1,8 @@
// This file was generated by [tauri-specta](https://github.com/oscartbeaumont/tauri-specta). Do not edit this file manually. // This file was generated by [tauri-specta](https://github.com/oscartbeaumont/tauri-specta). Do not edit this file manually.
/** user-defined commands **/
export const commands = { export const commands = {
async getRelays() : Promise<Result<Relays, null>> { async getRelays() : Promise<Result<Relays, null>> {
try { try {
@@ -57,17 +60,17 @@ try {
else return { status: "error", error: e as any }; else return { status: "error", error: e as any };
} }
}, },
async nostrConnect(npub: string, uri: string) : Promise<Result<string, string>> { async connectRemoteAccount(uri: string) : Promise<Result<string, string>> {
try { try {
return { status: "ok", data: await TAURI_INVOKE("nostr_connect", { npub, uri }) }; return { status: "ok", data: await TAURI_INVOKE("connect_remote_account", { uri }) };
} catch (e) { } catch (e) {
if(e instanceof Error) throw e; if(e instanceof Error) throw e;
else return { status: "error", error: e as any }; else return { status: "error", error: e as any };
} }
}, },
async loadAccount(npub: string) : Promise<Result<boolean, string>> { async loadAccount(npub: string, bunker: string | null) : Promise<Result<boolean, string>> {
try { try {
return { status: "ok", data: await TAURI_INVOKE("load_account", { npub }) }; return { status: "ok", data: await TAURI_INVOKE("load_account", { npub, bunker }) };
} catch (e) { } catch (e) {
if(e instanceof Error) throw e; if(e instanceof Error) throw e;
else return { status: "error", error: e as any }; else return { status: "error", error: e as any };
@@ -89,14 +92,6 @@ try {
else return { status: "error", error: e as any }; else return { status: "error", error: e as any };
} }
}, },
async toNpub(hex: string) : Promise<Result<string, null>> {
try {
return { status: "ok", data: await TAURI_INVOKE("to_npub", { hex }) };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async verifyNip05(key: string, nip05: string) : Promise<Result<boolean, string>> { async verifyNip05(key: string, nip05: string) : Promise<Result<boolean, string>> {
try { try {
return { status: "ok", data: await TAURI_INVOKE("verify_nip05", { key, nip05 }) }; return { status: "ok", data: await TAURI_INVOKE("verify_nip05", { key, nip05 }) };
@@ -105,14 +100,6 @@ try {
else return { status: "error", error: e as any }; else return { status: "error", error: e as any };
} }
}, },
async getActivities(account: string, kind: string) : Promise<Result<string[], string>> {
try {
return { status: "ok", data: await TAURI_INVOKE("get_activities", { account, kind }) };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async getCurrentUserProfile() : Promise<Result<string, string>> { async getCurrentUserProfile() : Promise<Result<string, string>> {
try { try {
return { status: "ok", data: await TAURI_INVOKE("get_current_user_profile") }; return { status: "ok", data: await TAURI_INVOKE("get_current_user_profile") };
@@ -233,6 +220,14 @@ try {
else return { status: "error", error: e as any }; else return { status: "error", error: e as any };
} }
}, },
async getNotifications() : Promise<Result<string[], string>> {
try {
return { status: "ok", data: await TAURI_INVOKE("get_notifications") };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async getEvent(id: string) : Promise<Result<string, string>> { async getEvent(id: string) : Promise<Result<string, string>> {
try { try {
return { status: "ok", data: await TAURI_INVOKE("get_event", { id }) }; return { status: "ok", data: await TAURI_INVOKE("get_event", { id }) };
@@ -345,8 +340,14 @@ await TAURI_INVOKE("set_badge", { count });
} }
} }
/** user-defined events **/
/** user-defined statics **/
/** user-defined types **/ /** user-defined types **/
export type Account = { npub: string; nsec: string } export type Account = { npub: string; nsec: string }
+22 -6
View File
@@ -22,7 +22,7 @@ export class LumeEvent {
return this.tags.filter((tag) => tag[0] === "p").map((tag) => tag[1]); return this.tags.filter((tag) => tag[0] === "p").map((tag) => tag[1]);
} }
static getEventThread(tags: string[][], gossip?: boolean) { static getEventThread(tags: string[][], gossip = true) {
let root: string = null; let root: string = null;
let reply: string = null; let reply: string = null;
@@ -30,14 +30,22 @@ export class LumeEvent {
const events = tags.filter((el) => el[0] === "e" && el[3] !== "mention"); const events = tags.filter((el) => el[0] === "e" && el[3] !== "mention");
if (gossip) { if (gossip) {
const relays = tags.filter((el) => el[0] === "e" && el[2]?.length); const relays = tags
.filter((el) => el[0] === "e" && el[2]?.length)
.map((tag) => tag[2]);
if (relays.length >= 1) { if (relays.length >= 1) {
for (const relay of relays) { for (const relay of relays) {
if (relay[2]?.length) try {
commands if (relay.length) {
.connectRelay(relay[2]) const url = new URL(relay);
.then(() => console.log("[gossip]: ", relay[2])); commands
.connectRelay(url.toString())
.then(() => console.log("[relay hint]: ", url));
}
} catch (e) {
console.log("[relay hint] error: ", relay);
}
} }
} }
} }
@@ -82,6 +90,14 @@ export class LumeEvent {
for (const tag of tags) { for (const tag of tags) {
const rootIndex = events.findIndex((el) => el.id === tag[1]); const rootIndex = events.findIndex((el) => el.id === tag[1]);
// Relay Hint
if (tag[2]?.length) {
const url = new URL(tag[2]);
commands
.connectRelay(url.toString())
.then(() => console.log("[relay hint]: ", url));
}
if (rootIndex !== -1) { if (rootIndex !== -1) {
const rootEvent = events[rootIndex]; const rootEvent = events[rootIndex];
+22 -20
View File
@@ -68,6 +68,18 @@ export class NostrQuery {
} }
} }
static async getNotifications() {
const query = await commands.getNotifications();
if (query.status === "ok") {
const events = query.data.map((item) => JSON.parse(item) as NostrEvent);
return events;
} else {
console.error(query.error);
return [];
}
}
static async getProfile(pubkey: string) { static async getProfile(pubkey: string) {
const normalize = pubkey.replace("nostr:", "").replace(/[^\w\s]/gi, ""); const normalize = pubkey.replace("nostr:", "").replace(/[^\w\s]/gi, "");
const query = await commands.getProfile(normalize); const query = await commands.getProfile(normalize);
@@ -104,20 +116,6 @@ export class NostrQuery {
} }
} }
static async getUserActivities(
account: string,
kind: "1" | "6" | "9735" = "1",
) {
const query = await commands.getActivities(account, kind);
if (query.status === "ok") {
const events = query.data.map((item) => JSON.parse(item) as NostrEvent);
return events;
} else {
return [];
}
}
static async getLocalEvents(pubkeys: string[], asOf?: number) { static async getLocalEvents(pubkeys: string[], asOf?: number) {
const until: string = asOf && asOf > 0 ? asOf.toString() : undefined; const until: string = asOf && asOf > 0 ? asOf.toString() : undefined;
const query = await commands.getLocalEvents(pubkeys, until); const query = await commands.getLocalEvents(pubkeys, until);
@@ -242,15 +240,19 @@ export class NostrQuery {
const systemColumns: LumeColumn[] = JSON.parse(resourceFile); const systemColumns: LumeColumn[] = JSON.parse(resourceFile);
const query = await commands.getNstore(NSTORE_KEYS.columns); const query = await commands.getNstore(NSTORE_KEYS.columns);
if (query.status === "ok") { try {
const columns: LumeColumn[] = query.data ? JSON.parse(query.data) : []; if (query.status === "ok") {
const columns: LumeColumn[] = JSON.parse(query.data);
if (columns.length < 1) { if (!columns?.length) {
return systemColumns;
}
return columns;
} else {
return systemColumns; return systemColumns;
} }
} catch {
return columns;
} else {
return systemColumns; return systemColumns;
} }
} }
+1 -1
View File
@@ -92,7 +92,7 @@ export class LumeWindow {
const query = await commands.openWindow( const query = await commands.openWindow(
label, label,
"Settings", "Settings",
"/settings", "/settings/general",
800, 800,
500, 500,
); );
+1 -1
View File
@@ -110,7 +110,7 @@ export interface LumeColumn {
} }
export interface EventColumns { export interface EventColumns {
type: "add" | "remove" | "update" | "left" | "right" | "set_title"; type: "reset" | "add" | "remove" | "update" | "left" | "right" | "set_title";
label?: string; label?: string;
title?: string; title?: string;
column?: LumeColumn; column?: LumeColumn;
+1 -1
View File
@@ -13,7 +13,7 @@
"clsx": "^2.1.1", "clsx": "^2.1.1",
"dayjs": "^1.11.11", "dayjs": "^1.11.11",
"light-bolt11-decoder": "^3.1.1", "light-bolt11-decoder": "^3.1.1",
"nostr-tools": "^2.6.0", "nostr-tools": "^2.7.0",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"slate": "^0.103.0", "slate": "^0.103.0",
+186 -172
View File
@@ -11,46 +11,43 @@ importers:
'@tauri-apps/api': '@tauri-apps/api':
specifier: 2.0.0-beta.13 specifier: 2.0.0-beta.13
version: 2.0.0-beta.13 version: 2.0.0-beta.13
'@tauri-apps/plugin-autostart':
specifier: 2.0.0-beta.3
version: 2.0.0-beta.3
'@tauri-apps/plugin-clipboard-manager': '@tauri-apps/plugin-clipboard-manager':
specifier: 2.1.0-beta.1 specifier: 2.1.0-beta.3
version: 2.1.0-beta.1 version: 2.1.0-beta.3
'@tauri-apps/plugin-dialog': '@tauri-apps/plugin-dialog':
specifier: 2.0.0-beta.3 specifier: 2.0.0-beta.5
version: 2.0.0-beta.3 version: 2.0.0-beta.5
'@tauri-apps/plugin-fs': '@tauri-apps/plugin-fs':
specifier: 2.0.0-beta.3 specifier: 2.0.0-beta.5
version: 2.0.0-beta.3 version: 2.0.0-beta.5
'@tauri-apps/plugin-http': '@tauri-apps/plugin-http':
specifier: 2.0.0-beta.3 specifier: 2.0.0-beta.5
version: 2.0.0-beta.3 version: 2.0.0-beta.5
'@tauri-apps/plugin-notification': '@tauri-apps/plugin-notification':
specifier: 2.0.0-beta.3 specifier: 2.0.0-beta.5
version: 2.0.0-beta.3 version: 2.0.0-beta.5
'@tauri-apps/plugin-os': '@tauri-apps/plugin-os':
specifier: 2.0.0-beta.3 specifier: 2.0.0-beta.5
version: 2.0.0-beta.3 version: 2.0.0-beta.5
'@tauri-apps/plugin-process': '@tauri-apps/plugin-process':
specifier: 2.0.0-beta.3 specifier: 2.0.0-beta.5
version: 2.0.0-beta.3 version: 2.0.0-beta.5
'@tauri-apps/plugin-shell': '@tauri-apps/plugin-shell':
specifier: 2.0.0-beta.4 specifier: 2.0.0-beta.6
version: 2.0.0-beta.4 version: 2.0.0-beta.6
'@tauri-apps/plugin-updater': '@tauri-apps/plugin-updater':
specifier: 2.0.0-beta.3 specifier: 2.0.0-beta.5
version: 2.0.0-beta.3 version: 2.0.0-beta.5
'@tauri-apps/plugin-upload': '@tauri-apps/plugin-upload':
specifier: 2.0.0-beta.4 specifier: 2.0.0-beta.6
version: 2.0.0-beta.4 version: 2.0.0-beta.6
devDependencies: devDependencies:
'@biomejs/biome': '@biomejs/biome':
specifier: 1.7.3 specifier: 1.7.3
version: 1.7.3 version: 1.7.3
'@tauri-apps/cli': '@tauri-apps/cli':
specifier: 2.0.0-beta.12 specifier: 2.0.0-beta.20
version: 2.0.0-beta.12 version: 2.0.0-beta.20
turbo: turbo:
specifier: ^1.13.3 specifier: ^1.13.3
version: 1.13.3 version: 1.13.3
@@ -93,6 +90,9 @@ importers:
'@radix-ui/react-switch': '@radix-ui/react-switch':
specifier: ^1.0.3 specifier: ^1.0.3
version: 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1) version: 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
'@radix-ui/react-tabs':
specifier: ^1.0.4
version: 1.0.4(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
'@radix-ui/react-tooltip': '@radix-ui/react-tooltip':
specifier: ^1.0.7 specifier: ^1.0.7
version: 1.0.7(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1) version: 1.0.7(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
@@ -106,8 +106,8 @@ importers:
specifier: ^5.40.0 specifier: ^5.40.0
version: 5.40.0(@tanstack/react-query@5.40.0)(react@18.3.1) version: 5.40.0(@tanstack/react-query@5.40.0)(react@18.3.1)
'@tanstack/react-router': '@tanstack/react-router':
specifier: ^1.34.5 specifier: ^1.34.9
version: 1.34.5(react-dom@18.3.1)(react@18.3.1) version: 1.34.9(react-dom@18.3.1)(react@18.3.1)
i18next: i18next:
specifier: ^23.11.5 specifier: ^23.11.5
version: 23.11.5 version: 23.11.5
@@ -120,9 +120,6 @@ importers:
nanoid: nanoid:
specifier: ^5.0.7 specifier: ^5.0.7
version: 5.0.7 version: 5.0.7
nostr-tools:
specifier: ^2.6.0
version: 2.6.0(typescript@5.4.5)
react: react:
specifier: ^18.3.1 specifier: ^18.3.1
version: 18.3.1 version: 18.3.1
@@ -170,11 +167,11 @@ importers:
specifier: workspace:^ specifier: workspace:^
version: link:../../packages/types version: link:../../packages/types
'@tanstack/router-devtools': '@tanstack/router-devtools':
specifier: ^1.34.5 specifier: ^1.34.9
version: 1.34.5(@tanstack/react-router@1.34.5)(csstype@3.1.3)(react-dom@18.3.1)(react@18.3.1) version: 1.34.9(@tanstack/react-router@1.34.9)(csstype@3.1.3)(react-dom@18.3.1)(react@18.3.1)
'@tanstack/router-vite-plugin': '@tanstack/router-vite-plugin':
specifier: ^1.34.1 specifier: ^1.34.8
version: 1.34.1(vite@5.2.12) version: 1.34.8(vite@5.2.12)
'@types/react': '@types/react':
specifier: ^18.3.3 specifier: ^18.3.3
version: 18.3.3 version: 18.3.3
@@ -365,8 +362,8 @@ importers:
specifier: ^3.1.1 specifier: ^3.1.1
version: 3.1.1 version: 3.1.1
nostr-tools: nostr-tools:
specifier: ^2.6.0 specifier: ^2.7.0
version: 2.6.0(typescript@5.4.5) version: 2.7.0(typescript@5.4.5)
react: react:
specifier: ^18.3.1 specifier: ^18.3.1
version: 18.3.1 version: 18.3.1
@@ -521,7 +518,7 @@ packages:
engines: {node: ^18.17.1 || ^20.3.0 || >=21.0.0} engines: {node: ^18.17.1 || ^20.3.0 || >=21.0.0}
dependencies: dependencies:
ci-info: 4.0.0 ci-info: 4.0.0
debug: 4.3.4 debug: 4.3.5
dlv: 1.1.3 dlv: 1.1.3
dset: 3.1.3 dset: 3.1.3
is-docker: 3.0.0 is-docker: 3.0.0
@@ -557,7 +554,7 @@ packages:
'@babel/traverse': 7.24.6 '@babel/traverse': 7.24.6
'@babel/types': 7.24.6 '@babel/types': 7.24.6
convert-source-map: 2.0.0 convert-source-map: 2.0.0
debug: 4.3.4 debug: 4.3.5
gensync: 1.0.0-beta.2 gensync: 1.0.0-beta.2
json5: 2.2.3 json5: 2.2.3
semver: 6.3.1 semver: 6.3.1
@@ -818,7 +815,7 @@ packages:
'@babel/helper-split-export-declaration': 7.24.6 '@babel/helper-split-export-declaration': 7.24.6
'@babel/parser': 7.24.6 '@babel/parser': 7.24.6
'@babel/types': 7.24.6 '@babel/types': 7.24.6
debug: 4.3.4 debug: 4.3.5
globals: 11.12.0 globals: 11.12.0
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@@ -2256,6 +2253,34 @@ packages:
react-dom: 18.3.1(react@18.3.1) react-dom: 18.3.1(react@18.3.1)
dev: false dev: false
/@radix-ui/react-tabs@1.0.4(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-egZfYY/+wRNCflXNHx+dePvnz9FbmssDTJBtgRfDY7e8SE5oIo3Py2eCB1ckAbh1Q7cQ/6yJZThJ++sgbxibog==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0
react-dom: ^16.8 || ^17.0 || ^18.0
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
dependencies:
'@babel/runtime': 7.24.6
'@radix-ui/primitive': 1.0.1
'@radix-ui/react-context': 1.0.1(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-direction': 1.0.1(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-id': 1.0.1(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-presence': 1.0.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
'@radix-ui/react-roving-focus': 1.0.4(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
'@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.3.3)(react@18.3.1)
'@types/react': 18.3.3
'@types/react-dom': 18.3.0
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
dev: false
/@radix-ui/react-tooltip@1.0.7(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1): /@radix-ui/react-tooltip@1.0.7(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-lPh5iKNFVQ/jav/j6ZrWq3blfDJ0OH9R6FlNUHPMqdLuQ9vwDgFsRxvl8b7Asuy5c8xmoojHUxKHQSOAvMHxyw==} resolution: {integrity: sha512-lPh5iKNFVQ/jav/j6ZrWq3blfDJ0OH9R6FlNUHPMqdLuQ9vwDgFsRxvl8b7Asuy5c8xmoojHUxKHQSOAvMHxyw==}
peerDependencies: peerDependencies:
@@ -2562,8 +2587,8 @@ packages:
resolution: {integrity: sha512-CqYyepN4SnBopaoXYwng4NO8riB5ask/LTCkhOFq+GNGtr2X+aKeD767eYdqYukeixEUvv4bXdyTYVaogj7KBw==} resolution: {integrity: sha512-CqYyepN4SnBopaoXYwng4NO8riB5ask/LTCkhOFq+GNGtr2X+aKeD767eYdqYukeixEUvv4bXdyTYVaogj7KBw==}
dev: false dev: false
/@swc/core-darwin-arm64@1.5.7: /@swc/core-darwin-arm64@1.5.24:
resolution: {integrity: sha512-bZLVHPTpH3h6yhwVl395k0Mtx8v6CGhq5r4KQdAoPbADU974Mauz1b6ViHAJ74O0IVE5vyy7tD3OpkQxL/vMDQ==} resolution: {integrity: sha512-M7oLOcC0sw+UTyAuL/9uyB9GeO4ZpaBbH76JSH6g1m0/yg7LYJZGRmplhDmwVSDAR5Fq4Sjoi1CksmmGkgihGA==}
engines: {node: '>=10'} engines: {node: '>=10'}
cpu: [arm64] cpu: [arm64]
os: [darwin] os: [darwin]
@@ -2571,8 +2596,8 @@ packages:
dev: true dev: true
optional: true optional: true
/@swc/core-darwin-x64@1.5.7: /@swc/core-darwin-x64@1.5.24:
resolution: {integrity: sha512-RpUyu2GsviwTc2qVajPL0l8nf2vKj5wzO3WkLSHAHEJbiUZk83NJrZd1RVbEknIMO7+Uyjh54hEh8R26jSByaw==} resolution: {integrity: sha512-MfcFjGGYognpSBSos2pYUNYJSmqEhuw5ceGr6qAdME7ddbjGXliza4W6FggsM+JnWwpqa31+e7/R+GetW4WkaQ==}
engines: {node: '>=10'} engines: {node: '>=10'}
cpu: [x64] cpu: [x64]
os: [darwin] os: [darwin]
@@ -2580,8 +2605,8 @@ packages:
dev: true dev: true
optional: true optional: true
/@swc/core-linux-arm-gnueabihf@1.5.7: /@swc/core-linux-arm-gnueabihf@1.5.24:
resolution: {integrity: sha512-cTZWTnCXLABOuvWiv6nQQM0hP6ZWEkzdgDvztgHI/+u/MvtzJBN5lBQ2lue/9sSFYLMqzqff5EHKlFtrJCA9dQ==} resolution: {integrity: sha512-amI2pwtcWV3E/m/nf+AQtn1LWDzKLZyjCmWd3ms7QjEueWYrY8cU1Y4Wp7wNNsxIoPOi8zek1Uj2wwFD/pttNQ==}
engines: {node: '>=10'} engines: {node: '>=10'}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
@@ -2589,8 +2614,8 @@ packages:
dev: true dev: true
optional: true optional: true
/@swc/core-linux-arm64-gnu@1.5.7: /@swc/core-linux-arm64-gnu@1.5.24:
resolution: {integrity: sha512-hoeTJFBiE/IJP30Be7djWF8Q5KVgkbDtjySmvYLg9P94bHg9TJPSQoC72tXx/oXOgXvElDe/GMybru0UxhKx4g==} resolution: {integrity: sha512-sTSvmqMmgT1ynH/nP75Pc51s+iT4crZagHBiDOf5cq+kudUYjda9lWMs7xkXB/TUKFHPCRK0HGunl8bkwiIbuw==}
engines: {node: '>=10'} engines: {node: '>=10'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
@@ -2598,8 +2623,8 @@ packages:
dev: true dev: true
optional: true optional: true
/@swc/core-linux-arm64-musl@1.5.7: /@swc/core-linux-arm64-musl@1.5.24:
resolution: {integrity: sha512-+NDhK+IFTiVK1/o7EXdCeF2hEzCiaRSrb9zD7X2Z7inwWlxAntcSuzZW7Y6BRqGQH89KA91qYgwbnjgTQ22PiQ==} resolution: {integrity: sha512-vd2/hfOBGbrX21FxsFdXCUaffjkHvlZkeE2UMRajdXifwv79jqOHIJg3jXG1F3ZrhCghCzirFts4tAZgcG8XWg==}
engines: {node: '>=10'} engines: {node: '>=10'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
@@ -2607,8 +2632,8 @@ packages:
dev: true dev: true
optional: true optional: true
/@swc/core-linux-x64-gnu@1.5.7: /@swc/core-linux-x64-gnu@1.5.24:
resolution: {integrity: sha512-25GXpJmeFxKB+7pbY7YQLhWWjkYlR+kHz5I3j9WRl3Lp4v4UD67OGXwPe+DIcHqcouA1fhLhsgHJWtsaNOMBNg==} resolution: {integrity: sha512-Zrdzi7NqzQxm2BvAG5KyOSBEggQ7ayrxh599AqqevJmsUXJ8o2nMiWQOBvgCGp7ye+Biz3pvZn1EnRzAp+TpUg==}
engines: {node: '>=10'} engines: {node: '>=10'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
@@ -2616,8 +2641,8 @@ packages:
dev: true dev: true
optional: true optional: true
/@swc/core-linux-x64-musl@1.5.7: /@swc/core-linux-x64-musl@1.5.24:
resolution: {integrity: sha512-0VN9Y5EAPBESmSPPsCJzplZHV26akC0sIgd3Hc/7S/1GkSMoeuVL+V9vt+F/cCuzr4VidzSkqftdP3qEIsXSpg==} resolution: {integrity: sha512-1F8z9NRi52jdZQCGc5sflwYSctL6omxiVmIFVp8TC9nngjQKc00TtX/JC2Eo2HwvgupkFVl5YQJidAck9YtmJw==}
engines: {node: '>=10'} engines: {node: '>=10'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
@@ -2625,8 +2650,8 @@ packages:
dev: true dev: true
optional: true optional: true
/@swc/core-win32-arm64-msvc@1.5.7: /@swc/core-win32-arm64-msvc@1.5.24:
resolution: {integrity: sha512-RtoNnstBwy5VloNCvmvYNApkTmuCe4sNcoYWpmY7C1+bPR+6SOo8im1G6/FpNem8AR5fcZCmXHWQ+EUmRWJyuA==} resolution: {integrity: sha512-cKpP7KvS6Xr0jFSTBXY53HZX/YfomK5EMQYpCVDOvfsZeYHN20sQSKXfpVLvA/q2igVt1zzy1XJcOhpJcgiKLg==}
engines: {node: '>=10'} engines: {node: '>=10'}
cpu: [arm64] cpu: [arm64]
os: [win32] os: [win32]
@@ -2634,8 +2659,8 @@ packages:
dev: true dev: true
optional: true optional: true
/@swc/core-win32-ia32-msvc@1.5.7: /@swc/core-win32-ia32-msvc@1.5.24:
resolution: {integrity: sha512-Xm0TfvcmmspvQg1s4+USL3x8D+YPAfX2JHygvxAnCJ0EHun8cm2zvfNBcsTlnwYb0ybFWXXY129aq1wgFC9TpQ==} resolution: {integrity: sha512-IoPWfi0iwqjZuf7gE223+B97/ZwkKbu7qL5KzGP7g3hJrGSKAvv7eC5Y9r2iKKtLKyv5R/T6Ho0kFR/usi7rHw==}
engines: {node: '>=10'} engines: {node: '>=10'}
cpu: [ia32] cpu: [ia32]
os: [win32] os: [win32]
@@ -2643,8 +2668,8 @@ packages:
dev: true dev: true
optional: true optional: true
/@swc/core-win32-x64-msvc@1.5.7: /@swc/core-win32-x64-msvc@1.5.24:
resolution: {integrity: sha512-tp43WfJLCsKLQKBmjmY/0vv1slVywR5Q4qKjF5OIY8QijaEW7/8VwPyUyVoJZEnDgv9jKtUTG5PzqtIYPZGnyg==} resolution: {integrity: sha512-zHgF2k1uVJL8KIW+PnVz1To4a3Cz9THbh2z2lbehaF/gKHugH4c3djBozU4das1v35KOqf5jWIEviBLql2wDLQ==}
engines: {node: '>=10'} engines: {node: '>=10'}
cpu: [x64] cpu: [x64]
os: [win32] os: [win32]
@@ -2652,12 +2677,12 @@ packages:
dev: true dev: true
optional: true optional: true
/@swc/core@1.5.7: /@swc/core@1.5.24:
resolution: {integrity: sha512-U4qJRBefIJNJDRCCiVtkfa/hpiZ7w0R6kASea+/KLp+vkus3zcLSB8Ub8SvKgTIxjWpwsKcZlPf5nrv4ls46SQ==} resolution: {integrity: sha512-Eph9zvO4xvqWZGVzTdtdEJ0Vqf0VIML/o/e4Qd2RLOqtfgnlRi7avmMu5C0oqciJ0tk+hqdUKVUZ4JPoPaiGvQ==}
engines: {node: '>=10'} engines: {node: '>=10'}
requiresBuild: true requiresBuild: true
peerDependencies: peerDependencies:
'@swc/helpers': ^0.5.0 '@swc/helpers': '*'
peerDependenciesMeta: peerDependenciesMeta:
'@swc/helpers': '@swc/helpers':
optional: true optional: true
@@ -2665,16 +2690,16 @@ packages:
'@swc/counter': 0.1.3 '@swc/counter': 0.1.3
'@swc/types': 0.1.7 '@swc/types': 0.1.7
optionalDependencies: optionalDependencies:
'@swc/core-darwin-arm64': 1.5.7 '@swc/core-darwin-arm64': 1.5.24
'@swc/core-darwin-x64': 1.5.7 '@swc/core-darwin-x64': 1.5.24
'@swc/core-linux-arm-gnueabihf': 1.5.7 '@swc/core-linux-arm-gnueabihf': 1.5.24
'@swc/core-linux-arm64-gnu': 1.5.7 '@swc/core-linux-arm64-gnu': 1.5.24
'@swc/core-linux-arm64-musl': 1.5.7 '@swc/core-linux-arm64-musl': 1.5.24
'@swc/core-linux-x64-gnu': 1.5.7 '@swc/core-linux-x64-gnu': 1.5.24
'@swc/core-linux-x64-musl': 1.5.7 '@swc/core-linux-x64-musl': 1.5.24
'@swc/core-win32-arm64-msvc': 1.5.7 '@swc/core-win32-arm64-msvc': 1.5.24
'@swc/core-win32-ia32-msvc': 1.5.7 '@swc/core-win32-ia32-msvc': 1.5.24
'@swc/core-win32-x64-msvc': 1.5.7 '@swc/core-win32-x64-msvc': 1.5.24
dev: true dev: true
/@swc/counter@0.1.3: /@swc/counter@0.1.3:
@@ -2749,8 +2774,8 @@ packages:
react: 18.3.1 react: 18.3.1
dev: false dev: false
/@tanstack/react-router@1.34.5(react-dom@18.3.1)(react@18.3.1): /@tanstack/react-router@1.34.9(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-mOMbNHSJ1cAgRuJj9W35wteQL7zFiCNJYgg3QHkxj+obO9zQWiAwycFs0hQTRxqzGfC+jhVLJe1+cW93BhqKyA==} resolution: {integrity: sha512-dU7rkiWQTH7glJIcnUcFezkmANzqQwlgRiJFiHJOPjYSoKVGI9dWQMbNc8W5d4JjXB/Nhh0QOiI/+GoYZ3WrhA==}
engines: {node: '>=12'} engines: {node: '>=12'}
peerDependencies: peerDependencies:
react: '>=16.8' react: '>=16.8'
@@ -2774,15 +2799,15 @@ packages:
react-dom: 18.3.1(react@18.3.1) react-dom: 18.3.1(react@18.3.1)
use-sync-external-store: 1.2.2(react@18.3.1) use-sync-external-store: 1.2.2(react@18.3.1)
/@tanstack/router-devtools@1.34.5(@tanstack/react-router@1.34.5)(csstype@3.1.3)(react-dom@18.3.1)(react@18.3.1): /@tanstack/router-devtools@1.34.9(@tanstack/react-router@1.34.9)(csstype@3.1.3)(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-ps0SC14ijFkYkmvqRDlIo7M182Kinvs9syNbQJ5NZT3HjC4yNSZpKWIFkEEDL9IhQvIXQbk0FROz/c7Us7VpsA==} resolution: {integrity: sha512-8gc/ptUjiB/ry1jWCBcPNypfpfQ/eFtC4vqg8dfppvKUm2DwLp0gNM6YvGbcfySTLLei3RLPlmuob0/2vbeS7A==}
engines: {node: '>=12'} engines: {node: '>=12'}
peerDependencies: peerDependencies:
'@tanstack/react-router': ^1.34.5 '@tanstack/react-router': ^1.34.9
react: '>=16.8' react: '>=16.8'
react-dom: '>=16.8' react-dom: '>=16.8'
dependencies: dependencies:
'@tanstack/react-router': 1.34.5(react-dom@18.3.1)(react@18.3.1) '@tanstack/react-router': 1.34.9(react-dom@18.3.1)(react@18.3.1)
clsx: 2.1.1 clsx: 2.1.1
date-fns: 2.30.0 date-fns: 2.30.0
goober: 2.1.14(csstype@3.1.3) goober: 2.1.14(csstype@3.1.3)
@@ -2792,16 +2817,16 @@ packages:
- csstype - csstype
dev: true dev: true
/@tanstack/router-generator@1.33.12: /@tanstack/router-generator@1.34.8:
resolution: {integrity: sha512-CTZ9yWJkmV4hmVqZnqlrTBfAnjqkWkqF0V5kRdNNK7Q8mfUhL5WPAdZfrCVhq7uBFDEJ1XyMVwn2lQytAMrHnA==} resolution: {integrity: sha512-xi0otLis4zQ8lYVgvNfUTDHKnboL2swXpHcLiG+bKYDihPnw4r3qRO0ULGsqV1n/w6yZAaQ2tZwvZ7Zf0G2pPg==}
engines: {node: '>=12'} engines: {node: '>=12'}
dependencies: dependencies:
prettier: 3.2.5 prettier: 3.2.5
zod: 3.23.8 zod: 3.23.8
dev: true dev: true
/@tanstack/router-vite-plugin@1.34.1(vite@5.2.12): /@tanstack/router-vite-plugin@1.34.8(vite@5.2.12):
resolution: {integrity: sha512-UtSDFKdDzhiFuUUHQp+/bUYkTLnJeYnVXEdXo+D7rUH12i9WFpbZY5dcG3Nj/c8y4WTljwtE6e8N/+yAyXYKeg==} resolution: {integrity: sha512-FZRl7GVda/1NMEbnalXhYI2e9Qe8MduxXRNZ2cJMYgPR/FxMiao+XQAN+mTD4OoMxJ8gKY+IshgRxCc3xwu6bA==}
engines: {node: '>=12'} engines: {node: '>=12'}
dependencies: dependencies:
'@babel/core': 7.24.6 '@babel/core': 7.24.6
@@ -2813,7 +2838,7 @@ packages:
'@babel/template': 7.24.6 '@babel/template': 7.24.6
'@babel/traverse': 7.24.6 '@babel/traverse': 7.24.6
'@babel/types': 7.24.6 '@babel/types': 7.24.6
'@tanstack/router-generator': 1.33.12 '@tanstack/router-generator': 1.34.8
'@types/babel__core': 7.20.5 '@types/babel__core': 7.20.5
'@types/babel__generator': 7.6.8 '@types/babel__generator': 7.6.8
'@types/babel__template': 7.4.4 '@types/babel__template': 7.4.4
@@ -2828,18 +2853,13 @@ packages:
/@tanstack/store@0.1.3: /@tanstack/store@0.1.3:
resolution: {integrity: sha512-GnolmC8Fr4mvsHE1fGQmR3Nm0eBO3KnZjDU0a+P3TeQNM/dDscFGxtA7p31NplQNW3KwBw4t1RVFmz0VeKLxcw==} resolution: {integrity: sha512-GnolmC8Fr4mvsHE1fGQmR3Nm0eBO3KnZjDU0a+P3TeQNM/dDscFGxtA7p31NplQNW3KwBw4t1RVFmz0VeKLxcw==}
/@tauri-apps/api@2.0.0-beta.11:
resolution: {integrity: sha512-wJRY+fBUm3KpqZDHMIz5HRv+1vlnvRJ/dFxiyY3NlINTx2qXqDou5qWYcP1CuZXsd39InWVPV3FAZvno/kGCkA==}
engines: {node: '>= 18', npm: '>= 6.6.0', yarn: '>= 1.19.1'}
dev: false
/@tauri-apps/api@2.0.0-beta.13: /@tauri-apps/api@2.0.0-beta.13:
resolution: {integrity: sha512-Np1opKANzRMF3lgJ9gDquBCB9SxlE2lRmNpVx1+L6RyzAmigkuh0ZulT5jMnDA3JLsuSDU135r/s4t/Pmx4atg==} resolution: {integrity: sha512-Np1opKANzRMF3lgJ9gDquBCB9SxlE2lRmNpVx1+L6RyzAmigkuh0ZulT5jMnDA3JLsuSDU135r/s4t/Pmx4atg==}
engines: {node: '>= 18', npm: '>= 6.6.0', yarn: '>= 1.19.1'} engines: {node: '>= 18', npm: '>= 6.6.0', yarn: '>= 1.19.1'}
dev: false dev: false
/@tauri-apps/cli-darwin-arm64@2.0.0-beta.12: /@tauri-apps/cli-darwin-arm64@2.0.0-beta.20:
resolution: {integrity: sha512-+Ksrxc4QQE1R62OreCybM8PNnF090qhf+ZeRZTZ9JxOujz4Tq/RiE1vC5nRlDu+Cd3pL63fZ2TsANj2PnpN+qw==} resolution: {integrity: sha512-oCJOCib7GuYkwkBXx+ekamR8NZZU+2i3MLP+DHpDxK5gS2uhCE+CBkamJkNt6y1x6xdVnwyqZOm5RvN4SRtyIA==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [darwin] os: [darwin]
@@ -2847,8 +2867,8 @@ packages:
dev: true dev: true
optional: true optional: true
/@tauri-apps/cli-darwin-x64@2.0.0-beta.12: /@tauri-apps/cli-darwin-x64@2.0.0-beta.20:
resolution: {integrity: sha512-KEkcJp7jfWMEiRFTJ3/yJhy8EFwgJPNGnNYjKTD5CAba9wMexxs23U7YLAWdSNrbSgPAox0pBqXaSj6p2HH0Wg==} resolution: {integrity: sha512-lC5QSnRExedYN4Ds6ZlSvC2PxP8qfIYBJQ5ktf+PJI5gQALdNeVtd6YnTG1ODCEklfLq9WKkGwp7JdALTU5wDA==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [darwin] os: [darwin]
@@ -2856,8 +2876,8 @@ packages:
dev: true dev: true
optional: true optional: true
/@tauri-apps/cli-linux-arm-gnueabihf@2.0.0-beta.12: /@tauri-apps/cli-linux-arm-gnueabihf@2.0.0-beta.20:
resolution: {integrity: sha512-EUDen1XNF/WfG5hjTZ6P/8IcpYPFm2Ox/FwlBPlePKZJrc+/3IveME/oyC3wrloscodV41yL2ise4SZuJtNAHA==} resolution: {integrity: sha512-nZCeBMHHye5DLOJV5k2w658hnCS+LYaOZ8y/G9l3ei+g0L/HBjlSy6r4simsAT5TG8+l3oCZzLBngfTMdDS/YA==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
@@ -2865,8 +2885,8 @@ packages:
dev: true dev: true
optional: true optional: true
/@tauri-apps/cli-linux-arm64-gnu@2.0.0-beta.12: /@tauri-apps/cli-linux-arm64-gnu@2.0.0-beta.20:
resolution: {integrity: sha512-VUhCBB6kaQmi7MyXTRccqemmz7s5n15Z718OIW1n6wI68oh0IsvC9KsrbDfgOLZVG6RXzmtzyXwnaIFQAgl+Cg==} resolution: {integrity: sha512-B79ISVLPVBgwnCchVqwTKU+vxnFYqxKomcR4rmsvxfs0NVtT5QuNzE1k4NUQnw3966yjwhYR3mnHsSJQSB4Eyw==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
@@ -2874,8 +2894,8 @@ packages:
dev: true dev: true
optional: true optional: true
/@tauri-apps/cli-linux-arm64-musl@2.0.0-beta.12: /@tauri-apps/cli-linux-arm64-musl@2.0.0-beta.20:
resolution: {integrity: sha512-eus/fGCQZPcvwEa2HH2TJh1Xb2FeeVrnxL6lQseLPkgD7Wcu5mWz07AlLCMDOreUobb5vyOmm0L7d1HMIPrvEQ==} resolution: {integrity: sha512-ojIkv/1uZHhcrgfIN8xgn4BBeo/Xg+bnV0wer6lD78zyxkUMWeEZ+u3mae1ejCJNhhaZOxNaUQ67MvDOiGyr5Q==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
@@ -2883,8 +2903,8 @@ packages:
dev: true dev: true
optional: true optional: true
/@tauri-apps/cli-linux-x64-gnu@2.0.0-beta.12: /@tauri-apps/cli-linux-x64-gnu@2.0.0-beta.20:
resolution: {integrity: sha512-C4SkDnjuhFSdSgsK8KNRj2b/oe/j1vrvI8+ZxUj+/o1jK71A2eiCN8pJlyMZYCN8wZUksGazxaFz62g0jceAEg==} resolution: {integrity: sha512-xBy1FNbHKlc7T6pOmFQQPECxJaI5A9QWX7Kb9N64cNVusoOGlvc3xHYkXMS4PTr7xXOT0yiE1Ww2OwDRJ3lYsg==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
@@ -2892,8 +2912,8 @@ packages:
dev: true dev: true
optional: true optional: true
/@tauri-apps/cli-linux-x64-musl@2.0.0-beta.12: /@tauri-apps/cli-linux-x64-musl@2.0.0-beta.20:
resolution: {integrity: sha512-V8NlJ8wgNcMfKTGOubgfjRyyJVQsZxpOZkGjdfn/YK0UNdOC6iLuqxIki47hbnoJMqvuxa37lr7Z1JVawOMUyw==} resolution: {integrity: sha512-+O6zq5jmtUxA1FUAAwF2ywPysy4NRo2Y6G+ESZDkY9XosRwdt5OUjqAsYktZA3AxDMZVei8r9buwTqUwi9ny/g==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
@@ -2901,8 +2921,8 @@ packages:
dev: true dev: true
optional: true optional: true
/@tauri-apps/cli-win32-arm64-msvc@2.0.0-beta.12: /@tauri-apps/cli-win32-arm64-msvc@2.0.0-beta.20:
resolution: {integrity: sha512-dIsE5U4JRoVImW0PX9K82/dMjwugqc3DmgKbFKLYXgTFmSNaTNdojI/5VOPmbOIMJ8BNGDF8sjS80I0PZuEqvw==} resolution: {integrity: sha512-RswgMbWyOQcv53CHvIuiuhAh4kKDqaGyZfWD4VlxqX/XhkoF5gsNgr0MxzrY7pmoL+89oVI+fiGVJz4nOQE5vA==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [win32] os: [win32]
@@ -2910,8 +2930,8 @@ packages:
dev: true dev: true
optional: true optional: true
/@tauri-apps/cli-win32-ia32-msvc@2.0.0-beta.12: /@tauri-apps/cli-win32-ia32-msvc@2.0.0-beta.20:
resolution: {integrity: sha512-vTNrGzVV/LclD+4u+IOOwl1Ia2CqwZYK6AmMLp0ROLzbSn/9ROJJYe47V5VAZrnMjThEdb9fRL0FPYPk9yDaNA==} resolution: {integrity: sha512-5lgWmDVXhX3SBGbiv5SduM1yajiRnUEJClWhSdRrEEJeXdsxpCsBEhxYnUnDCEzPKxLLn5fdBv3VrVctJ03csQ==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [ia32] cpu: [ia32]
os: [win32] os: [win32]
@@ -2919,8 +2939,8 @@ packages:
dev: true dev: true
optional: true optional: true
/@tauri-apps/cli-win32-x64-msvc@2.0.0-beta.12: /@tauri-apps/cli-win32-x64-msvc@2.0.0-beta.20:
resolution: {integrity: sha512-Z/++lJ1FraoN1ZaNxYuCynNm7SyEGC+yd/adYQvhaQyudZa5HW/8eFkLx8FRcIQkEkSqLBkHf9VpwjnTDBVlwQ==} resolution: {integrity: sha512-SuSiiVQTQPSzWlsxQp/NMzWbzDS9TdVDOw7CCfgiG5wnT2GsxzrcIAVN6i7ILsVFLxrjr0bIgPldSJcdcH84Yw==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [win32] os: [win32]
@@ -2928,87 +2948,81 @@ packages:
dev: true dev: true
optional: true optional: true
/@tauri-apps/cli@2.0.0-beta.12: /@tauri-apps/cli@2.0.0-beta.20:
resolution: {integrity: sha512-MFh4Z093O+PXWI6KFR8E2zIOgpJ4zL8waDhFXVQgBpqiD4ieiqywjmbmNIWkVqYBGAia7ZI0juxpkZSyXT4f1A==} resolution: {integrity: sha512-707q9uIc2oNrYHd2dtMvxTrpZXVpart5EIktnRymNOpphkLlB6WUBjHD+ga45WqTU6cNGKbYvkKqTNfshNul9Q==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
hasBin: true hasBin: true
optionalDependencies: optionalDependencies:
'@tauri-apps/cli-darwin-arm64': 2.0.0-beta.12 '@tauri-apps/cli-darwin-arm64': 2.0.0-beta.20
'@tauri-apps/cli-darwin-x64': 2.0.0-beta.12 '@tauri-apps/cli-darwin-x64': 2.0.0-beta.20
'@tauri-apps/cli-linux-arm-gnueabihf': 2.0.0-beta.12 '@tauri-apps/cli-linux-arm-gnueabihf': 2.0.0-beta.20
'@tauri-apps/cli-linux-arm64-gnu': 2.0.0-beta.12 '@tauri-apps/cli-linux-arm64-gnu': 2.0.0-beta.20
'@tauri-apps/cli-linux-arm64-musl': 2.0.0-beta.12 '@tauri-apps/cli-linux-arm64-musl': 2.0.0-beta.20
'@tauri-apps/cli-linux-x64-gnu': 2.0.0-beta.12 '@tauri-apps/cli-linux-x64-gnu': 2.0.0-beta.20
'@tauri-apps/cli-linux-x64-musl': 2.0.0-beta.12 '@tauri-apps/cli-linux-x64-musl': 2.0.0-beta.20
'@tauri-apps/cli-win32-arm64-msvc': 2.0.0-beta.12 '@tauri-apps/cli-win32-arm64-msvc': 2.0.0-beta.20
'@tauri-apps/cli-win32-ia32-msvc': 2.0.0-beta.12 '@tauri-apps/cli-win32-ia32-msvc': 2.0.0-beta.20
'@tauri-apps/cli-win32-x64-msvc': 2.0.0-beta.12 '@tauri-apps/cli-win32-x64-msvc': 2.0.0-beta.20
dev: true dev: true
/@tauri-apps/plugin-autostart@2.0.0-beta.3: /@tauri-apps/plugin-clipboard-manager@2.1.0-beta.3:
resolution: {integrity: sha512-+qIHvO+cfe8MnRxa23l5ebF5rZMt6Jd7FjCD9zoVm/KdYpb0VIhixsYvrLjty9eUWcp8k3XVknfttnvw/veHmg==} resolution: {integrity: sha512-j48jTdZWeX5BylIGLYbXtg6ynZAcFKDKK6dguHAbG3zviqNE/XMf2vHOPJeFDZsDE5kVOZZS7nKOj7T2WiCpvw==}
dependencies: dependencies:
'@tauri-apps/api': 2.0.0-beta.11 '@tauri-apps/api': 2.0.0-beta.13
dev: false dev: false
/@tauri-apps/plugin-clipboard-manager@2.1.0-beta.1: /@tauri-apps/plugin-dialog@2.0.0-beta.5:
resolution: {integrity: sha512-nipr29/eQVlgNw6A1futwsFwWeeS0tBbq1Pww7yb+IZDKfhIFXYJ1NmrEcVGQ6vYz4KPIu+llR6D9pjKl/vSvA==} resolution: {integrity: sha512-jkaBCsx2v6WB6sB77fTMCeijuvT3FlzwschiHnPlD7aU6CHvQgRlpCv/FttPdTq4ih2t6MIlM4oX85hNYgfs6w==}
dependencies: dependencies:
'@tauri-apps/api': 2.0.0-beta.11 '@tauri-apps/api': 2.0.0-beta.13
dev: false dev: false
/@tauri-apps/plugin-dialog@2.0.0-beta.3: /@tauri-apps/plugin-fs@2.0.0-beta.5:
resolution: {integrity: sha512-B3KxLjFxCVB3AIYWN/xY9HM6tO7m4u6aQk1t0ZrBToXThowRU9f62527EO0oj0uNlyKlXx20up68NkpdlER0yg==} resolution: {integrity: sha512-uTqCDFA1z8KDtTe5fKlbRrKV4zxh63UVUvD/PR8GnyNLV+qxj/fEuJuGvMdfMbVKrTljGqSpun5wnP5jqD5fMg==}
dependencies: dependencies:
'@tauri-apps/api': 2.0.0-beta.11 '@tauri-apps/api': 2.0.0-beta.13
dev: false dev: false
/@tauri-apps/plugin-fs@2.0.0-beta.3: /@tauri-apps/plugin-http@2.0.0-beta.5:
resolution: {integrity: sha512-LBgA7S10NwcitHaugIfmCSkewz45vSz1VOpMHhzvE38i1r1KpuTSHlr3MZ0LLq93tH/lvhYZ+3LAml4Sriwthw==} resolution: {integrity: sha512-zRWWWCNw/TdewCSrBY8Racm2dwMpkzMOnFmHGExEIoklqUUIq3FU/hkqcX/67Nplf+t1y+WljOxKpCHbm1fOQw==}
dependencies: dependencies:
'@tauri-apps/api': 2.0.0-beta.11 '@tauri-apps/api': 2.0.0-beta.13
dev: false dev: false
/@tauri-apps/plugin-http@2.0.0-beta.3: /@tauri-apps/plugin-notification@2.0.0-beta.5:
resolution: {integrity: sha512-TsSFQje2F3Sy5FYuOV+TcgtlvpO9FVdODGT/dJQa+kq/a8Ocel6V57Emj2FiCIyhOJ84us+MiMbQWR1GTfa+mQ==} resolution: {integrity: sha512-5zYK2aT1ZvR+LnuwsnTvg28iEhI7FiZSPvBqQ8y6fj28T4oIHVox19Pk4YRyTyfyJN1nZclXxVAqWLSXLI9SKQ==}
dependencies: dependencies:
'@tauri-apps/api': 2.0.0-beta.11 '@tauri-apps/api': 2.0.0-beta.13
dev: false dev: false
/@tauri-apps/plugin-notification@2.0.0-beta.3: /@tauri-apps/plugin-os@2.0.0-beta.5:
resolution: {integrity: sha512-OCwvX10JrOgJh50HXIGD9EHgGUMtdYAZ+Q5peGCSS3/aA8XWL1GOENtC7CNb0P+W7HjRAj2N8jtEApgV0kD6Gg==} resolution: {integrity: sha512-Qfs/clZ9R05J+OVOGkko+9OaYaL+xJaGICeQ1G5CnLFpUdTfMV10D+1nBBauxDdiLU4ay5I0iprJ5aG5GJBunQ==}
dependencies: dependencies:
'@tauri-apps/api': 2.0.0-beta.11 '@tauri-apps/api': 2.0.0-beta.13
dev: false dev: false
/@tauri-apps/plugin-os@2.0.0-beta.3: /@tauri-apps/plugin-process@2.0.0-beta.5:
resolution: {integrity: sha512-kuTfns6z7z/RKAqij3293fnSsgRHlogO/SzNWziFDGHpijGUm/peH70Cv45LKvHw+7kEMX+nfHFWOZm8UEVy/w==} resolution: {integrity: sha512-UMiBm6TtNYfxRb6GwzA4PalkZGwalHdclI/t0MVG33fNXgX1PaWONR/NZW/k7JE+WpvRAnN/Kf9ur8aEzjVVSQ==}
dependencies: dependencies:
'@tauri-apps/api': 2.0.0-beta.11 '@tauri-apps/api': 2.0.0-beta.13
dev: false dev: false
/@tauri-apps/plugin-process@2.0.0-beta.3: /@tauri-apps/plugin-shell@2.0.0-beta.6:
resolution: {integrity: sha512-N3TkHxoQQb45FP8mbKfcI+e6foR17QlLrrwvPK8SH+LhEP9uo1b5EUvpZM1TIegAmL/3LQIBtJw1loyHVn8Esg==} resolution: {integrity: sha512-g3nM9cQQGl7Iv4MvyFuco/aPTiwOI/MixcoKso3VQIg5Aqd64NqR0r+GfsB0qx52txItqzSXwmeaj1eZjO9Q6Q==}
dependencies: dependencies:
'@tauri-apps/api': 2.0.0-beta.11 '@tauri-apps/api': 2.0.0-beta.13
dev: false dev: false
/@tauri-apps/plugin-shell@2.0.0-beta.4: /@tauri-apps/plugin-updater@2.0.0-beta.5:
resolution: {integrity: sha512-y9APB3ZLtkY4IM6syE+dn/R4XR2UTHBcAI/n9L5PgnvOjJAuuEzVYURn10G51nKnLw+sFbxO7LW4VW66Uoaa9g==} resolution: {integrity: sha512-h8uNFQDtXaZPFyQcNAB5SxiSIvPbYRlM1fXf/lCcW8bAaMTanyszbHvR2vAYtVa/wIzKAOYriC/MpsJaKLjyhg==}
dependencies: dependencies:
'@tauri-apps/api': 2.0.0-beta.11 '@tauri-apps/api': 2.0.0-beta.13
dev: false dev: false
/@tauri-apps/plugin-updater@2.0.0-beta.3: /@tauri-apps/plugin-upload@2.0.0-beta.6:
resolution: {integrity: sha512-bD1ikPz80uK9YJKNYpYlA6StSp9lr0Ob1kGLG2XdmOgspv7SZLLNVzMORtKeqgepxwG99qdYGDDegT3Ll6+UlA==} resolution: {integrity: sha512-CbIj5ew167/Xfi/tsomPlRhty8U52m+FXEIj2tVNu6F1Kc6NEfd5BQM6iLf6RfKQ4WsUo+zwUkftnlpgP4FCpA==}
dependencies: dependencies:
'@tauri-apps/api': 2.0.0-beta.11 '@tauri-apps/api': 2.0.0-beta.13
dev: false
/@tauri-apps/plugin-upload@2.0.0-beta.4:
resolution: {integrity: sha512-nToNaeA7NqDteIOBkjVeqqVDVniWjjGxpIIux3/PdFZwUivJGL50WXlu8n5ad/0B80t+dDjMdCYfF3tdb5j00g==}
dependencies:
'@tauri-apps/api': 2.0.0-beta.11
dev: false dev: false
/@types/babel__core@7.20.5: /@types/babel__core@7.20.5:
@@ -3110,7 +3124,7 @@ packages:
peerDependencies: peerDependencies:
vite: ^4 || ^5 vite: ^4 || ^5
dependencies: dependencies:
'@swc/core': 1.5.7 '@swc/core': 1.5.24
vite: 5.2.12 vite: 5.2.12
transitivePeerDependencies: transitivePeerDependencies:
- '@swc/helpers' - '@swc/helpers'
@@ -3335,7 +3349,7 @@ packages:
common-ancestor-path: 1.0.1 common-ancestor-path: 1.0.1
cookie: 0.6.0 cookie: 0.6.0
cssesc: 3.0.0 cssesc: 3.0.0
debug: 4.3.4 debug: 4.3.5
deterministic-object-hash: 2.0.2 deterministic-object-hash: 2.0.2
devalue: 5.0.0 devalue: 5.0.0
diff: 5.2.0 diff: 5.2.0
@@ -3367,7 +3381,7 @@ packages:
shiki: 1.6.1 shiki: 1.6.1
string-width: 7.1.0 string-width: 7.1.0
strip-ansi: 7.1.0 strip-ansi: 7.1.0
tsconfck: 3.0.3(typescript@5.4.5) tsconfck: 3.1.0(typescript@5.4.5)
unist-util-visit: 5.0.0 unist-util-visit: 5.0.0
vfile: 6.0.1 vfile: 6.0.1
vite: 5.2.12 vite: 5.2.12
@@ -3467,7 +3481,7 @@ packages:
hasBin: true hasBin: true
dependencies: dependencies:
caniuse-lite: 1.0.30001625 caniuse-lite: 1.0.30001625
electron-to-chromium: 1.4.783 electron-to-chromium: 1.4.788
node-releases: 2.0.14 node-releases: 2.0.14
update-browserslist-db: 1.0.16(browserslist@4.23.0) update-browserslist-db: 1.0.16(browserslist@4.23.0)
@@ -3649,8 +3663,8 @@ packages:
resolution: {integrity: sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg==} resolution: {integrity: sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg==}
dev: false dev: false
/debug@4.3.4: /debug@4.3.5:
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} resolution: {integrity: sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==}
engines: {node: '>=6.0'} engines: {node: '>=6.0'}
peerDependencies: peerDependencies:
supports-color: '*' supports-color: '*'
@@ -3723,8 +3737,8 @@ packages:
/eastasianwidth@0.2.0: /eastasianwidth@0.2.0:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
/electron-to-chromium@1.4.783: /electron-to-chromium@1.4.788:
resolution: {integrity: sha512-bT0jEz/Xz1fahQpbZ1D7LgmPYZ3iHVY39NcWWro1+hA2IvjiPeaXtfSqrQ+nXjApMvQRE2ASt1itSLRrebHMRQ==} resolution: {integrity: sha512-ubp5+Ev/VV8KuRoWnfP2QF2Bg+O2ZFdb49DiiNbz2VmgkIqrnyYaqIOqj8A6K/3p1xV0QcU5hBQ1+BmB6ot1OA==}
/emmet@2.4.7: /emmet@2.4.7:
resolution: {integrity: sha512-O5O5QNqtdlnQM2bmKHtJgyChcrFMgQuulI+WdiOw2NArzprUqqxUW6bgYtKvzKgrsYpuLWalOkdhNP+1jluhCA==} resolution: {integrity: sha512-O5O5QNqtdlnQM2bmKHtJgyChcrFMgQuulI+WdiOw2NArzprUqqxUW6bgYtKvzKgrsYpuLWalOkdhNP+1jluhCA==}
@@ -4821,7 +4835,7 @@ packages:
resolution: {integrity: sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ==} resolution: {integrity: sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ==}
dependencies: dependencies:
'@types/debug': 4.1.12 '@types/debug': 4.1.12
debug: 4.3.4 debug: 4.3.5
decode-named-character-reference: 1.0.2 decode-named-character-reference: 1.0.2
devlop: 1.1.0 devlop: 1.1.0
micromark-core-commonmark: 2.0.1 micromark-core-commonmark: 2.0.1
@@ -4925,8 +4939,8 @@ packages:
resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
/nostr-tools@2.6.0(typescript@5.4.5): /nostr-tools@2.7.0(typescript@5.4.5):
resolution: {integrity: sha512-7Jm+rZZPd8TjfHhkxuQHtUZV+3Dd1gl5YqFLMWhmtPBsPDyFJMBsLaPtrBmuWjYGxzoOYop98hw+chI2Xaq0UQ==} resolution: {integrity: sha512-jJoL2J1CBiKDxaXZww27nY/Wsuxzx7AULxmGKFce4sskDu1tohNyfnzYQ8BvDyvkstU8kNZUAXPL32tre33uig==}
peerDependencies: peerDependencies:
typescript: '>=5.0.0' typescript: '>=5.0.0'
peerDependenciesMeta: peerDependenciesMeta:
@@ -5884,8 +5898,8 @@ packages:
/ts-interface-checker@0.1.13: /ts-interface-checker@0.1.13:
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
/tsconfck@3.0.3(typescript@5.4.5): /tsconfck@3.1.0(typescript@5.4.5):
resolution: {integrity: sha512-4t0noZX9t6GcPTfBAbIbbIU4pfpCwh0ueq3S4O/5qXI1VwK1outmxhe9dOiEWqMz3MW2LKgDTpqWV+37IWuVbA==} resolution: {integrity: sha512-CMjc5zMnyAjcS9sPLytrbFmj89st2g+JYtY/c02ug4Q+CZaAtCgbyviI0n1YvjZE/pzoc6FbNsINS13DOL1B9w==}
engines: {node: ^18 || >=20} engines: {node: ^18 || >=20}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
@@ -6229,7 +6243,7 @@ packages:
vite: '>=2.8' vite: '>=2.8'
dependencies: dependencies:
'@rollup/plugin-virtual': 3.0.2 '@rollup/plugin-virtual': 3.0.2
'@swc/core': 1.5.7 '@swc/core': 1.5.24
uuid: 9.0.1 uuid: 9.0.1
vite: 5.2.12 vite: 5.2.12
transitivePeerDependencies: transitivePeerDependencies:
@@ -6245,9 +6259,9 @@ packages:
vite: vite:
optional: true optional: true
dependencies: dependencies:
debug: 4.3.4 debug: 4.3.5
globrex: 0.1.2 globrex: 0.1.2
tsconfck: 3.0.3(typescript@5.4.5) tsconfck: 3.1.0(typescript@5.4.5)
vite: 5.2.12 vite: 5.2.12
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
+558 -538
View File
File diff suppressed because it is too large Load Diff
+16 -16
View File
@@ -5,7 +5,7 @@ description = "nostr client"
authors = ["npub1zfss807aer0j26mwp2la0ume0jqde3823rmu97ra6sgyyg956e0s6xw445"] authors = ["npub1zfss807aer0j26mwp2la0ume0jqde3823rmu97ra6sgyyg956e0s6xw445"]
repository = "https://github.com/lumehq/lume" repository = "https://github.com/lumehq/lume"
edition = "2021" edition = "2021"
rust-version = "1.68" rust-version = "1.70"
[build-dependencies] [build-dependencies]
tauri-build = { version = "2.0.0-beta", features = [] } tauri-build = { version = "2.0.0-beta", features = [] }
@@ -15,6 +15,7 @@ nostr-sdk = { version = "0.31", features = ["sqlite"] }
tokio = { version = "1", features = ["full"] } tokio = { version = "1", features = ["full"] }
serde_json = "1.0" serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
monitor = { git = "https://github.com/ahkohd/tauri-toolkit", branch = "v2" }
tauri = { version = "2.0.0-beta", features = [ tauri = { version = "2.0.0-beta", features = [
"unstable", "unstable",
"tray-icon", "tray-icon",
@@ -22,24 +23,23 @@ tauri = { version = "2.0.0-beta", features = [
"native-tls-vendored", "native-tls-vendored",
"protocol-asset", "protocol-asset",
] } ] }
tauri-plugin-clipboard-manager = "2.1.0-beta" tauri-plugin-clipboard-manager = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
tauri-plugin-dialog = "2.0.0-beta" tauri-plugin-dialog = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
tauri-plugin-fs = "2.0.0-beta" tauri-plugin-fs = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
tauri-plugin-http = "2.0.0-beta" tauri-plugin-http = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
tauri-plugin-notification = "2.0.0-beta" tauri-plugin-notification = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
tauri-plugin-os = "2.0.0-beta" tauri-plugin-os = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
tauri-plugin-process = "2.0.0-beta" tauri-plugin-process = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
tauri-plugin-shell = "2.0.0-beta" tauri-plugin-shell = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
tauri-plugin-updater = "2.0.0-beta" tauri-plugin-updater = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
tauri-plugin-upload = "2.0.0-beta" tauri-plugin-upload = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
tauri-plugin-window-state = "2.0.0-beta" tauri-nspanel = { git = "https://github.com/ahkohd/tauri-nspanel", branch = "v2" }
tauri-specta = { version = "^2.0.0-rc.11", features = ["typescript"] }
tauri-plugin-theme = "0.4.1"
tauri-plugin-decorum = "0.1.0" tauri-plugin-decorum = "0.1.0"
webpage = { version = "2.0", features = ["serde"] } specta = "^2.0.0-rc.12"
keyring = "2" keyring = "2"
keyring-search = "0.2.0" keyring-search = "0.2.0"
specta = "=2.0.0-rc.12"
tauri-specta = { version = "=2.0.0-rc.10", features = ["typescript"] }
tauri-plugin-theme = "0.4.1"
[target.'cfg(target_os = "macos")'.dependencies] [target.'cfg(target_os = "macos")'.dependencies]
cocoa = "0.25.0" cocoa = "0.25.0"
+84 -83
View File
@@ -1,85 +1,86 @@
{ {
"$schema": "../gen/schemas/desktop-schema.json", "$schema": "../gen/schemas/desktop-schema.json",
"identifier": "desktop-capability", "identifier": "desktop-capability",
"description": "Capability for the desktop", "description": "Capability for the desktop",
"platforms": ["linux", "macOS", "windows"], "platforms": ["linux", "macOS", "windows"],
"windows": [ "windows": [
"main", "main",
"splash", "panel",
"settings", "splash",
"search", "settings",
"nwc", "search",
"activity", "nwc",
"zap-*", "activity",
"event-*", "zap-*",
"user-*", "event-*",
"editor-*", "user-*",
"column-*" "editor-*",
], "column-*"
"permissions": [ ],
"path:default", "permissions": [
"event:default", "path:default",
"window:default", "event:default",
"app:default", "window:default",
"resources:default", "app:default",
"menu:default", "resources:default",
"tray:default", "menu:default",
"notification:allow-is-permission-granted", "tray:default",
"notification:allow-request-permission", "notification:allow-is-permission-granted",
"notification:default", "notification:allow-request-permission",
"os:allow-locale", "notification:default",
"os:allow-platform", "os:allow-locale",
"os:allow-os-type", "os:allow-platform",
"updater:default", "os:allow-os-type",
"updater:allow-check", "updater:default",
"updater:allow-download-and-install", "updater:allow-check",
"window:allow-start-dragging", "updater:allow-download-and-install",
"window:allow-create", "window:allow-start-dragging",
"window:allow-close", "window:allow-create",
"window:allow-set-focus", "window:allow-close",
"window:allow-center", "window:allow-set-focus",
"window:allow-minimize", "window:allow-center",
"window:allow-maximize", "window:allow-minimize",
"window:allow-set-size", "window:allow-maximize",
"window:allow-set-focus", "window:allow-set-size",
"window:allow-start-dragging", "window:allow-set-focus",
"decorum:allow-show-snap-overlay", "window:allow-start-dragging",
"clipboard-manager:allow-write-text", "decorum:allow-show-snap-overlay",
"clipboard-manager:allow-read-text", "clipboard-manager:allow-write-text",
"webview:allow-create-webview-window", "clipboard-manager:allow-read-text",
"webview:allow-create-webview", "webview:allow-create-webview-window",
"webview:allow-set-webview-size", "webview:allow-create-webview",
"webview:allow-set-webview-position", "webview:allow-set-webview-size",
"webview:allow-webview-close", "webview:allow-set-webview-position",
"dialog:allow-open", "webview:allow-webview-close",
"dialog:allow-ask", "dialog:allow-open",
"dialog:allow-message", "dialog:allow-ask",
"process:allow-restart", "dialog:allow-message",
"fs:allow-read-file", "process:allow-restart",
"theme:allow-set-theme", "fs:allow-read-file",
"theme:allow-get-theme", "theme:allow-set-theme",
"shell:allow-open", "theme:allow-get-theme",
{ "shell:allow-open",
"identifier": "http:default", {
"allow": [ "identifier": "http:default",
{ "allow": [
"url": "http://**/" {
}, "url": "http://**/"
{ },
"url": "https://**/" {
} "url": "https://**/"
] }
}, ]
{ },
"identifier": "fs:allow-read-text-file", {
"allow": [ "identifier": "fs:allow-read-text-file",
{ "allow": [
"path": "$RESOURCE/locales/*" {
}, "path": "$RESOURCE/locales/*"
{ },
"path": "$RESOURCE/resources/*" {
} "path": "$RESOURCE/resources/*"
] }
} ]
] }
]
} }
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1 +1 @@
{"desktop-capability":{"identifier":"desktop-capability","description":"Capability for the desktop","local":true,"windows":["main","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","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"]}} {"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","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"]}}
+42 -48
View File
@@ -5863,6 +5863,13 @@
"updater:allow-check" "updater:allow-check"
] ]
}, },
{
"description": "updater:allow-download -> Enables the download command without any pre-configured scope.",
"type": "string",
"enum": [
"updater:allow-download"
]
},
{ {
"description": "updater:allow-download-and-install -> Enables the download_and_install command without any pre-configured scope.", "description": "updater:allow-download-and-install -> Enables the download_and_install command without any pre-configured scope.",
"type": "string", "type": "string",
@@ -5870,6 +5877,13 @@
"updater:allow-download-and-install" "updater:allow-download-and-install"
] ]
}, },
{
"description": "updater:allow-install -> Enables the install command without any pre-configured scope.",
"type": "string",
"enum": [
"updater:allow-install"
]
},
{ {
"description": "updater:deny-check -> Denies the check command without any pre-configured scope.", "description": "updater:deny-check -> Denies the check command without any pre-configured scope.",
"type": "string", "type": "string",
@@ -5877,6 +5891,13 @@
"updater:deny-check" "updater:deny-check"
] ]
}, },
{
"description": "updater:deny-download -> Denies the download command without any pre-configured scope.",
"type": "string",
"enum": [
"updater:deny-download"
]
},
{ {
"description": "updater:deny-download-and-install -> Denies the download_and_install command without any pre-configured scope.", "description": "updater:deny-download-and-install -> Denies the download_and_install command without any pre-configured scope.",
"type": "string", "type": "string",
@@ -5884,6 +5905,13 @@
"updater:deny-download-and-install" "updater:deny-download-and-install"
] ]
}, },
{
"description": "updater:deny-install -> Denies the install command without any pre-configured scope.",
"type": "string",
"enum": [
"updater:deny-install"
]
},
{ {
"type": "string", "type": "string",
"enum": [ "enum": [
@@ -6261,6 +6289,13 @@
"window:allow-minimize" "window:allow-minimize"
] ]
}, },
{
"description": "window:allow-monitor-from-point -> Enables the monitor_from_point command without any pre-configured scope.",
"type": "string",
"enum": [
"window:allow-monitor-from-point"
]
},
{ {
"description": "window:allow-outer-position -> Enables the outer_position command without any pre-configured scope.", "description": "window:allow-outer-position -> Enables the outer_position command without any pre-configured scope.",
"type": "string", "type": "string",
@@ -6695,6 +6730,13 @@
"window:deny-minimize" "window:deny-minimize"
] ]
}, },
{
"description": "window:deny-monitor-from-point -> Denies the monitor_from_point command without any pre-configured scope.",
"type": "string",
"enum": [
"window:deny-monitor-from-point"
]
},
{ {
"description": "window:deny-outer-position -> Denies the outer_position command without any pre-configured scope.", "description": "window:deny-outer-position -> Denies the outer_position command without any pre-configured scope.",
"type": "string", "type": "string",
@@ -6967,54 +7009,6 @@
"enum": [ "enum": [
"window:deny-unminimize" "window:deny-unminimize"
] ]
},
{
"type": "string",
"enum": [
"window-state:default"
]
},
{
"description": "window-state:allow-filename -> Enables the filename command without any pre-configured scope.",
"type": "string",
"enum": [
"window-state:allow-filename"
]
},
{
"description": "window-state:allow-restore-state -> Enables the restore_state command without any pre-configured scope.",
"type": "string",
"enum": [
"window-state:allow-restore-state"
]
},
{
"description": "window-state:allow-save-window-state -> Enables the save_window_state command without any pre-configured scope.",
"type": "string",
"enum": [
"window-state:allow-save-window-state"
]
},
{
"description": "window-state:deny-filename -> Denies the filename command without any pre-configured scope.",
"type": "string",
"enum": [
"window-state:deny-filename"
]
},
{
"description": "window-state:deny-restore-state -> Denies the restore_state command without any pre-configured scope.",
"type": "string",
"enum": [
"window-state:deny-restore-state"
]
},
{
"description": "window-state:deny-save-window-state -> Denies the save_window_state command without any pre-configured scope.",
"type": "string",
"enum": [
"window-state:deny-save-window-state"
]
} }
] ]
}, },
+42 -48
View File
@@ -5863,6 +5863,13 @@
"updater:allow-check" "updater:allow-check"
] ]
}, },
{
"description": "updater:allow-download -> Enables the download command without any pre-configured scope.",
"type": "string",
"enum": [
"updater:allow-download"
]
},
{ {
"description": "updater:allow-download-and-install -> Enables the download_and_install command without any pre-configured scope.", "description": "updater:allow-download-and-install -> Enables the download_and_install command without any pre-configured scope.",
"type": "string", "type": "string",
@@ -5870,6 +5877,13 @@
"updater:allow-download-and-install" "updater:allow-download-and-install"
] ]
}, },
{
"description": "updater:allow-install -> Enables the install command without any pre-configured scope.",
"type": "string",
"enum": [
"updater:allow-install"
]
},
{ {
"description": "updater:deny-check -> Denies the check command without any pre-configured scope.", "description": "updater:deny-check -> Denies the check command without any pre-configured scope.",
"type": "string", "type": "string",
@@ -5877,6 +5891,13 @@
"updater:deny-check" "updater:deny-check"
] ]
}, },
{
"description": "updater:deny-download -> Denies the download command without any pre-configured scope.",
"type": "string",
"enum": [
"updater:deny-download"
]
},
{ {
"description": "updater:deny-download-and-install -> Denies the download_and_install command without any pre-configured scope.", "description": "updater:deny-download-and-install -> Denies the download_and_install command without any pre-configured scope.",
"type": "string", "type": "string",
@@ -5884,6 +5905,13 @@
"updater:deny-download-and-install" "updater:deny-download-and-install"
] ]
}, },
{
"description": "updater:deny-install -> Denies the install command without any pre-configured scope.",
"type": "string",
"enum": [
"updater:deny-install"
]
},
{ {
"type": "string", "type": "string",
"enum": [ "enum": [
@@ -6261,6 +6289,13 @@
"window:allow-minimize" "window:allow-minimize"
] ]
}, },
{
"description": "window:allow-monitor-from-point -> Enables the monitor_from_point command without any pre-configured scope.",
"type": "string",
"enum": [
"window:allow-monitor-from-point"
]
},
{ {
"description": "window:allow-outer-position -> Enables the outer_position command without any pre-configured scope.", "description": "window:allow-outer-position -> Enables the outer_position command without any pre-configured scope.",
"type": "string", "type": "string",
@@ -6695,6 +6730,13 @@
"window:deny-minimize" "window:deny-minimize"
] ]
}, },
{
"description": "window:deny-monitor-from-point -> Denies the monitor_from_point command without any pre-configured scope.",
"type": "string",
"enum": [
"window:deny-monitor-from-point"
]
},
{ {
"description": "window:deny-outer-position -> Denies the outer_position command without any pre-configured scope.", "description": "window:deny-outer-position -> Denies the outer_position command without any pre-configured scope.",
"type": "string", "type": "string",
@@ -6967,54 +7009,6 @@
"enum": [ "enum": [
"window:deny-unminimize" "window:deny-unminimize"
] ]
},
{
"type": "string",
"enum": [
"window-state:default"
]
},
{
"description": "window-state:allow-filename -> Enables the filename command without any pre-configured scope.",
"type": "string",
"enum": [
"window-state:allow-filename"
]
},
{
"description": "window-state:allow-restore-state -> Enables the restore_state command without any pre-configured scope.",
"type": "string",
"enum": [
"window-state:allow-restore-state"
]
},
{
"description": "window-state:allow-save-window-state -> Enables the save_window_state command without any pre-configured scope.",
"type": "string",
"enum": [
"window-state:allow-save-window-state"
]
},
{
"description": "window-state:deny-filename -> Denies the filename command without any pre-configured scope.",
"type": "string",
"enum": [
"window-state:deny-filename"
]
},
{
"description": "window-state:deny-restore-state -> Denies the restore_state command without any pre-configured scope.",
"type": "string",
"enum": [
"window-state:deny-restore-state"
]
},
{
"description": "window-state:deny-save-window-state -> Denies the save_window_state command without any pre-configured scope.",
"type": "string",
"enum": [
"window-state:deny-save-window-state"
]
} }
] ]
}, },
Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

+180
View File
@@ -0,0 +1,180 @@
use cocoa::appkit::NSWindowCollectionBehavior;
use std::ffi::CString;
use tauri::Manager;
use tauri_nspanel::{
block::ConcreteBlock,
cocoa::{
appkit::{NSMainMenuWindowLevel, NSView, NSWindow},
base::{id, nil},
foundation::{NSPoint, NSRect},
},
objc::{class, msg_send, runtime::NO, sel, sel_impl},
panel_delegate, ManagerExt, WebviewWindowExt,
};
#[allow(non_upper_case_globals)]
const NSWindowStyleMaskNonActivatingPanel: i32 = 1 << 7;
pub fn swizzle_to_menubar_panel(app_handle: &tauri::AppHandle) {
let window = app_handle.get_webview_window("panel").unwrap();
let panel = window.to_panel().unwrap();
let handle = app_handle.to_owned();
let delegate = panel_delegate!(MyPanelDelegate {
window_did_become_key,
window_did_resign_key
});
delegate.set_listener(Box::new(move |delegate_name: String| {
match delegate_name.as_str() {
"window_did_become_key" => {
let app_name = handle.package_info().name.to_owned();
println!("[info]: {:?} panel becomes key window!", app_name);
}
"window_did_resign_key" => {
println!("[info]: panel resigned from key window!");
}
_ => (),
}
}));
panel.set_level(NSMainMenuWindowLevel + 1);
panel.set_style_mask(NSWindowStyleMaskNonActivatingPanel);
panel.set_collection_behaviour(
NSWindowCollectionBehavior::NSWindowCollectionBehaviorCanJoinAllSpaces
| NSWindowCollectionBehavior::NSWindowCollectionBehaviorStationary
| NSWindowCollectionBehavior::NSWindowCollectionBehaviorFullScreenAuxiliary,
);
panel.set_delegate(delegate);
}
pub fn setup_menubar_panel_listeners(app_handle: &tauri::AppHandle) {
fn hide_menubar_panel(app_handle: &tauri::AppHandle) {
if check_menubar_frontmost() {
return;
}
let panel = app_handle.get_webview_panel("panel").unwrap();
panel.order_out(None);
}
let handle = app_handle.clone();
app_handle.listen_any("menubar_panel_did_resign_key", move |_| {
hide_menubar_panel(&handle);
});
let handle = app_handle.clone();
let callback = Box::new(move || {
hide_menubar_panel(&handle);
});
register_workspace_listener(
"NSWorkspaceDidActivateApplicationNotification".into(),
callback.clone(),
);
register_workspace_listener(
"NSWorkspaceActiveSpaceDidChangeNotification".into(),
callback,
);
}
pub fn update_menubar_appearance(app_handle: &tauri::AppHandle) {
let window = app_handle.get_window("panel").unwrap();
set_corner_radius(&window, 13.0);
}
pub fn set_corner_radius(window: &tauri::Window, radius: f64) {
let win: id = window.ns_window().unwrap() as _;
unsafe {
let view: id = win.contentView();
view.wantsLayer();
let layer: id = view.layer();
let _: () = msg_send![layer, setCornerRadius: radius];
}
}
pub fn position_menubar_panel(app_handle: &tauri::AppHandle, padding_top: f64) {
let window = app_handle.get_webview_window("panel").unwrap();
let monitor = monitor::get_monitor_with_cursor().unwrap();
let scale_factor = monitor.scale_factor();
let visible_area = monitor.visible_area();
let monitor_pos = visible_area.position().to_logical::<f64>(scale_factor);
let monitor_size = visible_area.size().to_logical::<f64>(scale_factor);
let mouse_location: NSPoint = unsafe { msg_send![class!(NSEvent), mouseLocation] };
let handle: id = window.ns_window().unwrap() as _;
let mut win_frame: NSRect = unsafe { msg_send![handle, frame] };
win_frame.origin.y = (monitor_pos.y + monitor_size.height) - win_frame.size.height;
win_frame.origin.y -= padding_top;
win_frame.origin.x = {
let top_right = mouse_location.x + (win_frame.size.width / 2.0);
let is_offscreen = top_right > monitor_pos.x + monitor_size.width;
if !is_offscreen {
mouse_location.x - (win_frame.size.width / 2.0)
} else {
let diff = top_right - (monitor_pos.x + monitor_size.width);
mouse_location.x - (win_frame.size.width / 2.0) - diff
}
};
let _: () = unsafe { msg_send![handle, setFrame: win_frame display: NO] };
}
fn register_workspace_listener(name: String, callback: Box<dyn Fn()>) {
let workspace: id = unsafe { msg_send![class!(NSWorkspace), sharedWorkspace] };
let notification_center: id = unsafe { msg_send![workspace, notificationCenter] };
let block = ConcreteBlock::new(move |_notif: id| {
callback();
});
let block = block.copy();
let name: id =
unsafe { msg_send![class!(NSString), stringWithCString: CString::new(name).unwrap()] };
unsafe {
let _: () = msg_send![
notification_center,
addObserverForName: name object: nil queue: nil usingBlock: block
];
}
}
fn app_pid() -> i32 {
let process_info: id = unsafe { msg_send![class!(NSProcessInfo), processInfo] };
let pid: i32 = unsafe { msg_send![process_info, processIdentifier] };
pid
}
fn get_frontmost_app_pid() -> i32 {
let workspace: id = unsafe { msg_send![class!(NSWorkspace), sharedWorkspace] };
let frontmost_application: id = unsafe { msg_send![workspace, frontmostApplication] };
let pid: i32 = unsafe { msg_send![frontmost_application, processIdentifier] };
pid
}
pub fn check_menubar_frontmost() -> bool {
get_frontmost_app_pid() == app_pid()
}
+49 -13
View File
@@ -4,8 +4,8 @@
)] )]
pub mod commands; pub mod commands;
pub mod fns;
pub mod nostr; pub mod nostr;
pub mod tray;
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
extern crate cocoa; extern crate cocoa;
@@ -17,8 +17,18 @@ extern crate objc;
use nostr_sdk::prelude::*; use nostr_sdk::prelude::*;
use std::fs; use std::fs;
use tauri::Manager; use tauri::Manager;
use tauri_nspanel::ManagerExt;
use tauri_plugin_decorum::WebviewWindowExt; use tauri_plugin_decorum::WebviewWindowExt;
#[cfg(target_os = "macos")]
use crate::fns::{
position_menubar_panel, setup_menubar_panel_listeners, swizzle_to_menubar_panel,
update_menubar_appearance,
};
#[cfg(target_os = "macos")]
use tauri::tray::{MouseButtonState, TrayIconEvent};
pub struct Nostr { pub struct Nostr {
client: Client, client: Client,
} }
@@ -34,13 +44,11 @@ fn main() {
nostr::keys::create_account, nostr::keys::create_account,
nostr::keys::save_account, nostr::keys::save_account,
nostr::keys::get_encrypted_key, nostr::keys::get_encrypted_key,
nostr::keys::nostr_connect, nostr::keys::connect_remote_account,
nostr::keys::load_account, nostr::keys::load_account,
nostr::keys::event_to_bech32, nostr::keys::event_to_bech32,
nostr::keys::user_to_bech32, nostr::keys::user_to_bech32,
nostr::keys::to_npub,
nostr::keys::verify_nip05, nostr::keys::verify_nip05,
nostr::metadata::get_activities,
nostr::metadata::get_current_user_profile, nostr::metadata::get_current_user_profile,
nostr::metadata::get_profile, nostr::metadata::get_profile,
nostr::metadata::get_contact_list, nostr::metadata::get_contact_list,
@@ -56,6 +64,7 @@ fn main() {
nostr::metadata::zap_profile, nostr::metadata::zap_profile,
nostr::metadata::zap_event, nostr::metadata::zap_event,
nostr::metadata::friend_to_friend, nostr::metadata::friend_to_friend,
nostr::metadata::get_notifications,
nostr::event::get_event, nostr::event::get_event,
nostr::event::get_replies, nostr::event::get_replies,
nostr::event::get_events_by, nostr::event::get_events_by,
@@ -91,20 +100,46 @@ fn main() {
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
main_window.set_traffic_lights_inset(8.0, 16.0).unwrap(); main_window.set_traffic_lights_inset(8.0, 16.0).unwrap();
// Setup app tray // Create panel
let handle = app.handle().clone(); #[cfg(target_os = "macos")]
tray::create_tray(app.handle()).unwrap(); swizzle_to_menubar_panel(&app.handle());
#[cfg(target_os = "macos")]
update_menubar_appearance(&app.handle());
#[cfg(target_os = "macos")]
setup_menubar_panel_listeners(&app.handle());
// Setup tray icon
#[cfg(target_os = "macos")]
let tray = app.tray_by_id("tray_panel").unwrap();
// Handle tray icon event
#[cfg(target_os = "macos")]
tray.on_tray_icon_event(|tray, event| match event {
TrayIconEvent::Click { button_state, .. } => {
if button_state == MouseButtonState::Up {
let app = tray.app_handle();
let panel = app.get_webview_panel("panel").unwrap();
match panel.is_visible() {
true => panel.order_out(None),
false => {
position_menubar_panel(&app, 0.0);
panel.show();
}
}
}
}
_ => {}
});
// Create data folder if not exist // Create data folder if not exist
let home_dir = app.path().home_dir().unwrap(); let home_dir = app.path().home_dir().unwrap();
fs::create_dir_all(home_dir.join("Lume/")).unwrap(); let _ = fs::create_dir_all(home_dir.join("Lume/"));
tauri::async_runtime::block_on(async move { tauri::async_runtime::block_on(async move {
// Create nostr database connection
let sqlite = SQLiteDatabase::open(home_dir.join("Lume/lume.db")).await;
// Create nostr connection // Create nostr connection
let client = match sqlite { let database = SQLiteDatabase::open(home_dir.join("Lume/lume.db")).await;
let client = match database {
Ok(db) => ClientBuilder::default().database(db).build(), Ok(db) => ClientBuilder::default().database(db).build(),
Err(_) => ClientBuilder::default().build(), Err(_) => ClientBuilder::default().build(),
}; };
@@ -130,11 +165,12 @@ fn main() {
client.connect().await; client.connect().await;
// Update global state // Update global state
handle.manage(Nostr { client }) app.handle().manage(Nostr { client })
}); });
Ok(()) Ok(())
}) })
.plugin(tauri_nspanel::init())
.plugin(tauri_plugin_theme::init(ctx.config_mut())) .plugin(tauri_plugin_theme::init(ctx.config_mut()))
.plugin(tauri_plugin_decorum::init()) .plugin(tauri_plugin_decorum::init())
.plugin(tauri_plugin_clipboard_manager::init()) .plugin(tauri_plugin_clipboard_manager::init())
+5 -6
View File
@@ -13,9 +13,8 @@ pub async fn get_event(id: &str, state: State<'_, Nostr>) -> Result<String, Stri
Nip19::Event(event) => { Nip19::Event(event) => {
let relays = event.relays; let relays = event.relays;
for relay in relays.into_iter() { for relay in relays.into_iter() {
let url = Url::from_str(&relay).unwrap(); let _ = client.add_relay(&relay).await.unwrap_or_default();
let _ = client.add_relay(&url).await.unwrap_or_default(); client.connect_relay(&relay).await.unwrap_or_default();
client.connect_relay(&url).await.unwrap_or_default();
} }
Some(event.event_id) Some(event.event_id)
} }
@@ -122,11 +121,11 @@ pub async fn get_local_events(
let filter = Filter::new() let filter = Filter::new()
.kinds(vec![Kind::TextNote, Kind::Repost]) .kinds(vec![Kind::TextNote, Kind::Repost])
.limit(20) .limit(20)
.authors(authors) .until(as_of)
.until(as_of); .authors(authors);
match client match client
.get_events_of(vec![filter], Some(Duration::from_secs(8))) .get_events_of(vec![filter], Some(Duration::from_secs(10)))
.await .await
{ {
Ok(events) => Ok(events.into_iter().map(|ev| ev.as_json()).collect()), Ok(events) => Ok(events.into_iter().map(|ev| ev.as_json()).collect()),
+197 -127
View File
@@ -6,7 +6,8 @@ use serde::Serialize;
use specta::Type; use specta::Type;
use std::str::FromStr; use std::str::FromStr;
use std::time::Duration; use std::time::Duration;
use tauri::{Manager, State}; use tauri::{EventTarget, Manager, State};
use tauri_plugin_notification::NotificationExt;
#[derive(Serialize, Type)] #[derive(Serialize, Type)]
pub struct Account { pub struct Account {
@@ -102,158 +103,236 @@ pub async fn save_account(
#[specta::specta] #[specta::specta]
pub async fn load_account( pub async fn load_account(
npub: &str, npub: &str,
bunker: Option<&str>,
state: State<'_, Nostr>, state: State<'_, Nostr>,
app: tauri::AppHandle, app: tauri::AppHandle,
) -> Result<bool, String> { ) -> Result<bool, String> {
let handle = app.clone();
let client = &state.client; let client = &state.client;
let keyring = Entry::new(npub, "nostr_secret").unwrap(); let keyring = Entry::new(npub, "nostr_secret").unwrap();
match keyring.get_password() { if let Ok(password) = keyring.get_password() {
Ok(password) => { match bunker {
if password.starts_with("bunker://") { Some(uri) => {
let local_keyring = Entry::new(npub, "bunker_local_account").unwrap(); let app_keys = Keys::parse(password).expect("Secret Key is modified, please check again.");
match local_keyring.get_password() { match NostrConnectURI::parse(uri) {
Ok(local_password) => { Ok(bunker_uri) => {
let secret_key = SecretKey::from_bech32(local_password).unwrap(); match Nip46Signer::new(bunker_uri, app_keys, Duration::from_secs(30), None).await {
let app_keys = Keys::new(secret_key); Ok(signer) => client.set_signer(Some(signer.into())).await,
let bunker_uri = NostrConnectURI::parse(password).unwrap(); Err(err) => return Err(err.to_string()),
let signer = Nip46Signer::new(bunker_uri, app_keys, Duration::from_secs(60), None) }
.await
.unwrap();
// Update signer
client.set_signer(Some(signer.into())).await;
} }
Err(_) => todo!(), Err(err) => return Err(err.to_string()),
} }
} else { }
let secret_key = SecretKey::from_bech32(password).expect("Get secret key failed"); None => {
let keys = Keys::new(secret_key); let keys = Keys::parse(password).expect("Secret Key is modified, please check again.");
let signer = NostrSigner::Keys(keys); let signer = NostrSigner::Keys(keys);
// Update signer // Update signer
client.set_signer(Some(signer)).await; client.set_signer(Some(signer)).await;
} }
}
// Verify signer // Verify signer
let signer = client.signer().await.unwrap(); let signer = client.signer().await.unwrap();
let public_key = signer.public_key().await.unwrap(); let public_key = signer.public_key().await.unwrap();
let filter = Filter::new() let filter = Filter::new()
.author(public_key) .author(public_key)
.kind(Kind::RelayList) .kind(Kind::RelayList)
.limit(1); .limit(1);
// Connect to user's relay (NIP-65) // Connect to user's relay (NIP-65)
// #TODO: Let rust-nostr handle it // #TODO: Let rust-nostr handle it
match client if let Ok(events) = client
.get_events_of(vec![filter], Some(Duration::from_secs(10))) .get_events_of(vec![filter], Some(Duration::from_secs(10)))
.await .await
{ {
Ok(events) => { if let Some(event) = events.first() {
if let Some(event) = events.first() { let relay_list = nip65::extract_relay_list(event);
let relay_list = nip65::extract_relay_list(event); for item in relay_list.into_iter() {
for item in relay_list.into_iter() { println!("connecting to relay: {} - {:?}", item.0, item.1);
println!("connecting to relay: {} - {:?}", item.0, item.1);
let relay_url = item.0.to_string(); let relay_url = item.0.to_string();
let opts = match item.1 { let opts = match item.1 {
Some(val) => { Some(val) => {
if val == &RelayMetadata::Read { if val == &RelayMetadata::Read {
RelayOptions::new().read(true).write(false) RelayOptions::new().read(true).write(false)
} else { } else {
RelayOptions::new().write(true).read(false) RelayOptions::new().write(true).read(false)
}
}
None => RelayOptions::new(),
};
// Add relay to relay pool
let _ = client
.add_relay_with_opts(relay_url.clone(), opts)
.await
.unwrap_or_default();
// Connect relay
client.connect_relay(relay_url).await.unwrap_or_default();
}
}
}
Err(_) => todo!(),
};
// Run notification service
tauri::async_runtime::spawn(async move {
let window = app.get_window("main").unwrap();
let state = window.state::<Nostr>();
let client = &state.client;
let subscription = Filter::new()
.pubkey(public_key)
.kinds(vec![Kind::TextNote, Kind::Repost, Kind::ZapReceipt])
.since(Timestamp::now());
let activity_id = SubscriptionId::new("activity");
// Create a subscription for activity
client
.subscribe_with_id(activity_id.clone(), vec![subscription], None)
.await;
// Handle notifications
let _ = client
.handle_notifications(|notification| async {
if let RelayPoolNotification::Event {
subscription_id,
event,
..
} = notification
{
if subscription_id == activity_id {
let _ = app.emit("activity", event.as_json());
} }
} }
Ok(false) None => RelayOptions::new(),
}) };
.await;
});
Ok(true) // Add relay to relay pool
} let _ = client
Err(err) => Err(err.to_string()), .add_relay_with_opts(&relay_url, opts)
.await
.unwrap_or_default();
// Connect relay
client.connect_relay(relay_url).await.unwrap_or_default();
}
}
};
// Run sync service
tauri::async_runtime::spawn(async move {
let window = handle.get_window("main").unwrap();
let state = window.state::<Nostr>();
let client = &state.client;
let filter = Filter::new()
.pubkey(public_key)
.kinds(vec![
Kind::TextNote,
Kind::Repost,
Kind::Reaction,
Kind::ZapReceipt,
])
.limit(500);
match client.reconcile(filter, NegentropyOptions::default()).await {
Ok(_) => println!("Sync notification done."),
Err(_) => println!("Sync notification failed."),
}
});
// Run notification service
tauri::async_runtime::spawn(async move {
println!("Starting notification service...");
let window = app.get_window("main").unwrap();
let state = window.state::<Nostr>();
let client = &state.client;
// Create a subscription for notification
let notification_id = SubscriptionId::new("notification");
let filter = Filter::new()
.pubkey(public_key)
.kinds(vec![
Kind::TextNote,
Kind::Repost,
Kind::Reaction,
Kind::ZapReceipt,
])
.since(Timestamp::now());
// Subscribe
client
.subscribe_with_id(notification_id.clone(), vec![filter], None)
.await;
// Handle notifications
let _ = client
.handle_notifications(|notification| async {
if let RelayPoolNotification::Event {
subscription_id,
event,
..
} = notification
{
if subscription_id == notification_id {
println!("new notification: {}", event.as_json());
if let Err(_) = app.emit_to(
EventTarget::window("panel"),
"notification",
event.as_json(),
) {
println!("Emit new notification failed.")
}
let handle = app.app_handle();
let author = client.metadata(event.pubkey).await.unwrap();
match event.kind() {
Kind::TextNote => {
if let Err(e) = handle
.notification()
.builder()
.body("Mentioned you in a thread.")
.title(author.display_name.unwrap_or_else(|| "Lume".to_string()))
.show()
{
println!("Failed to show notification: {:?}", e);
}
}
Kind::Repost => {
if let Err(e) = handle
.notification()
.builder()
.body("Reposted your note.")
.title(author.display_name.unwrap_or_else(|| "Lume".to_string()))
.show()
{
println!("Failed to show notification: {:?}", e);
}
}
Kind::Reaction => {
let content = event.content();
if let Err(e) = handle
.notification()
.builder()
.body(content)
.title(author.display_name.unwrap_or_else(|| "Lume".to_string()))
.show()
{
println!("Failed to show notification: {:?}", e);
}
}
Kind::ZapReceipt => {
if let Err(e) = handle
.notification()
.builder()
.body("Zapped you.")
.title(author.display_name.unwrap_or_else(|| "Lume".to_string()))
.show()
{
println!("Failed to show notification: {:?}", e);
}
}
_ => {}
}
}
}
Ok(false)
})
.await;
});
Ok(true)
} else {
Err("Key not found.".into())
} }
} }
#[tauri::command] #[tauri::command]
#[specta::specta] #[specta::specta]
pub async fn nostr_connect( pub async fn connect_remote_account(uri: &str, state: State<'_, Nostr>) -> Result<String, String> {
npub: &str,
uri: &str,
state: State<'_, Nostr>,
) -> Result<String, String> {
let client = &state.client; let client = &state.client;
let local_key = Keys::generate();
match NostrConnectURI::parse(uri) { match NostrConnectURI::parse(uri) {
Ok(bunker_uri) => { Ok(bunker_uri) => {
match Nip46Signer::new( let app_keys = Keys::generate();
bunker_uri, let app_secret = app_keys.secret_key().unwrap().to_string();
local_key.clone(),
Duration::from_secs(120), // Get remote user
None, let remote_user = bunker_uri.signer_public_key().unwrap();
) let remote_npub = remote_user.to_bech32().unwrap();
.await
{ match Nip46Signer::new(bunker_uri, app_keys, Duration::from_secs(120), None).await {
Ok(signer) => { Ok(signer) => {
let local_secret = local_key.secret_key().unwrap().to_bech32().unwrap(); let keyring = Entry::new(&remote_npub, "nostr_secret").unwrap();
let secret_keyring = Entry::new(&npub, "nostr_secret").unwrap(); let _ = keyring.set_password(&app_secret);
let account_keyring = Entry::new(&npub, "bunker_local_account").unwrap();
let _ = secret_keyring.set_password(uri);
let _ = account_keyring.set_password(&local_secret);
// Update signer // Update signer
let _ = client.set_signer(Some(signer.into())).await; let _ = client.set_signer(Some(signer.into())).await;
Ok(npub.into()) Ok(remote_npub)
} }
Err(err) => Err(err.to_string()), Err(err) => Err(err.to_string()),
} }
@@ -299,15 +378,6 @@ pub fn user_to_bech32(key: &str, relays: Vec<String>) -> Result<String, ()> {
Ok(profile.to_bech32().unwrap()) Ok(profile.to_bech32().unwrap())
} }
#[tauri::command]
#[specta::specta]
pub fn to_npub(hex: &str) -> Result<String, ()> {
let public_key = PublicKey::from_str(hex).unwrap();
let npub = Nip19::Pubkey(public_key);
Ok(npub.to_bech32().unwrap())
}
#[tauri::command] #[tauri::command]
#[specta::specta] #[specta::specta]
pub async fn verify_nip05(key: &str, nip05: &str) -> Result<bool, String> { pub async fn verify_nip05(key: &str, nip05: &str) -> Result<bool, String> {
+139 -70
View File
@@ -1,76 +1,9 @@
use super::get_latest_event;
use crate::Nostr; use crate::Nostr;
use keyring::Entry; use keyring::Entry;
use nostr_sdk::prelude::*; use nostr_sdk::prelude::*;
use std::{str::FromStr, time::Duration}; use std::{str::FromStr, time::Duration};
use tauri::State; use tauri::State;
use url::Url;
#[tauri::command]
#[specta::specta]
pub async fn get_activities(
account: &str,
kind: &str,
state: State<'_, Nostr>,
) -> Result<Vec<String>, String> {
let client = &state.client;
if let Ok(pubkey) = PublicKey::from_str(account) {
if let Ok(kind) = Kind::from_str(kind) {
let filter = Filter::new()
.pubkey(pubkey)
.kind(kind)
.limit(100)
.until(Timestamp::now());
match client.get_events_of(vec![filter], None).await {
Ok(events) => Ok(events.into_iter().map(|ev| ev.as_json()).collect()),
Err(err) => Err(err.to_string()),
}
} else {
Err("Kind is not valid, please check again.".into())
}
} else {
Err("Public Key is not valid, please check again.".into())
}
}
#[tauri::command]
#[specta::specta]
pub async fn friend_to_friend(npub: &str, state: State<'_, Nostr>) -> Result<bool, String> {
let client = &state.client;
match PublicKey::from_bech32(npub) {
Ok(author) => {
let mut contact_list: Vec<Contact> = Vec::new();
let contact_list_filter = Filter::new()
.author(author)
.kind(Kind::ContactList)
.limit(1);
if let Ok(contact_list_events) = client.get_events_of(vec![contact_list_filter], None).await {
for event in contact_list_events.into_iter() {
for tag in event.into_iter_tags() {
if let Some(TagStandard::PublicKey {
public_key,
relay_url,
alias,
uppercase: false,
}) = tag.to_standardized()
{
contact_list.push(Contact::new(public_key, relay_url, alias))
}
}
}
}
match client.set_contact_list(contact_list).await {
Ok(_) => Ok(true),
Err(err) => Err(err.to_string()),
}
}
Err(err) => Err(err.to_string()),
}
}
#[tauri::command] #[tauri::command]
#[specta::specta] #[specta::specta]
@@ -88,7 +21,7 @@ pub async fn get_current_user_profile(state: State<'_, Nostr>) -> Result<String,
.await .await
{ {
Ok(events) => { Ok(events) => {
if let Some(event) = events.first() { if let Some(event) = get_latest_event(&events) {
if let Ok(metadata) = Metadata::from_json(&event.content) { if let Ok(metadata) = Metadata::from_json(&event.content) {
Ok(metadata.as_json()) Ok(metadata.as_json())
} else { } else {
@@ -109,7 +42,14 @@ pub async fn get_profile(id: &str, state: State<'_, Nostr>) -> Result<String, St
let public_key: Option<PublicKey> = match Nip19::from_bech32(id) { let public_key: Option<PublicKey> = match Nip19::from_bech32(id) {
Ok(val) => match val { Ok(val) => match val {
Nip19::Pubkey(pubkey) => Some(pubkey), Nip19::Pubkey(pubkey) => Some(pubkey),
Nip19::Profile(profile) => Some(profile.public_key), Nip19::Profile(profile) => {
let relays = profile.relays;
for relay in relays.into_iter() {
let _ = client.add_relay(&relay).await.unwrap_or_default();
client.connect_relay(&relay).await.unwrap_or_default();
}
Some(profile.public_key)
}
_ => None, _ => None,
}, },
Err(_) => match PublicKey::from_str(id) { Err(_) => match PublicKey::from_str(id) {
@@ -464,3 +404,132 @@ pub async fn zap_event(
Err("Parse public key failed".into()) Err("Parse public key failed".into())
} }
} }
#[tauri::command]
#[specta::specta]
pub async fn friend_to_friend(npub: &str, state: State<'_, Nostr>) -> Result<bool, String> {
let client = &state.client;
match PublicKey::from_bech32(npub) {
Ok(author) => {
let mut contact_list: Vec<Contact> = Vec::new();
let contact_list_filter = Filter::new()
.author(author)
.kind(Kind::ContactList)
.limit(1);
if let Ok(contact_list_events) = client.get_events_of(vec![contact_list_filter], None).await {
for event in contact_list_events.into_iter() {
for tag in event.into_iter_tags() {
if let Some(TagStandard::PublicKey {
public_key,
relay_url,
alias,
uppercase: false,
}) = tag.to_standardized()
{
contact_list.push(Contact::new(public_key, relay_url, alias))
}
}
}
}
match client.set_contact_list(contact_list).await {
Ok(_) => Ok(true),
Err(err) => Err(err.to_string()),
}
}
Err(err) => Err(err.to_string()),
}
}
pub async fn get_following(
state: State<'_, Nostr>,
public_key: &str,
timeout: Option<u64>,
) -> Result<Vec<String>, String> {
let client = &state.client;
let public_key = match PublicKey::from_str(public_key) {
Ok(val) => val,
Err(err) => return Err(err.to_string()),
};
let duration = timeout.map(Duration::from_secs);
let filter = Filter::new().kind(Kind::ContactList).author(public_key);
let events = match client.get_events_of(vec![filter], duration).await {
Ok(events) => events,
Err(err) => return Err(err.to_string()),
};
let mut ret: Vec<String> = vec![];
if let Some(latest_event) = events.iter().max_by_key(|event| event.created_at()) {
ret.extend(latest_event.tags().iter().filter_map(|tag| {
if let Some(TagStandard::PublicKey {
uppercase: false, ..
}) = <nostr_sdk::Tag as Clone>::clone(tag).to_standardized()
{
tag.content().map(String::from)
} else {
None
}
}));
}
Ok(ret)
}
pub async fn get_followers(
state: State<'_, Nostr>,
public_key: &str,
timeout: Option<u64>,
) -> Result<Vec<String>, String> {
let client = &state.client;
let public_key = match PublicKey::from_str(public_key) {
Ok(val) => val,
Err(err) => return Err(err.to_string()),
};
let duration = timeout.map(Duration::from_secs);
let filter = Filter::new().kind(Kind::ContactList).custom_tag(
SingleLetterTag::lowercase(Alphabet::P),
vec![public_key.to_hex()],
);
let events = match client.get_events_of(vec![filter], duration).await {
Ok(events) => events,
Err(err) => return Err(err.to_string()),
};
let ret: Vec<String> = events
.into_iter()
.map(|event| event.author().to_hex())
.collect();
Ok(ret)
//todo: get more than 500 events
}
#[tauri::command]
#[specta::specta]
pub async fn get_notifications(state: State<'_, Nostr>) -> Result<Vec<String>, String> {
let client = &state.client;
match client.signer().await {
Ok(signer) => {
let public_key = signer.public_key().await.unwrap();
let filter = Filter::new()
.pubkey(public_key)
.kinds(vec![
Kind::TextNote,
Kind::Repost,
Kind::Reaction,
Kind::ZapReceipt,
])
.limit(200);
match client
.database()
.query(vec![filter], Order::default())
.await
{
Ok(events) => Ok(events.into_iter().map(|ev| ev.as_json()).collect()),
Err(err) => Err(err.to_string()),
}
}
Err(err) => Err(err.to_string()),
}
}
@@ -2,3 +2,5 @@ pub mod event;
pub mod keys; pub mod keys;
pub mod metadata; pub mod metadata;
pub mod relay; pub mod relay;
mod utils;
pub use utils::get_latest_event;
+5
View File
@@ -0,0 +1,5 @@
use nostr_sdk::Event;
pub fn get_latest_event(events: &[Event]) -> Option<&Event> {
events.iter().max_by_key(|event| event.created_at())
}
-180
View File
@@ -1,180 +0,0 @@
use std::path::PathBuf;
#[cfg(target_os = "macos")]
use tauri::TitleBarStyle;
use tauri::{
utils::config::WindowEffectsConfig, window::Effect, Manager, Runtime, WebviewUrl,
WebviewWindowBuilder,
};
use tauri_plugin_shell::ShellExt;
pub fn create_tray<R: Runtime>(app: &tauri::AppHandle<R>) -> tauri::Result<()> {
let version = app.package_info().version.to_string();
let tray = app.tray_by_id("main_tray").unwrap();
let menu = tauri::menu::MenuBuilder::new(app)
.item(&tauri::menu::MenuItem::with_id(app, "open", "Open Lume", true, None::<&str>).unwrap())
.item(&tauri::menu::MenuItem::with_id(app, "editor", "New Post", true, Some("cmd+n")).unwrap())
.item(&tauri::menu::MenuItem::with_id(app, "search", "Search", true, Some("cmd+k")).unwrap())
.separator()
.item(
&tauri::menu::MenuItem::with_id(
app,
"version",
format!("Version {}", version),
false,
None::<&str>,
)
.unwrap(),
)
.item(&tauri::menu::MenuItem::with_id(app, "about", "About Lume", true, None::<&str>).unwrap())
.item(
&tauri::menu::MenuItem::with_id(app, "update", "Check for Updates", true, None::<&str>)
.unwrap(),
)
.item(
&tauri::menu::MenuItem::with_id(app, "settings", "Settings...", true, Some("cmd+,")).unwrap(),
)
.separator()
.item(&tauri::menu::MenuItem::with_id(app, "quit", "Quit", true, None::<&str>).unwrap())
.build()
.unwrap();
let _ = tray.set_menu(Some(menu));
tray.on_menu_event(move |app, event| match event.id.0.as_str() {
"open" => {
if let Some(window) = app.get_window("main") {
if window.is_visible().unwrap_or_default() {
let _ = window.set_focus();
} else {
let _ = window.show();
let _ = window.set_focus();
};
}
}
"editor" => {
if let Some(window) = app.get_window("editor-0") {
if window.is_visible().unwrap_or_default() {
let _ = window.set_focus();
} else {
let _ = window.show();
let _ = window.set_focus();
};
} else {
#[cfg(target_os = "macos")]
let _ =
WebviewWindowBuilder::new(app, "editor-0", WebviewUrl::App(PathBuf::from("editor")))
.title("Editor")
.min_inner_size(560., 340.)
.inner_size(560., 340.)
.hidden_title(true)
.title_bar_style(TitleBarStyle::Overlay)
.transparent(true)
.effects(WindowEffectsConfig {
state: None,
effects: vec![Effect::WindowBackground],
radius: None,
color: None,
})
.build()
.unwrap();
#[cfg(not(target_os = "macos"))]
let _ =
WebviewWindowBuilder::new(app, "editor-0", WebviewUrl::App(PathBuf::from("editor")))
.title("Editor")
.min_inner_size(560., 340.)
.inner_size(560., 340.)
.build()
.unwrap();
}
}
"search" => {
if let Some(window) = app.get_window("search") {
if window.is_visible().unwrap_or_default() {
let _ = window.set_focus();
} else {
let _ = window.show();
let _ = window.set_focus();
};
} else {
#[cfg(target_os = "macos")]
let _ = WebviewWindowBuilder::new(app, "search", WebviewUrl::App(PathBuf::from("search")))
.title("Search")
.inner_size(400., 600.)
.minimizable(false)
.title_bar_style(TitleBarStyle::Overlay)
.transparent(true)
.effects(WindowEffectsConfig {
state: None,
effects: vec![Effect::WindowBackground],
radius: None,
color: None,
})
.build()
.unwrap();
#[cfg(not(target_os = "macos"))]
let _ = WebviewWindowBuilder::new(app, "Search", WebviewUrl::App(PathBuf::from("search")))
.title("Search")
.inner_size(750., 470.)
.minimizable(false)
.resizable(false)
.build()
.unwrap();
}
}
"about" => {
app.shell().open("https://lume.nu", None).unwrap();
}
"update" => {
println!("todo!")
}
"settings" => {
if let Some(window) = app.get_window("settings") {
if window.is_visible().unwrap_or_default() {
let _ = window.set_focus();
} else {
let _ = window.show();
let _ = window.set_focus();
};
} else {
#[cfg(target_os = "macos")]
let _ = WebviewWindowBuilder::new(
app,
"settings",
WebviewUrl::App(PathBuf::from("settings/general")),
)
.title("Settings")
.inner_size(800., 500.)
.title_bar_style(TitleBarStyle::Overlay)
.hidden_title(true)
.resizable(false)
.minimizable(false)
.transparent(true)
.effects(WindowEffectsConfig {
state: None,
effects: vec![Effect::WindowBackground],
radius: None,
color: None,
})
.build()
.unwrap();
#[cfg(not(target_os = "macos"))]
let _ = WebviewWindowBuilder::new(
app,
"settings",
WebviewUrl::App(PathBuf::from("settings/general")),
)
.title("Settings")
.inner_size(800., 500.)
.resizable(false)
.minimizable(false)
.build()
.unwrap();
}
}
"quit" => {
app.exit(0);
}
_ => {}
});
Ok(())
}
+122 -121
View File
@@ -1,123 +1,124 @@
{ {
"$schema": "../node_modules/@tauri-apps/cli/schema.json", "$schema": "../node_modules/@tauri-apps/cli/schema.json",
"productName": "Lume", "productName": "Lume",
"version": "4.0.7", "version": "4.0.9",
"identifier": "nu.lume.Lume", "identifier": "nu.lume.Lume",
"build": { "build": {
"beforeBuildCommand": "pnpm desktop:build", "beforeBuildCommand": "pnpm desktop:build",
"beforeDevCommand": "pnpm desktop:dev", "beforeDevCommand": "pnpm desktop:dev",
"devUrl": "http://localhost:3000", "devUrl": "http://localhost:3000",
"frontendDist": "../dist" "frontendDist": "../dist"
}, },
"app": { "app": {
"macOSPrivateApi": true, "macOSPrivateApi": true,
"withGlobalTauri": true, "withGlobalTauri": true,
"trayIcon": { "trayIcon": {
"id": "main_tray", "id": "tray_panel",
"iconPath": "./icons/tray.png", "iconPath": "./icons/tray.png",
"iconAsTemplate": true "iconAsTemplate": true,
}, "menuOnLeftClick": false
"security": { },
"assetProtocol": { "security": {
"enable": true, "assetProtocol": {
"scope": [ "enable": true,
"$APPDATA/*", "scope": [
"$DATA/*", "$APPDATA/*",
"$LOCALDATA/*", "$DATA/*",
"$DESKTOP/*", "$LOCALDATA/*",
"$DOCUMENT/*", "$DESKTOP/*",
"$DOWNLOAD/*", "$DOCUMENT/*",
"$HOME/*", "$DOWNLOAD/*",
"$PICTURE/*", "$HOME/*",
"$PUBLIC/*", "$PICTURE/*",
"$VIDEO/*", "$PUBLIC/*",
"$APPCONFIG/*", "$VIDEO/*",
"$RESOURCE/*" "$APPCONFIG/*",
] "$RESOURCE/*"
} ]
} }
}, }
"bundle": { },
"licenseFile": "../LICENSE", "bundle": {
"longDescription": "nostr client for desktop", "licenseFile": "../LICENSE",
"shortDescription": "nostr client", "longDescription": "nostr client for desktop",
"targets": "all", "shortDescription": "nostr client",
"active": true, "targets": "all",
"category": "SocialNetworking", "active": true,
"resources": ["resources/*", "locales/*"], "category": "SocialNetworking",
"icon": [ "resources": ["resources/*", "locales/*"],
"icons/32x32.png", "icon": [
"icons/128x128.png", "icons/32x32.png",
"icons/128x128@2x.png", "icons/128x128.png",
"icons/icon.icns", "icons/128x128@2x.png",
"icons/icon.ico" "icons/icon.icns",
], "icons/icon.ico"
"linux": { ],
"appimage": { "linux": {
"bundleMediaFramework": true, "appimage": {
"files": {} "bundleMediaFramework": true,
}, "files": {}
"deb": { },
"files": {} "deb": {
}, "files": {}
"rpm": { },
"epoch": 0, "rpm": {
"files": {}, "epoch": 0,
"release": "1" "files": {},
} "release": "1"
}, }
"macOS": { },
"dmg": { "macOS": {
"appPosition": { "dmg": {
"x": 180, "appPosition": {
"y": 170 "x": 180,
}, "y": 170
"applicationFolderPosition": { },
"x": 480, "applicationFolderPosition": {
"y": 170 "x": 480,
}, "y": 170
"windowSize": { },
"height": 400, "windowSize": {
"width": 660 "height": 400,
} "width": 660
}, }
"files": {}, },
"minimumSystemVersion": "10.15" "files": {},
}, "minimumSystemVersion": "10.15"
"windows": { },
"allowDowngrades": true, "windows": {
"certificateThumbprint": null, "allowDowngrades": true,
"digestAlgorithm": "sha256", "certificateThumbprint": null,
"nsis": null, "digestAlgorithm": "sha256",
"timestampUrl": null, "nsis": null,
"tsp": false, "timestampUrl": null,
"webviewFixedRuntimePath": null, "tsp": false,
"webviewInstallMode": { "webviewFixedRuntimePath": null,
"silent": true, "webviewInstallMode": {
"type": "downloadBootstrapper" "silent": true,
}, "type": "downloadBootstrapper"
"wix": null },
}, "wix": null
"fileAssociations": [ },
{ "fileAssociations": [
"name": "bech32", {
"description": "Nostr Bech32", "name": "bech32",
"ext": ["npub", "nsec", "nprofile", "nevent", "naddr", "nrelay"], "description": "Nostr Bech32",
"role": "Viewer" "ext": ["npub", "nsec", "nprofile", "nevent", "naddr", "nrelay"],
} "role": "Viewer"
] }
}, ]
"plugins": { },
"updater": { "plugins": {
"active": true, "updater": {
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEU3OTdCMkM3RjU5QzE2NzkKUldSNUZwejF4N0tYNTVHYjMrU0JkL090SlEyNUVLYU5TM2hTU3RXSWtEWngrZWJ4a0pydUhXZHEK", "active": true,
"windows": { "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEU3OTdCMkM3RjU5QzE2NzkKUldSNUZwejF4N0tYNTVHYjMrU0JkL090SlEyNUVLYU5TM2hTU3RXSWtEWngrZWJ4a0pydUhXZHEK",
"installMode": "quiet" "windows": {
}, "installMode": "quiet"
"endpoints": [ },
"https://lus.reya3772.workers.dev/v1/{{target}}/{{arch}}/{{current_version}}", "endpoints": [
"https://lus.reya3772.workers.dev/{{target}}/{{current_version}}" "https://lus.reya3772.workers.dev/v1/{{target}}/{{arch}}/{{current_version}}",
] "https://lus.reya3772.workers.dev/{{target}}/{{current_version}}"
} ]
} }
}
} }
+15
View File
@@ -16,6 +16,21 @@
"windowEffects": { "windowEffects": {
"effects": ["windowBackground"] "effects": ["windowBackground"]
} }
},
{
"title": "Lume Panel",
"label": "panel",
"url": "/panel",
"width": 350,
"height": 500,
"fullscreen": false,
"resizable": false,
"decorations": false,
"transparent": true,
"visible": false,
"windowEffects": {
"effects": ["popover"]
}
} }
] ]
} }