Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6996e30889 | ||
|
|
7ba793fad8 | ||
|
|
f11f836518 | ||
|
|
04fe0fcec8 | ||
|
|
799835a629 | ||
|
|
4e7da4108b | ||
|
|
7c7b082b3a | ||
|
|
38d6c51921 | ||
|
|
1738cbdd97 | ||
|
|
2e885b76a1 | ||
|
|
f94680e487 | ||
|
|
c682a58842 | ||
|
|
921cf871ee | ||
|
|
d5b1593aca | ||
|
|
6676b4e2a4 | ||
|
|
5f30ddcfca | ||
|
|
41d0de539d | ||
|
|
e254ee3203 |
2
.github/workflows/main.yml
vendored
2
.github/workflows/main.yml
vendored
@@ -16,6 +16,8 @@ jobs:
|
||||
args: "--target aarch64-apple-darwin"
|
||||
- platform: "macos-latest" # for Intel based macs.
|
||||
args: "--target x86_64-apple-darwin"
|
||||
- platform: "macos-latest" # for Intel based macs.
|
||||
args: "--target universal-apple-darwin"
|
||||
#- platform: 'ubuntu-22.04'
|
||||
# args: ''
|
||||
#- platform: 'windows-latest'
|
||||
|
||||
@@ -21,16 +21,16 @@
|
||||
"@radix-ui/react-hover-card": "^1.0.7",
|
||||
"@radix-ui/react-popover": "^1.0.7",
|
||||
"@radix-ui/react-switch": "^1.0.3",
|
||||
"@radix-ui/react-tabs": "^1.0.4",
|
||||
"@radix-ui/react-tooltip": "^1.0.7",
|
||||
"@tanstack/query-sync-storage-persister": "^5.40.0",
|
||||
"@tanstack/react-query": "^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-resources-to-backend": "^1.2.1",
|
||||
"minidenticons": "^4.2.1",
|
||||
"nanoid": "^5.0.7",
|
||||
"nostr-tools": "^2.6.0",
|
||||
"react": "^18.3.1",
|
||||
"react-currency-input-field": "^3.8.0",
|
||||
"react-dom": "^18.3.1",
|
||||
@@ -48,8 +48,8 @@
|
||||
"@lume/tailwindcss": "workspace:^",
|
||||
"@lume/tsconfig": "workspace:^",
|
||||
"@lume/types": "workspace:^",
|
||||
"@tanstack/router-devtools": "^1.34.5",
|
||||
"@tanstack/router-vite-plugin": "^1.34.1",
|
||||
"@tanstack/router-devtools": "^1.34.9",
|
||||
"@tanstack/router-vite-plugin": "^1.34.8",
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@vitejs/plugin-react-swc": "^3.7.0",
|
||||
|
||||
BIN
apps/desktop2/public/404.jpg
Normal file
BIN
apps/desktop2/public/404.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
@@ -3,7 +3,7 @@ import type { LumeColumn } from "@lume/types";
|
||||
import { cn } from "@lume/utils";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { getCurrent } from "@tauri-apps/api/webviewWindow";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
|
||||
export function Column({
|
||||
column,
|
||||
@@ -17,7 +17,10 @@ export function Column({
|
||||
isResize: boolean;
|
||||
}) {
|
||||
const container = useRef<HTMLDivElement>(null);
|
||||
const webviewLabel = `column-${account}_${column.label}`;
|
||||
const webviewLabel = useMemo(
|
||||
() => `column-${account}_${column.label}`,
|
||||
[account],
|
||||
);
|
||||
|
||||
const [isCreated, setIsCreated] = useState(false);
|
||||
|
||||
@@ -48,6 +51,8 @@ export function Column({
|
||||
}, [isScroll]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!container?.current) return;
|
||||
|
||||
const rect = container.current.getBoundingClientRect();
|
||||
const url = `${column.content}?account=${account}&label=${column.label}&name=${column.name}`;
|
||||
|
||||
@@ -59,11 +64,16 @@ export function Column({
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
url,
|
||||
}).then(() => setIsCreated(true));
|
||||
}).then(() => {
|
||||
console.log("created: ", webviewLabel);
|
||||
setIsCreated(true);
|
||||
});
|
||||
|
||||
// close webview when unmounted
|
||||
return () => {
|
||||
invoke("close_column", { label: webviewLabel });
|
||||
invoke("close_column", { label: webviewLabel }).then(() => {
|
||||
console.log("closed: ", webviewLabel);
|
||||
});
|
||||
};
|
||||
}, [account]);
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ export function NoteContent({
|
||||
<div className="flex flex-col gap-2">
|
||||
<div
|
||||
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" : "",
|
||||
className,
|
||||
)}
|
||||
|
||||
@@ -145,7 +145,7 @@ export function NoteContentLarge({
|
||||
|
||||
return (
|
||||
<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}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@ export function Hashtag({ tag }: { tag: string }) {
|
||||
return (
|
||||
<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="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>
|
||||
<div
|
||||
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" : "",
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -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 { download } from "@tauri-apps/plugin-upload";
|
||||
import { type SyntheticEvent, useState } from "react";
|
||||
|
||||
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 }) => {
|
||||
try {
|
||||
e.stopPropagation();
|
||||
if (!window) {
|
||||
const newWindow = new WebviewWindow(label, {
|
||||
url,
|
||||
title: "Image Viewer",
|
||||
width: 800,
|
||||
height: 800,
|
||||
titleBarStyle: "overlay",
|
||||
});
|
||||
|
||||
const downloadDirPath = await downloadDir();
|
||||
const filename = url.substring(url.lastIndexOf("/") + 1);
|
||||
await download(url, `${downloadDirPath}/${filename}`);
|
||||
|
||||
setDownloaded(true);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return newWindow;
|
||||
}
|
||||
};
|
||||
|
||||
const open = async () => {
|
||||
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 await window.setFocus();
|
||||
};
|
||||
|
||||
return (
|
||||
// biome-ignore lint/a11y/useKeyWithClickEvents: <explanation>
|
||||
<div onClick={() => open()} className="group relative my-1">
|
||||
<div className="group relative my-1">
|
||||
<img
|
||||
src={url}
|
||||
alt={url}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
style={{ contentVisibility: "auto" }}
|
||||
onError={fallback}
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -36,6 +36,10 @@ export function Images({ urls }: { urls: string[] }) {
|
||||
style={{ contentVisibility: "auto" }}
|
||||
className="max-h-[400px] w-auto object-cover rounded-lg outline outline-1 -outline-offset-1 outline-black/15"
|
||||
onClick={() => open(urls[0])}
|
||||
onError={({ currentTarget }) => {
|
||||
currentTarget.onerror = null;
|
||||
currentTarget.src = "/404.jpg";
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@@ -54,6 +58,10 @@ export function Images({ urls }: { urls: string[] }) {
|
||||
style={{ contentVisibility: "auto" }}
|
||||
className="w-full h-full object-cover rounded-lg outline outline-1 -outline-offset-1 outline-black/15"
|
||||
onClick={() => open(item)}
|
||||
onError={({ currentTarget }) => {
|
||||
currentTarget.onerror = null;
|
||||
currentTarget.src = "/404.jpg";
|
||||
}}
|
||||
/>
|
||||
</CarouselItem>
|
||||
)}
|
||||
|
||||
@@ -23,7 +23,7 @@ export function UserAvatar({ className }: { className?: string }) {
|
||||
alt={user.pubkey}
|
||||
loading="eager"
|
||||
decoding="async"
|
||||
className={cn("outline-[.5px] outline-black/5", className)}
|
||||
className={cn("outline-[.5px] outline-black/5 object-cover", className)}
|
||||
/>
|
||||
<Avatar.Fallback delayMs={120}>
|
||||
<img
|
||||
|
||||
@@ -13,9 +13,11 @@ import { VList, type VListHandle } from "virtua";
|
||||
|
||||
export const Route = createFileRoute("/$account/home")({
|
||||
loader: async () => {
|
||||
const columns = NostrQuery.getColumns();
|
||||
const columns = await NostrQuery.getColumns();
|
||||
return columns;
|
||||
},
|
||||
gcTime: 0,
|
||||
shouldReload: false,
|
||||
component: Screen,
|
||||
});
|
||||
|
||||
@@ -24,11 +26,16 @@ function Screen() {
|
||||
const initialColumnList = Route.useLoaderData();
|
||||
const vlistRef = useRef<VListHandle>(null);
|
||||
|
||||
const [columns, setColumns] = useState<LumeColumn[]>([]);
|
||||
const [selectedIndex, setSelectedIndex] = useState(-1);
|
||||
const [columns, setColumns] = useState([]);
|
||||
const [isScroll, setIsScroll] = useState(false);
|
||||
const [isResize, setIsResize] = useState(false);
|
||||
|
||||
const reset = () => {
|
||||
setColumns(null);
|
||||
setSelectedIndex(-1);
|
||||
};
|
||||
|
||||
const goLeft = () => {
|
||||
const prevIndex = Math.max(selectedIndex - 1, 0);
|
||||
setSelectedIndex(prevIndex);
|
||||
@@ -64,7 +71,7 @@ function Screen() {
|
||||
|
||||
// scroll to the newest column
|
||||
vlistRef.current.scrollToIndex(newCols.length - 1, {
|
||||
align: "end",
|
||||
align: "center",
|
||||
});
|
||||
}, 150);
|
||||
|
||||
@@ -103,12 +110,15 @@ function Screen() {
|
||||
}, [initialColumnList]);
|
||||
|
||||
useEffect(() => {
|
||||
// save state
|
||||
NostrQuery.setColumns(columns);
|
||||
// save current state
|
||||
if (columns?.length) {
|
||||
NostrQuery.setColumns(columns).then(() => console.log("saved"));
|
||||
}
|
||||
}, [columns]);
|
||||
|
||||
useEffect(() => {
|
||||
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 === "remove") remove(data.payload.label);
|
||||
if (data.payload.type === "set_title")
|
||||
@@ -137,9 +147,9 @@ function Screen() {
|
||||
onScrollEnd={() => setIsScroll(false)}
|
||||
className="scrollbar-none h-full w-full overflow-x-auto focus:outline-none"
|
||||
>
|
||||
{columns.map((column) => (
|
||||
{columns?.map((column) => (
|
||||
<Column
|
||||
key={column.label}
|
||||
key={account + column.label}
|
||||
column={column}
|
||||
account={account}
|
||||
isScroll={isScroll}
|
||||
|
||||
@@ -1,36 +1,23 @@
|
||||
import {
|
||||
BellIcon,
|
||||
ComposeFilledIcon,
|
||||
HorizontalDotsIcon,
|
||||
PlusIcon,
|
||||
SearchIcon,
|
||||
} from "@lume/icons";
|
||||
import { type NostrEvent, Kind } from "@lume/types";
|
||||
import { User } from "@/components/user";
|
||||
import {
|
||||
cn,
|
||||
decodeZapInvoice,
|
||||
displayNpub,
|
||||
sendNativeNotification,
|
||||
} from "@lume/utils";
|
||||
import { cn } from "@lume/utils";
|
||||
import { Outlet, createFileRoute } from "@tanstack/react-router";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { getCurrent } from "@tauri-apps/api/window";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
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";
|
||||
|
||||
type AccountSearch = {
|
||||
accounts?: string[];
|
||||
};
|
||||
|
||||
export const Route = createFileRoute("/$account")({
|
||||
validateSearch: (search: Record<string, unknown>): AccountSearch => {
|
||||
return {
|
||||
accounts: (search?.accounts as string[]) || [],
|
||||
};
|
||||
beforeLoad: async () => {
|
||||
const accounts = await NostrAccount.getAccounts();
|
||||
return { accounts };
|
||||
},
|
||||
component: Screen,
|
||||
});
|
||||
@@ -50,7 +37,7 @@ function Screen() {
|
||||
<div className="flex items-center gap-3">
|
||||
<Accounts />
|
||||
<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"
|
||||
>
|
||||
<PlusIcon className="size-5" />
|
||||
@@ -65,7 +52,6 @@ function Screen() {
|
||||
<ComposeFilledIcon className="size-4" />
|
||||
New Post
|
||||
</button>
|
||||
<Bell />
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => LumeWindow.openSearch()}
|
||||
@@ -84,7 +70,7 @@ function Screen() {
|
||||
}
|
||||
|
||||
function Accounts() {
|
||||
const { accounts } = Route.useSearch();
|
||||
const { accounts } = Route.useRouteContext();
|
||||
const { account } = Route.useParams();
|
||||
|
||||
const [windowWidth, setWindowWidth] = useState<number>(null);
|
||||
@@ -108,11 +94,20 @@ function Accounts() {
|
||||
return await LumeWindow.openProfile(account);
|
||||
}
|
||||
|
||||
// change current account and update signer
|
||||
// Change current account and update signer
|
||||
const select = await NostrAccount.loadAccount(npub);
|
||||
|
||||
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 {
|
||||
toast.warning("Something wrong.");
|
||||
}
|
||||
@@ -191,64 +186,3 @@ function Accounts() {
|
||||
</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({
|
||||
to: "/auth/settings",
|
||||
to: "/auth/$account/settings",
|
||||
params: { account },
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -3,12 +3,11 @@ import { NostrQuery } from "@lume/system";
|
||||
import { Spinner } from "@lume/ui";
|
||||
import * as Switch from "@radix-ui/react-switch";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { requestPermission } from "@tauri-apps/plugin-notification";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { toast } from "sonner";
|
||||
|
||||
export const Route = createFileRoute("/auth/settings")({
|
||||
export const Route = createFileRoute("/auth/$account/settings")({
|
||||
beforeLoad: async () => {
|
||||
const settings = await NostrQuery.getSettings();
|
||||
return { settings };
|
||||
@@ -19,6 +18,7 @@ export const Route = createFileRoute("/auth/settings")({
|
||||
|
||||
function Screen() {
|
||||
const { settings } = Route.useRouteContext();
|
||||
const { account } = Route.useParams();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [newSettings, setNewSettings] = useState(settings);
|
||||
@@ -26,21 +26,6 @@ function Screen() {
|
||||
|
||||
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 = () => {
|
||||
setNewSettings((prev) => ({
|
||||
...prev,
|
||||
@@ -72,7 +57,8 @@ function Screen() {
|
||||
|
||||
if (eventId) {
|
||||
return navigate({
|
||||
to: "/",
|
||||
to: "/$account/home",
|
||||
params: { account },
|
||||
replace: true,
|
||||
});
|
||||
}
|
||||
@@ -99,22 +85,6 @@ function Screen() {
|
||||
</div>
|
||||
<div className="flex flex-col gap-5">
|
||||
<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-1">
|
||||
<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.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-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-1">
|
||||
<h3 className="font-semibold">Zap</h3>
|
||||
@@ -185,7 +140,7 @@ function Screen() {
|
||||
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"
|
||||
>
|
||||
{t("global.continue")}
|
||||
{loading ? <Spinner /> : t("global.continue")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -28,7 +28,8 @@ function Screen() {
|
||||
|
||||
if (npub) {
|
||||
navigate({
|
||||
to: "/auth/settings",
|
||||
to: "/auth/$account/settings",
|
||||
params: { account: npub },
|
||||
replace: true,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -23,11 +23,12 @@ function Screen() {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
const npub = await NostrAccount.connectRemoteAccount(uri);
|
||||
const remoteAccount = await NostrAccount.connectRemoteAccount(uri);
|
||||
|
||||
if (npub) {
|
||||
navigate({
|
||||
to: "/auth/settings",
|
||||
if (remoteAccount?.length) {
|
||||
return navigate({
|
||||
to: "/auth/$account/settings",
|
||||
params: { account: remoteAccount },
|
||||
replace: true,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -41,9 +41,6 @@ function Screen() {
|
||||
return navigate({
|
||||
to: "/$account/home",
|
||||
params: { account: npub },
|
||||
search: {
|
||||
accounts: context.accounts,
|
||||
},
|
||||
replace: true,
|
||||
});
|
||||
}
|
||||
@@ -60,7 +57,10 @@ function Screen() {
|
||||
});
|
||||
|
||||
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="text-center">
|
||||
<h2 className="text-xl text-neutral-700 dark:text-neutral-300">
|
||||
|
||||
314
apps/desktop2/src/routes/panel.tsx
Normal file
314
apps/desktop2/src/routes/panel.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
67
package.json
67
package.json
@@ -1,36 +1,35 @@
|
||||
{
|
||||
"name": "lume",
|
||||
"private": true,
|
||||
"version": "4.0.0",
|
||||
"scripts": {
|
||||
"build": "turbo run build",
|
||||
"dev": "turbo run dev",
|
||||
"web:dev": "turbo run dev --filter web",
|
||||
"desktop:dev": "turbo run dev --filter desktop2",
|
||||
"desktop:build": "turbo run build --filter desktop2",
|
||||
"tauri": "tauri"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "1.7.3",
|
||||
"@tauri-apps/cli": "2.0.0-beta.12",
|
||||
"turbo": "^1.13.3"
|
||||
},
|
||||
"packageManager": "pnpm@8.9.0",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"dependencies": {
|
||||
"@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.1",
|
||||
"@tauri-apps/plugin-dialog": "2.0.0-beta.3",
|
||||
"@tauri-apps/plugin-fs": "2.0.0-beta.3",
|
||||
"@tauri-apps/plugin-http": "2.0.0-beta.3",
|
||||
"@tauri-apps/plugin-notification": "2.0.0-beta.3",
|
||||
"@tauri-apps/plugin-os": "2.0.0-beta.3",
|
||||
"@tauri-apps/plugin-process": "2.0.0-beta.3",
|
||||
"@tauri-apps/plugin-shell": "2.0.0-beta.4",
|
||||
"@tauri-apps/plugin-updater": "2.0.0-beta.3",
|
||||
"@tauri-apps/plugin-upload": "2.0.0-beta.4"
|
||||
}
|
||||
"name": "lume",
|
||||
"private": true,
|
||||
"version": "4.0.0",
|
||||
"scripts": {
|
||||
"build": "turbo run build",
|
||||
"dev": "turbo run dev",
|
||||
"web:dev": "turbo run dev --filter web",
|
||||
"desktop:dev": "turbo run dev --filter desktop2",
|
||||
"desktop:build": "turbo run build --filter desktop2",
|
||||
"tauri": "tauri"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "1.7.3",
|
||||
"@tauri-apps/cli": "2.0.0-beta.20",
|
||||
"turbo": "^1.13.3"
|
||||
},
|
||||
"packageManager": "pnpm@8.9.0",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "2.0.0-beta.13",
|
||||
"@tauri-apps/plugin-clipboard-manager": "2.1.0-beta.3",
|
||||
"@tauri-apps/plugin-dialog": "2.0.0-beta.5",
|
||||
"@tauri-apps/plugin-fs": "2.0.0-beta.5",
|
||||
"@tauri-apps/plugin-http": "2.0.0-beta.5",
|
||||
"@tauri-apps/plugin-notification": "2.0.0-beta.5",
|
||||
"@tauri-apps/plugin-os": "2.0.0-beta.5",
|
||||
"@tauri-apps/plugin-process": "2.0.0-beta.5",
|
||||
"@tauri-apps/plugin-shell": "2.0.0-beta.6",
|
||||
"@tauri-apps/plugin-updater": "2.0.0-beta.5",
|
||||
"@tauri-apps/plugin-upload": "2.0.0-beta.6"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Metadata } from "@lume/types";
|
||||
import { commands } from "./commands";
|
||||
import { Result, commands } from "./commands";
|
||||
import { Window } from "@tauri-apps/api/window";
|
||||
|
||||
export class NostrAccount {
|
||||
static async getAccounts() {
|
||||
@@ -13,9 +14,19 @@ export class NostrAccount {
|
||||
}
|
||||
|
||||
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") {
|
||||
const panel = Window.getByLabel("panel");
|
||||
panel.emit("load-notification", { account: npub }); // trigger load notification
|
||||
|
||||
return query.data;
|
||||
} else {
|
||||
throw new Error(query.error);
|
||||
@@ -62,19 +73,19 @@ export class NostrAccount {
|
||||
}
|
||||
|
||||
static async connectRemoteAccount(uri: string) {
|
||||
const remoteKey = uri.replace("bunker://", "").split("?")[0];
|
||||
const npub = await commands.toNpub(remoteKey);
|
||||
const connect = await commands.connectRemoteAccount(uri);
|
||||
|
||||
if (npub.status === "ok") {
|
||||
const connect = await commands.nostrConnect(npub.data, uri);
|
||||
if (connect.status === "ok") {
|
||||
const npub = connect.data;
|
||||
const parsed = new URL(uri);
|
||||
parsed.searchParams.delete("secret");
|
||||
|
||||
if (connect.status === "ok") {
|
||||
return connect.data;
|
||||
} else {
|
||||
throw new Error(connect.error);
|
||||
}
|
||||
// save connection string
|
||||
localStorage.setItem(`${npub}_bunker`, parsed.toString());
|
||||
|
||||
return npub;
|
||||
} else {
|
||||
throw new Error(npub.error);
|
||||
throw new Error(connect.error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
|
||||
// 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 = {
|
||||
async getRelays() : Promise<Result<Relays, null>> {
|
||||
try {
|
||||
@@ -57,17 +60,17 @@ try {
|
||||
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 {
|
||||
return { status: "ok", data: await TAURI_INVOKE("nostr_connect", { npub, uri }) };
|
||||
return { status: "ok", data: await TAURI_INVOKE("connect_remote_account", { uri }) };
|
||||
} catch (e) {
|
||||
if(e instanceof Error) throw e;
|
||||
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 {
|
||||
return { status: "ok", data: await TAURI_INVOKE("load_account", { npub }) };
|
||||
return { status: "ok", data: await TAURI_INVOKE("load_account", { npub, bunker }) };
|
||||
} catch (e) {
|
||||
if(e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
@@ -89,14 +92,6 @@ try {
|
||||
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>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("verify_nip05", { key, nip05 }) };
|
||||
@@ -105,14 +100,6 @@ try {
|
||||
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>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("get_current_user_profile") };
|
||||
@@ -233,6 +220,14 @@ try {
|
||||
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>> {
|
||||
try {
|
||||
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 **/
|
||||
|
||||
export type Account = { npub: string; nsec: string }
|
||||
|
||||
@@ -22,7 +22,7 @@ export class LumeEvent {
|
||||
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 reply: string = null;
|
||||
|
||||
@@ -30,14 +30,22 @@ export class LumeEvent {
|
||||
const events = tags.filter((el) => el[0] === "e" && el[3] !== "mention");
|
||||
|
||||
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) {
|
||||
for (const relay of relays) {
|
||||
if (relay[2]?.length)
|
||||
commands
|
||||
.connectRelay(relay[2])
|
||||
.then(() => console.log("[gossip]: ", relay[2]));
|
||||
try {
|
||||
if (relay.length) {
|
||||
const url = new URL(relay);
|
||||
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) {
|
||||
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) {
|
||||
const rootEvent = events[rootIndex];
|
||||
|
||||
|
||||
@@ -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) {
|
||||
const normalize = pubkey.replace("nostr:", "").replace(/[^\w\s]/gi, "");
|
||||
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) {
|
||||
const until: string = asOf && asOf > 0 ? asOf.toString() : undefined;
|
||||
const query = await commands.getLocalEvents(pubkeys, until);
|
||||
@@ -242,15 +240,19 @@ export class NostrQuery {
|
||||
const systemColumns: LumeColumn[] = JSON.parse(resourceFile);
|
||||
const query = await commands.getNstore(NSTORE_KEYS.columns);
|
||||
|
||||
if (query.status === "ok") {
|
||||
const columns: LumeColumn[] = query.data ? JSON.parse(query.data) : [];
|
||||
try {
|
||||
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 columns;
|
||||
} else {
|
||||
} catch {
|
||||
return systemColumns;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ export class LumeWindow {
|
||||
const query = await commands.openWindow(
|
||||
label,
|
||||
"Settings",
|
||||
"/settings",
|
||||
"/settings/general",
|
||||
800,
|
||||
500,
|
||||
);
|
||||
|
||||
2
packages/types/index.d.ts
vendored
2
packages/types/index.d.ts
vendored
@@ -110,7 +110,7 @@ export interface LumeColumn {
|
||||
}
|
||||
|
||||
export interface EventColumns {
|
||||
type: "add" | "remove" | "update" | "left" | "right" | "set_title";
|
||||
type: "reset" | "add" | "remove" | "update" | "left" | "right" | "set_title";
|
||||
label?: string;
|
||||
title?: string;
|
||||
column?: LumeColumn;
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"clsx": "^2.1.1",
|
||||
"dayjs": "^1.11.11",
|
||||
"light-bolt11-decoder": "^3.1.1",
|
||||
"nostr-tools": "^2.6.0",
|
||||
"nostr-tools": "^2.7.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"slate": "^0.103.0",
|
||||
|
||||
358
pnpm-lock.yaml
generated
358
pnpm-lock.yaml
generated
@@ -11,46 +11,43 @@ importers:
|
||||
'@tauri-apps/api':
|
||||
specifier: 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':
|
||||
specifier: 2.1.0-beta.1
|
||||
version: 2.1.0-beta.1
|
||||
specifier: 2.1.0-beta.3
|
||||
version: 2.1.0-beta.3
|
||||
'@tauri-apps/plugin-dialog':
|
||||
specifier: 2.0.0-beta.3
|
||||
version: 2.0.0-beta.3
|
||||
specifier: 2.0.0-beta.5
|
||||
version: 2.0.0-beta.5
|
||||
'@tauri-apps/plugin-fs':
|
||||
specifier: 2.0.0-beta.3
|
||||
version: 2.0.0-beta.3
|
||||
specifier: 2.0.0-beta.5
|
||||
version: 2.0.0-beta.5
|
||||
'@tauri-apps/plugin-http':
|
||||
specifier: 2.0.0-beta.3
|
||||
version: 2.0.0-beta.3
|
||||
specifier: 2.0.0-beta.5
|
||||
version: 2.0.0-beta.5
|
||||
'@tauri-apps/plugin-notification':
|
||||
specifier: 2.0.0-beta.3
|
||||
version: 2.0.0-beta.3
|
||||
specifier: 2.0.0-beta.5
|
||||
version: 2.0.0-beta.5
|
||||
'@tauri-apps/plugin-os':
|
||||
specifier: 2.0.0-beta.3
|
||||
version: 2.0.0-beta.3
|
||||
specifier: 2.0.0-beta.5
|
||||
version: 2.0.0-beta.5
|
||||
'@tauri-apps/plugin-process':
|
||||
specifier: 2.0.0-beta.3
|
||||
version: 2.0.0-beta.3
|
||||
specifier: 2.0.0-beta.5
|
||||
version: 2.0.0-beta.5
|
||||
'@tauri-apps/plugin-shell':
|
||||
specifier: 2.0.0-beta.4
|
||||
version: 2.0.0-beta.4
|
||||
specifier: 2.0.0-beta.6
|
||||
version: 2.0.0-beta.6
|
||||
'@tauri-apps/plugin-updater':
|
||||
specifier: 2.0.0-beta.3
|
||||
version: 2.0.0-beta.3
|
||||
specifier: 2.0.0-beta.5
|
||||
version: 2.0.0-beta.5
|
||||
'@tauri-apps/plugin-upload':
|
||||
specifier: 2.0.0-beta.4
|
||||
version: 2.0.0-beta.4
|
||||
specifier: 2.0.0-beta.6
|
||||
version: 2.0.0-beta.6
|
||||
devDependencies:
|
||||
'@biomejs/biome':
|
||||
specifier: 1.7.3
|
||||
version: 1.7.3
|
||||
'@tauri-apps/cli':
|
||||
specifier: 2.0.0-beta.12
|
||||
version: 2.0.0-beta.12
|
||||
specifier: 2.0.0-beta.20
|
||||
version: 2.0.0-beta.20
|
||||
turbo:
|
||||
specifier: ^1.13.3
|
||||
version: 1.13.3
|
||||
@@ -93,6 +90,9 @@ importers:
|
||||
'@radix-ui/react-switch':
|
||||
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)
|
||||
'@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':
|
||||
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)
|
||||
@@ -106,8 +106,8 @@ importers:
|
||||
specifier: ^5.40.0
|
||||
version: 5.40.0(@tanstack/react-query@5.40.0)(react@18.3.1)
|
||||
'@tanstack/react-router':
|
||||
specifier: ^1.34.5
|
||||
version: 1.34.5(react-dom@18.3.1)(react@18.3.1)
|
||||
specifier: ^1.34.9
|
||||
version: 1.34.9(react-dom@18.3.1)(react@18.3.1)
|
||||
i18next:
|
||||
specifier: ^23.11.5
|
||||
version: 23.11.5
|
||||
@@ -120,9 +120,6 @@ importers:
|
||||
nanoid:
|
||||
specifier: ^5.0.7
|
||||
version: 5.0.7
|
||||
nostr-tools:
|
||||
specifier: ^2.6.0
|
||||
version: 2.6.0(typescript@5.4.5)
|
||||
react:
|
||||
specifier: ^18.3.1
|
||||
version: 18.3.1
|
||||
@@ -170,11 +167,11 @@ importers:
|
||||
specifier: workspace:^
|
||||
version: link:../../packages/types
|
||||
'@tanstack/router-devtools':
|
||||
specifier: ^1.34.5
|
||||
version: 1.34.5(@tanstack/react-router@1.34.5)(csstype@3.1.3)(react-dom@18.3.1)(react@18.3.1)
|
||||
specifier: ^1.34.9
|
||||
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':
|
||||
specifier: ^1.34.1
|
||||
version: 1.34.1(vite@5.2.12)
|
||||
specifier: ^1.34.8
|
||||
version: 1.34.8(vite@5.2.12)
|
||||
'@types/react':
|
||||
specifier: ^18.3.3
|
||||
version: 18.3.3
|
||||
@@ -365,8 +362,8 @@ importers:
|
||||
specifier: ^3.1.1
|
||||
version: 3.1.1
|
||||
nostr-tools:
|
||||
specifier: ^2.6.0
|
||||
version: 2.6.0(typescript@5.4.5)
|
||||
specifier: ^2.7.0
|
||||
version: 2.7.0(typescript@5.4.5)
|
||||
react:
|
||||
specifier: ^18.3.1
|
||||
version: 18.3.1
|
||||
@@ -521,7 +518,7 @@ packages:
|
||||
engines: {node: ^18.17.1 || ^20.3.0 || >=21.0.0}
|
||||
dependencies:
|
||||
ci-info: 4.0.0
|
||||
debug: 4.3.4
|
||||
debug: 4.3.5
|
||||
dlv: 1.1.3
|
||||
dset: 3.1.3
|
||||
is-docker: 3.0.0
|
||||
@@ -557,7 +554,7 @@ packages:
|
||||
'@babel/traverse': 7.24.6
|
||||
'@babel/types': 7.24.6
|
||||
convert-source-map: 2.0.0
|
||||
debug: 4.3.4
|
||||
debug: 4.3.5
|
||||
gensync: 1.0.0-beta.2
|
||||
json5: 2.2.3
|
||||
semver: 6.3.1
|
||||
@@ -818,7 +815,7 @@ packages:
|
||||
'@babel/helper-split-export-declaration': 7.24.6
|
||||
'@babel/parser': 7.24.6
|
||||
'@babel/types': 7.24.6
|
||||
debug: 4.3.4
|
||||
debug: 4.3.5
|
||||
globals: 11.12.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
@@ -2256,6 +2253,34 @@ packages:
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
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):
|
||||
resolution: {integrity: sha512-lPh5iKNFVQ/jav/j6ZrWq3blfDJ0OH9R6FlNUHPMqdLuQ9vwDgFsRxvl8b7Asuy5c8xmoojHUxKHQSOAvMHxyw==}
|
||||
peerDependencies:
|
||||
@@ -2562,8 +2587,8 @@ packages:
|
||||
resolution: {integrity: sha512-CqYyepN4SnBopaoXYwng4NO8riB5ask/LTCkhOFq+GNGtr2X+aKeD767eYdqYukeixEUvv4bXdyTYVaogj7KBw==}
|
||||
dev: false
|
||||
|
||||
/@swc/core-darwin-arm64@1.5.7:
|
||||
resolution: {integrity: sha512-bZLVHPTpH3h6yhwVl395k0Mtx8v6CGhq5r4KQdAoPbADU974Mauz1b6ViHAJ74O0IVE5vyy7tD3OpkQxL/vMDQ==}
|
||||
/@swc/core-darwin-arm64@1.5.24:
|
||||
resolution: {integrity: sha512-M7oLOcC0sw+UTyAuL/9uyB9GeO4ZpaBbH76JSH6g1m0/yg7LYJZGRmplhDmwVSDAR5Fq4Sjoi1CksmmGkgihGA==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
@@ -2571,8 +2596,8 @@ packages:
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@swc/core-darwin-x64@1.5.7:
|
||||
resolution: {integrity: sha512-RpUyu2GsviwTc2qVajPL0l8nf2vKj5wzO3WkLSHAHEJbiUZk83NJrZd1RVbEknIMO7+Uyjh54hEh8R26jSByaw==}
|
||||
/@swc/core-darwin-x64@1.5.24:
|
||||
resolution: {integrity: sha512-MfcFjGGYognpSBSos2pYUNYJSmqEhuw5ceGr6qAdME7ddbjGXliza4W6FggsM+JnWwpqa31+e7/R+GetW4WkaQ==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
@@ -2580,8 +2605,8 @@ packages:
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@swc/core-linux-arm-gnueabihf@1.5.7:
|
||||
resolution: {integrity: sha512-cTZWTnCXLABOuvWiv6nQQM0hP6ZWEkzdgDvztgHI/+u/MvtzJBN5lBQ2lue/9sSFYLMqzqff5EHKlFtrJCA9dQ==}
|
||||
/@swc/core-linux-arm-gnueabihf@1.5.24:
|
||||
resolution: {integrity: sha512-amI2pwtcWV3E/m/nf+AQtn1LWDzKLZyjCmWd3ms7QjEueWYrY8cU1Y4Wp7wNNsxIoPOi8zek1Uj2wwFD/pttNQ==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
@@ -2589,8 +2614,8 @@ packages:
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@swc/core-linux-arm64-gnu@1.5.7:
|
||||
resolution: {integrity: sha512-hoeTJFBiE/IJP30Be7djWF8Q5KVgkbDtjySmvYLg9P94bHg9TJPSQoC72tXx/oXOgXvElDe/GMybru0UxhKx4g==}
|
||||
/@swc/core-linux-arm64-gnu@1.5.24:
|
||||
resolution: {integrity: sha512-sTSvmqMmgT1ynH/nP75Pc51s+iT4crZagHBiDOf5cq+kudUYjda9lWMs7xkXB/TUKFHPCRK0HGunl8bkwiIbuw==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
@@ -2598,8 +2623,8 @@ packages:
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@swc/core-linux-arm64-musl@1.5.7:
|
||||
resolution: {integrity: sha512-+NDhK+IFTiVK1/o7EXdCeF2hEzCiaRSrb9zD7X2Z7inwWlxAntcSuzZW7Y6BRqGQH89KA91qYgwbnjgTQ22PiQ==}
|
||||
/@swc/core-linux-arm64-musl@1.5.24:
|
||||
resolution: {integrity: sha512-vd2/hfOBGbrX21FxsFdXCUaffjkHvlZkeE2UMRajdXifwv79jqOHIJg3jXG1F3ZrhCghCzirFts4tAZgcG8XWg==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
@@ -2607,8 +2632,8 @@ packages:
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@swc/core-linux-x64-gnu@1.5.7:
|
||||
resolution: {integrity: sha512-25GXpJmeFxKB+7pbY7YQLhWWjkYlR+kHz5I3j9WRl3Lp4v4UD67OGXwPe+DIcHqcouA1fhLhsgHJWtsaNOMBNg==}
|
||||
/@swc/core-linux-x64-gnu@1.5.24:
|
||||
resolution: {integrity: sha512-Zrdzi7NqzQxm2BvAG5KyOSBEggQ7ayrxh599AqqevJmsUXJ8o2nMiWQOBvgCGp7ye+Biz3pvZn1EnRzAp+TpUg==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
@@ -2616,8 +2641,8 @@ packages:
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@swc/core-linux-x64-musl@1.5.7:
|
||||
resolution: {integrity: sha512-0VN9Y5EAPBESmSPPsCJzplZHV26akC0sIgd3Hc/7S/1GkSMoeuVL+V9vt+F/cCuzr4VidzSkqftdP3qEIsXSpg==}
|
||||
/@swc/core-linux-x64-musl@1.5.24:
|
||||
resolution: {integrity: sha512-1F8z9NRi52jdZQCGc5sflwYSctL6omxiVmIFVp8TC9nngjQKc00TtX/JC2Eo2HwvgupkFVl5YQJidAck9YtmJw==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
@@ -2625,8 +2650,8 @@ packages:
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@swc/core-win32-arm64-msvc@1.5.7:
|
||||
resolution: {integrity: sha512-RtoNnstBwy5VloNCvmvYNApkTmuCe4sNcoYWpmY7C1+bPR+6SOo8im1G6/FpNem8AR5fcZCmXHWQ+EUmRWJyuA==}
|
||||
/@swc/core-win32-arm64-msvc@1.5.24:
|
||||
resolution: {integrity: sha512-cKpP7KvS6Xr0jFSTBXY53HZX/YfomK5EMQYpCVDOvfsZeYHN20sQSKXfpVLvA/q2igVt1zzy1XJcOhpJcgiKLg==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
@@ -2634,8 +2659,8 @@ packages:
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@swc/core-win32-ia32-msvc@1.5.7:
|
||||
resolution: {integrity: sha512-Xm0TfvcmmspvQg1s4+USL3x8D+YPAfX2JHygvxAnCJ0EHun8cm2zvfNBcsTlnwYb0ybFWXXY129aq1wgFC9TpQ==}
|
||||
/@swc/core-win32-ia32-msvc@1.5.24:
|
||||
resolution: {integrity: sha512-IoPWfi0iwqjZuf7gE223+B97/ZwkKbu7qL5KzGP7g3hJrGSKAvv7eC5Y9r2iKKtLKyv5R/T6Ho0kFR/usi7rHw==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
@@ -2643,8 +2668,8 @@ packages:
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@swc/core-win32-x64-msvc@1.5.7:
|
||||
resolution: {integrity: sha512-tp43WfJLCsKLQKBmjmY/0vv1slVywR5Q4qKjF5OIY8QijaEW7/8VwPyUyVoJZEnDgv9jKtUTG5PzqtIYPZGnyg==}
|
||||
/@swc/core-win32-x64-msvc@1.5.24:
|
||||
resolution: {integrity: sha512-zHgF2k1uVJL8KIW+PnVz1To4a3Cz9THbh2z2lbehaF/gKHugH4c3djBozU4das1v35KOqf5jWIEviBLql2wDLQ==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
@@ -2652,12 +2677,12 @@ packages:
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@swc/core@1.5.7:
|
||||
resolution: {integrity: sha512-U4qJRBefIJNJDRCCiVtkfa/hpiZ7w0R6kASea+/KLp+vkus3zcLSB8Ub8SvKgTIxjWpwsKcZlPf5nrv4ls46SQ==}
|
||||
/@swc/core@1.5.24:
|
||||
resolution: {integrity: sha512-Eph9zvO4xvqWZGVzTdtdEJ0Vqf0VIML/o/e4Qd2RLOqtfgnlRi7avmMu5C0oqciJ0tk+hqdUKVUZ4JPoPaiGvQ==}
|
||||
engines: {node: '>=10'}
|
||||
requiresBuild: true
|
||||
peerDependencies:
|
||||
'@swc/helpers': ^0.5.0
|
||||
'@swc/helpers': '*'
|
||||
peerDependenciesMeta:
|
||||
'@swc/helpers':
|
||||
optional: true
|
||||
@@ -2665,16 +2690,16 @@ packages:
|
||||
'@swc/counter': 0.1.3
|
||||
'@swc/types': 0.1.7
|
||||
optionalDependencies:
|
||||
'@swc/core-darwin-arm64': 1.5.7
|
||||
'@swc/core-darwin-x64': 1.5.7
|
||||
'@swc/core-linux-arm-gnueabihf': 1.5.7
|
||||
'@swc/core-linux-arm64-gnu': 1.5.7
|
||||
'@swc/core-linux-arm64-musl': 1.5.7
|
||||
'@swc/core-linux-x64-gnu': 1.5.7
|
||||
'@swc/core-linux-x64-musl': 1.5.7
|
||||
'@swc/core-win32-arm64-msvc': 1.5.7
|
||||
'@swc/core-win32-ia32-msvc': 1.5.7
|
||||
'@swc/core-win32-x64-msvc': 1.5.7
|
||||
'@swc/core-darwin-arm64': 1.5.24
|
||||
'@swc/core-darwin-x64': 1.5.24
|
||||
'@swc/core-linux-arm-gnueabihf': 1.5.24
|
||||
'@swc/core-linux-arm64-gnu': 1.5.24
|
||||
'@swc/core-linux-arm64-musl': 1.5.24
|
||||
'@swc/core-linux-x64-gnu': 1.5.24
|
||||
'@swc/core-linux-x64-musl': 1.5.24
|
||||
'@swc/core-win32-arm64-msvc': 1.5.24
|
||||
'@swc/core-win32-ia32-msvc': 1.5.24
|
||||
'@swc/core-win32-x64-msvc': 1.5.24
|
||||
dev: true
|
||||
|
||||
/@swc/counter@0.1.3:
|
||||
@@ -2749,8 +2774,8 @@ packages:
|
||||
react: 18.3.1
|
||||
dev: false
|
||||
|
||||
/@tanstack/react-router@1.34.5(react-dom@18.3.1)(react@18.3.1):
|
||||
resolution: {integrity: sha512-mOMbNHSJ1cAgRuJj9W35wteQL7zFiCNJYgg3QHkxj+obO9zQWiAwycFs0hQTRxqzGfC+jhVLJe1+cW93BhqKyA==}
|
||||
/@tanstack/react-router@1.34.9(react-dom@18.3.1)(react@18.3.1):
|
||||
resolution: {integrity: sha512-dU7rkiWQTH7glJIcnUcFezkmANzqQwlgRiJFiHJOPjYSoKVGI9dWQMbNc8W5d4JjXB/Nhh0QOiI/+GoYZ3WrhA==}
|
||||
engines: {node: '>=12'}
|
||||
peerDependencies:
|
||||
react: '>=16.8'
|
||||
@@ -2774,15 +2799,15 @@ packages:
|
||||
react-dom: 18.3.1(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):
|
||||
resolution: {integrity: sha512-ps0SC14ijFkYkmvqRDlIo7M182Kinvs9syNbQJ5NZT3HjC4yNSZpKWIFkEEDL9IhQvIXQbk0FROz/c7Us7VpsA==}
|
||||
/@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-8gc/ptUjiB/ry1jWCBcPNypfpfQ/eFtC4vqg8dfppvKUm2DwLp0gNM6YvGbcfySTLLei3RLPlmuob0/2vbeS7A==}
|
||||
engines: {node: '>=12'}
|
||||
peerDependencies:
|
||||
'@tanstack/react-router': ^1.34.5
|
||||
'@tanstack/react-router': ^1.34.9
|
||||
react: '>=16.8'
|
||||
react-dom: '>=16.8'
|
||||
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
|
||||
date-fns: 2.30.0
|
||||
goober: 2.1.14(csstype@3.1.3)
|
||||
@@ -2792,16 +2817,16 @@ packages:
|
||||
- csstype
|
||||
dev: true
|
||||
|
||||
/@tanstack/router-generator@1.33.12:
|
||||
resolution: {integrity: sha512-CTZ9yWJkmV4hmVqZnqlrTBfAnjqkWkqF0V5kRdNNK7Q8mfUhL5WPAdZfrCVhq7uBFDEJ1XyMVwn2lQytAMrHnA==}
|
||||
/@tanstack/router-generator@1.34.8:
|
||||
resolution: {integrity: sha512-xi0otLis4zQ8lYVgvNfUTDHKnboL2swXpHcLiG+bKYDihPnw4r3qRO0ULGsqV1n/w6yZAaQ2tZwvZ7Zf0G2pPg==}
|
||||
engines: {node: '>=12'}
|
||||
dependencies:
|
||||
prettier: 3.2.5
|
||||
zod: 3.23.8
|
||||
dev: true
|
||||
|
||||
/@tanstack/router-vite-plugin@1.34.1(vite@5.2.12):
|
||||
resolution: {integrity: sha512-UtSDFKdDzhiFuUUHQp+/bUYkTLnJeYnVXEdXo+D7rUH12i9WFpbZY5dcG3Nj/c8y4WTljwtE6e8N/+yAyXYKeg==}
|
||||
/@tanstack/router-vite-plugin@1.34.8(vite@5.2.12):
|
||||
resolution: {integrity: sha512-FZRl7GVda/1NMEbnalXhYI2e9Qe8MduxXRNZ2cJMYgPR/FxMiao+XQAN+mTD4OoMxJ8gKY+IshgRxCc3xwu6bA==}
|
||||
engines: {node: '>=12'}
|
||||
dependencies:
|
||||
'@babel/core': 7.24.6
|
||||
@@ -2813,7 +2838,7 @@ packages:
|
||||
'@babel/template': 7.24.6
|
||||
'@babel/traverse': 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__generator': 7.6.8
|
||||
'@types/babel__template': 7.4.4
|
||||
@@ -2828,18 +2853,13 @@ packages:
|
||||
/@tanstack/store@0.1.3:
|
||||
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:
|
||||
resolution: {integrity: sha512-Np1opKANzRMF3lgJ9gDquBCB9SxlE2lRmNpVx1+L6RyzAmigkuh0ZulT5jMnDA3JLsuSDU135r/s4t/Pmx4atg==}
|
||||
engines: {node: '>= 18', npm: '>= 6.6.0', yarn: '>= 1.19.1'}
|
||||
dev: false
|
||||
|
||||
/@tauri-apps/cli-darwin-arm64@2.0.0-beta.12:
|
||||
resolution: {integrity: sha512-+Ksrxc4QQE1R62OreCybM8PNnF090qhf+ZeRZTZ9JxOujz4Tq/RiE1vC5nRlDu+Cd3pL63fZ2TsANj2PnpN+qw==}
|
||||
/@tauri-apps/cli-darwin-arm64@2.0.0-beta.20:
|
||||
resolution: {integrity: sha512-oCJOCib7GuYkwkBXx+ekamR8NZZU+2i3MLP+DHpDxK5gS2uhCE+CBkamJkNt6y1x6xdVnwyqZOm5RvN4SRtyIA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
@@ -2847,8 +2867,8 @@ packages:
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@tauri-apps/cli-darwin-x64@2.0.0-beta.12:
|
||||
resolution: {integrity: sha512-KEkcJp7jfWMEiRFTJ3/yJhy8EFwgJPNGnNYjKTD5CAba9wMexxs23U7YLAWdSNrbSgPAox0pBqXaSj6p2HH0Wg==}
|
||||
/@tauri-apps/cli-darwin-x64@2.0.0-beta.20:
|
||||
resolution: {integrity: sha512-lC5QSnRExedYN4Ds6ZlSvC2PxP8qfIYBJQ5ktf+PJI5gQALdNeVtd6YnTG1ODCEklfLq9WKkGwp7JdALTU5wDA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
@@ -2856,8 +2876,8 @@ packages:
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@tauri-apps/cli-linux-arm-gnueabihf@2.0.0-beta.12:
|
||||
resolution: {integrity: sha512-EUDen1XNF/WfG5hjTZ6P/8IcpYPFm2Ox/FwlBPlePKZJrc+/3IveME/oyC3wrloscodV41yL2ise4SZuJtNAHA==}
|
||||
/@tauri-apps/cli-linux-arm-gnueabihf@2.0.0-beta.20:
|
||||
resolution: {integrity: sha512-nZCeBMHHye5DLOJV5k2w658hnCS+LYaOZ8y/G9l3ei+g0L/HBjlSy6r4simsAT5TG8+l3oCZzLBngfTMdDS/YA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
@@ -2865,8 +2885,8 @@ packages:
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@tauri-apps/cli-linux-arm64-gnu@2.0.0-beta.12:
|
||||
resolution: {integrity: sha512-VUhCBB6kaQmi7MyXTRccqemmz7s5n15Z718OIW1n6wI68oh0IsvC9KsrbDfgOLZVG6RXzmtzyXwnaIFQAgl+Cg==}
|
||||
/@tauri-apps/cli-linux-arm64-gnu@2.0.0-beta.20:
|
||||
resolution: {integrity: sha512-B79ISVLPVBgwnCchVqwTKU+vxnFYqxKomcR4rmsvxfs0NVtT5QuNzE1k4NUQnw3966yjwhYR3mnHsSJQSB4Eyw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
@@ -2874,8 +2894,8 @@ packages:
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@tauri-apps/cli-linux-arm64-musl@2.0.0-beta.12:
|
||||
resolution: {integrity: sha512-eus/fGCQZPcvwEa2HH2TJh1Xb2FeeVrnxL6lQseLPkgD7Wcu5mWz07AlLCMDOreUobb5vyOmm0L7d1HMIPrvEQ==}
|
||||
/@tauri-apps/cli-linux-arm64-musl@2.0.0-beta.20:
|
||||
resolution: {integrity: sha512-ojIkv/1uZHhcrgfIN8xgn4BBeo/Xg+bnV0wer6lD78zyxkUMWeEZ+u3mae1ejCJNhhaZOxNaUQ67MvDOiGyr5Q==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
@@ -2883,8 +2903,8 @@ packages:
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@tauri-apps/cli-linux-x64-gnu@2.0.0-beta.12:
|
||||
resolution: {integrity: sha512-C4SkDnjuhFSdSgsK8KNRj2b/oe/j1vrvI8+ZxUj+/o1jK71A2eiCN8pJlyMZYCN8wZUksGazxaFz62g0jceAEg==}
|
||||
/@tauri-apps/cli-linux-x64-gnu@2.0.0-beta.20:
|
||||
resolution: {integrity: sha512-xBy1FNbHKlc7T6pOmFQQPECxJaI5A9QWX7Kb9N64cNVusoOGlvc3xHYkXMS4PTr7xXOT0yiE1Ww2OwDRJ3lYsg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
@@ -2892,8 +2912,8 @@ packages:
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@tauri-apps/cli-linux-x64-musl@2.0.0-beta.12:
|
||||
resolution: {integrity: sha512-V8NlJ8wgNcMfKTGOubgfjRyyJVQsZxpOZkGjdfn/YK0UNdOC6iLuqxIki47hbnoJMqvuxa37lr7Z1JVawOMUyw==}
|
||||
/@tauri-apps/cli-linux-x64-musl@2.0.0-beta.20:
|
||||
resolution: {integrity: sha512-+O6zq5jmtUxA1FUAAwF2ywPysy4NRo2Y6G+ESZDkY9XosRwdt5OUjqAsYktZA3AxDMZVei8r9buwTqUwi9ny/g==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
@@ -2901,8 +2921,8 @@ packages:
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@tauri-apps/cli-win32-arm64-msvc@2.0.0-beta.12:
|
||||
resolution: {integrity: sha512-dIsE5U4JRoVImW0PX9K82/dMjwugqc3DmgKbFKLYXgTFmSNaTNdojI/5VOPmbOIMJ8BNGDF8sjS80I0PZuEqvw==}
|
||||
/@tauri-apps/cli-win32-arm64-msvc@2.0.0-beta.20:
|
||||
resolution: {integrity: sha512-RswgMbWyOQcv53CHvIuiuhAh4kKDqaGyZfWD4VlxqX/XhkoF5gsNgr0MxzrY7pmoL+89oVI+fiGVJz4nOQE5vA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
@@ -2910,8 +2930,8 @@ packages:
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@tauri-apps/cli-win32-ia32-msvc@2.0.0-beta.12:
|
||||
resolution: {integrity: sha512-vTNrGzVV/LclD+4u+IOOwl1Ia2CqwZYK6AmMLp0ROLzbSn/9ROJJYe47V5VAZrnMjThEdb9fRL0FPYPk9yDaNA==}
|
||||
/@tauri-apps/cli-win32-ia32-msvc@2.0.0-beta.20:
|
||||
resolution: {integrity: sha512-5lgWmDVXhX3SBGbiv5SduM1yajiRnUEJClWhSdRrEEJeXdsxpCsBEhxYnUnDCEzPKxLLn5fdBv3VrVctJ03csQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
@@ -2919,8 +2939,8 @@ packages:
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@tauri-apps/cli-win32-x64-msvc@2.0.0-beta.12:
|
||||
resolution: {integrity: sha512-Z/++lJ1FraoN1ZaNxYuCynNm7SyEGC+yd/adYQvhaQyudZa5HW/8eFkLx8FRcIQkEkSqLBkHf9VpwjnTDBVlwQ==}
|
||||
/@tauri-apps/cli-win32-x64-msvc@2.0.0-beta.20:
|
||||
resolution: {integrity: sha512-SuSiiVQTQPSzWlsxQp/NMzWbzDS9TdVDOw7CCfgiG5wnT2GsxzrcIAVN6i7ILsVFLxrjr0bIgPldSJcdcH84Yw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
@@ -2928,87 +2948,81 @@ packages:
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@tauri-apps/cli@2.0.0-beta.12:
|
||||
resolution: {integrity: sha512-MFh4Z093O+PXWI6KFR8E2zIOgpJ4zL8waDhFXVQgBpqiD4ieiqywjmbmNIWkVqYBGAia7ZI0juxpkZSyXT4f1A==}
|
||||
/@tauri-apps/cli@2.0.0-beta.20:
|
||||
resolution: {integrity: sha512-707q9uIc2oNrYHd2dtMvxTrpZXVpart5EIktnRymNOpphkLlB6WUBjHD+ga45WqTU6cNGKbYvkKqTNfshNul9Q==}
|
||||
engines: {node: '>= 10'}
|
||||
hasBin: true
|
||||
optionalDependencies:
|
||||
'@tauri-apps/cli-darwin-arm64': 2.0.0-beta.12
|
||||
'@tauri-apps/cli-darwin-x64': 2.0.0-beta.12
|
||||
'@tauri-apps/cli-linux-arm-gnueabihf': 2.0.0-beta.12
|
||||
'@tauri-apps/cli-linux-arm64-gnu': 2.0.0-beta.12
|
||||
'@tauri-apps/cli-linux-arm64-musl': 2.0.0-beta.12
|
||||
'@tauri-apps/cli-linux-x64-gnu': 2.0.0-beta.12
|
||||
'@tauri-apps/cli-linux-x64-musl': 2.0.0-beta.12
|
||||
'@tauri-apps/cli-win32-arm64-msvc': 2.0.0-beta.12
|
||||
'@tauri-apps/cli-win32-ia32-msvc': 2.0.0-beta.12
|
||||
'@tauri-apps/cli-win32-x64-msvc': 2.0.0-beta.12
|
||||
'@tauri-apps/cli-darwin-arm64': 2.0.0-beta.20
|
||||
'@tauri-apps/cli-darwin-x64': 2.0.0-beta.20
|
||||
'@tauri-apps/cli-linux-arm-gnueabihf': 2.0.0-beta.20
|
||||
'@tauri-apps/cli-linux-arm64-gnu': 2.0.0-beta.20
|
||||
'@tauri-apps/cli-linux-arm64-musl': 2.0.0-beta.20
|
||||
'@tauri-apps/cli-linux-x64-gnu': 2.0.0-beta.20
|
||||
'@tauri-apps/cli-linux-x64-musl': 2.0.0-beta.20
|
||||
'@tauri-apps/cli-win32-arm64-msvc': 2.0.0-beta.20
|
||||
'@tauri-apps/cli-win32-ia32-msvc': 2.0.0-beta.20
|
||||
'@tauri-apps/cli-win32-x64-msvc': 2.0.0-beta.20
|
||||
dev: true
|
||||
|
||||
/@tauri-apps/plugin-autostart@2.0.0-beta.3:
|
||||
resolution: {integrity: sha512-+qIHvO+cfe8MnRxa23l5ebF5rZMt6Jd7FjCD9zoVm/KdYpb0VIhixsYvrLjty9eUWcp8k3XVknfttnvw/veHmg==}
|
||||
/@tauri-apps/plugin-clipboard-manager@2.1.0-beta.3:
|
||||
resolution: {integrity: sha512-j48jTdZWeX5BylIGLYbXtg6ynZAcFKDKK6dguHAbG3zviqNE/XMf2vHOPJeFDZsDE5kVOZZS7nKOj7T2WiCpvw==}
|
||||
dependencies:
|
||||
'@tauri-apps/api': 2.0.0-beta.11
|
||||
'@tauri-apps/api': 2.0.0-beta.13
|
||||
dev: false
|
||||
|
||||
/@tauri-apps/plugin-clipboard-manager@2.1.0-beta.1:
|
||||
resolution: {integrity: sha512-nipr29/eQVlgNw6A1futwsFwWeeS0tBbq1Pww7yb+IZDKfhIFXYJ1NmrEcVGQ6vYz4KPIu+llR6D9pjKl/vSvA==}
|
||||
/@tauri-apps/plugin-dialog@2.0.0-beta.5:
|
||||
resolution: {integrity: sha512-jkaBCsx2v6WB6sB77fTMCeijuvT3FlzwschiHnPlD7aU6CHvQgRlpCv/FttPdTq4ih2t6MIlM4oX85hNYgfs6w==}
|
||||
dependencies:
|
||||
'@tauri-apps/api': 2.0.0-beta.11
|
||||
'@tauri-apps/api': 2.0.0-beta.13
|
||||
dev: false
|
||||
|
||||
/@tauri-apps/plugin-dialog@2.0.0-beta.3:
|
||||
resolution: {integrity: sha512-B3KxLjFxCVB3AIYWN/xY9HM6tO7m4u6aQk1t0ZrBToXThowRU9f62527EO0oj0uNlyKlXx20up68NkpdlER0yg==}
|
||||
/@tauri-apps/plugin-fs@2.0.0-beta.5:
|
||||
resolution: {integrity: sha512-uTqCDFA1z8KDtTe5fKlbRrKV4zxh63UVUvD/PR8GnyNLV+qxj/fEuJuGvMdfMbVKrTljGqSpun5wnP5jqD5fMg==}
|
||||
dependencies:
|
||||
'@tauri-apps/api': 2.0.0-beta.11
|
||||
'@tauri-apps/api': 2.0.0-beta.13
|
||||
dev: false
|
||||
|
||||
/@tauri-apps/plugin-fs@2.0.0-beta.3:
|
||||
resolution: {integrity: sha512-LBgA7S10NwcitHaugIfmCSkewz45vSz1VOpMHhzvE38i1r1KpuTSHlr3MZ0LLq93tH/lvhYZ+3LAml4Sriwthw==}
|
||||
/@tauri-apps/plugin-http@2.0.0-beta.5:
|
||||
resolution: {integrity: sha512-zRWWWCNw/TdewCSrBY8Racm2dwMpkzMOnFmHGExEIoklqUUIq3FU/hkqcX/67Nplf+t1y+WljOxKpCHbm1fOQw==}
|
||||
dependencies:
|
||||
'@tauri-apps/api': 2.0.0-beta.11
|
||||
'@tauri-apps/api': 2.0.0-beta.13
|
||||
dev: false
|
||||
|
||||
/@tauri-apps/plugin-http@2.0.0-beta.3:
|
||||
resolution: {integrity: sha512-TsSFQje2F3Sy5FYuOV+TcgtlvpO9FVdODGT/dJQa+kq/a8Ocel6V57Emj2FiCIyhOJ84us+MiMbQWR1GTfa+mQ==}
|
||||
/@tauri-apps/plugin-notification@2.0.0-beta.5:
|
||||
resolution: {integrity: sha512-5zYK2aT1ZvR+LnuwsnTvg28iEhI7FiZSPvBqQ8y6fj28T4oIHVox19Pk4YRyTyfyJN1nZclXxVAqWLSXLI9SKQ==}
|
||||
dependencies:
|
||||
'@tauri-apps/api': 2.0.0-beta.11
|
||||
'@tauri-apps/api': 2.0.0-beta.13
|
||||
dev: false
|
||||
|
||||
/@tauri-apps/plugin-notification@2.0.0-beta.3:
|
||||
resolution: {integrity: sha512-OCwvX10JrOgJh50HXIGD9EHgGUMtdYAZ+Q5peGCSS3/aA8XWL1GOENtC7CNb0P+W7HjRAj2N8jtEApgV0kD6Gg==}
|
||||
/@tauri-apps/plugin-os@2.0.0-beta.5:
|
||||
resolution: {integrity: sha512-Qfs/clZ9R05J+OVOGkko+9OaYaL+xJaGICeQ1G5CnLFpUdTfMV10D+1nBBauxDdiLU4ay5I0iprJ5aG5GJBunQ==}
|
||||
dependencies:
|
||||
'@tauri-apps/api': 2.0.0-beta.11
|
||||
'@tauri-apps/api': 2.0.0-beta.13
|
||||
dev: false
|
||||
|
||||
/@tauri-apps/plugin-os@2.0.0-beta.3:
|
||||
resolution: {integrity: sha512-kuTfns6z7z/RKAqij3293fnSsgRHlogO/SzNWziFDGHpijGUm/peH70Cv45LKvHw+7kEMX+nfHFWOZm8UEVy/w==}
|
||||
/@tauri-apps/plugin-process@2.0.0-beta.5:
|
||||
resolution: {integrity: sha512-UMiBm6TtNYfxRb6GwzA4PalkZGwalHdclI/t0MVG33fNXgX1PaWONR/NZW/k7JE+WpvRAnN/Kf9ur8aEzjVVSQ==}
|
||||
dependencies:
|
||||
'@tauri-apps/api': 2.0.0-beta.11
|
||||
'@tauri-apps/api': 2.0.0-beta.13
|
||||
dev: false
|
||||
|
||||
/@tauri-apps/plugin-process@2.0.0-beta.3:
|
||||
resolution: {integrity: sha512-N3TkHxoQQb45FP8mbKfcI+e6foR17QlLrrwvPK8SH+LhEP9uo1b5EUvpZM1TIegAmL/3LQIBtJw1loyHVn8Esg==}
|
||||
/@tauri-apps/plugin-shell@2.0.0-beta.6:
|
||||
resolution: {integrity: sha512-g3nM9cQQGl7Iv4MvyFuco/aPTiwOI/MixcoKso3VQIg5Aqd64NqR0r+GfsB0qx52txItqzSXwmeaj1eZjO9Q6Q==}
|
||||
dependencies:
|
||||
'@tauri-apps/api': 2.0.0-beta.11
|
||||
'@tauri-apps/api': 2.0.0-beta.13
|
||||
dev: false
|
||||
|
||||
/@tauri-apps/plugin-shell@2.0.0-beta.4:
|
||||
resolution: {integrity: sha512-y9APB3ZLtkY4IM6syE+dn/R4XR2UTHBcAI/n9L5PgnvOjJAuuEzVYURn10G51nKnLw+sFbxO7LW4VW66Uoaa9g==}
|
||||
/@tauri-apps/plugin-updater@2.0.0-beta.5:
|
||||
resolution: {integrity: sha512-h8uNFQDtXaZPFyQcNAB5SxiSIvPbYRlM1fXf/lCcW8bAaMTanyszbHvR2vAYtVa/wIzKAOYriC/MpsJaKLjyhg==}
|
||||
dependencies:
|
||||
'@tauri-apps/api': 2.0.0-beta.11
|
||||
'@tauri-apps/api': 2.0.0-beta.13
|
||||
dev: false
|
||||
|
||||
/@tauri-apps/plugin-updater@2.0.0-beta.3:
|
||||
resolution: {integrity: sha512-bD1ikPz80uK9YJKNYpYlA6StSp9lr0Ob1kGLG2XdmOgspv7SZLLNVzMORtKeqgepxwG99qdYGDDegT3Ll6+UlA==}
|
||||
/@tauri-apps/plugin-upload@2.0.0-beta.6:
|
||||
resolution: {integrity: sha512-CbIj5ew167/Xfi/tsomPlRhty8U52m+FXEIj2tVNu6F1Kc6NEfd5BQM6iLf6RfKQ4WsUo+zwUkftnlpgP4FCpA==}
|
||||
dependencies:
|
||||
'@tauri-apps/api': 2.0.0-beta.11
|
||||
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
|
||||
'@tauri-apps/api': 2.0.0-beta.13
|
||||
dev: false
|
||||
|
||||
/@types/babel__core@7.20.5:
|
||||
@@ -3110,7 +3124,7 @@ packages:
|
||||
peerDependencies:
|
||||
vite: ^4 || ^5
|
||||
dependencies:
|
||||
'@swc/core': 1.5.7
|
||||
'@swc/core': 1.5.24
|
||||
vite: 5.2.12
|
||||
transitivePeerDependencies:
|
||||
- '@swc/helpers'
|
||||
@@ -3335,7 +3349,7 @@ packages:
|
||||
common-ancestor-path: 1.0.1
|
||||
cookie: 0.6.0
|
||||
cssesc: 3.0.0
|
||||
debug: 4.3.4
|
||||
debug: 4.3.5
|
||||
deterministic-object-hash: 2.0.2
|
||||
devalue: 5.0.0
|
||||
diff: 5.2.0
|
||||
@@ -3367,7 +3381,7 @@ packages:
|
||||
shiki: 1.6.1
|
||||
string-width: 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
|
||||
vfile: 6.0.1
|
||||
vite: 5.2.12
|
||||
@@ -3467,7 +3481,7 @@ packages:
|
||||
hasBin: true
|
||||
dependencies:
|
||||
caniuse-lite: 1.0.30001625
|
||||
electron-to-chromium: 1.4.783
|
||||
electron-to-chromium: 1.4.788
|
||||
node-releases: 2.0.14
|
||||
update-browserslist-db: 1.0.16(browserslist@4.23.0)
|
||||
|
||||
@@ -3649,8 +3663,8 @@ packages:
|
||||
resolution: {integrity: sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg==}
|
||||
dev: false
|
||||
|
||||
/debug@4.3.4:
|
||||
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
|
||||
/debug@4.3.5:
|
||||
resolution: {integrity: sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==}
|
||||
engines: {node: '>=6.0'}
|
||||
peerDependencies:
|
||||
supports-color: '*'
|
||||
@@ -3723,8 +3737,8 @@ packages:
|
||||
/eastasianwidth@0.2.0:
|
||||
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
|
||||
|
||||
/electron-to-chromium@1.4.783:
|
||||
resolution: {integrity: sha512-bT0jEz/Xz1fahQpbZ1D7LgmPYZ3iHVY39NcWWro1+hA2IvjiPeaXtfSqrQ+nXjApMvQRE2ASt1itSLRrebHMRQ==}
|
||||
/electron-to-chromium@1.4.788:
|
||||
resolution: {integrity: sha512-ubp5+Ev/VV8KuRoWnfP2QF2Bg+O2ZFdb49DiiNbz2VmgkIqrnyYaqIOqj8A6K/3p1xV0QcU5hBQ1+BmB6ot1OA==}
|
||||
|
||||
/emmet@2.4.7:
|
||||
resolution: {integrity: sha512-O5O5QNqtdlnQM2bmKHtJgyChcrFMgQuulI+WdiOw2NArzprUqqxUW6bgYtKvzKgrsYpuLWalOkdhNP+1jluhCA==}
|
||||
@@ -4821,7 +4835,7 @@ packages:
|
||||
resolution: {integrity: sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ==}
|
||||
dependencies:
|
||||
'@types/debug': 4.1.12
|
||||
debug: 4.3.4
|
||||
debug: 4.3.5
|
||||
decode-named-character-reference: 1.0.2
|
||||
devlop: 1.1.0
|
||||
micromark-core-commonmark: 2.0.1
|
||||
@@ -4925,8 +4939,8 @@ packages:
|
||||
resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
/nostr-tools@2.6.0(typescript@5.4.5):
|
||||
resolution: {integrity: sha512-7Jm+rZZPd8TjfHhkxuQHtUZV+3Dd1gl5YqFLMWhmtPBsPDyFJMBsLaPtrBmuWjYGxzoOYop98hw+chI2Xaq0UQ==}
|
||||
/nostr-tools@2.7.0(typescript@5.4.5):
|
||||
resolution: {integrity: sha512-jJoL2J1CBiKDxaXZww27nY/Wsuxzx7AULxmGKFce4sskDu1tohNyfnzYQ8BvDyvkstU8kNZUAXPL32tre33uig==}
|
||||
peerDependencies:
|
||||
typescript: '>=5.0.0'
|
||||
peerDependenciesMeta:
|
||||
@@ -5884,8 +5898,8 @@ packages:
|
||||
/ts-interface-checker@0.1.13:
|
||||
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
|
||||
|
||||
/tsconfck@3.0.3(typescript@5.4.5):
|
||||
resolution: {integrity: sha512-4t0noZX9t6GcPTfBAbIbbIU4pfpCwh0ueq3S4O/5qXI1VwK1outmxhe9dOiEWqMz3MW2LKgDTpqWV+37IWuVbA==}
|
||||
/tsconfck@3.1.0(typescript@5.4.5):
|
||||
resolution: {integrity: sha512-CMjc5zMnyAjcS9sPLytrbFmj89st2g+JYtY/c02ug4Q+CZaAtCgbyviI0n1YvjZE/pzoc6FbNsINS13DOL1B9w==}
|
||||
engines: {node: ^18 || >=20}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
@@ -6229,7 +6243,7 @@ packages:
|
||||
vite: '>=2.8'
|
||||
dependencies:
|
||||
'@rollup/plugin-virtual': 3.0.2
|
||||
'@swc/core': 1.5.7
|
||||
'@swc/core': 1.5.24
|
||||
uuid: 9.0.1
|
||||
vite: 5.2.12
|
||||
transitivePeerDependencies:
|
||||
@@ -6245,9 +6259,9 @@ packages:
|
||||
vite:
|
||||
optional: true
|
||||
dependencies:
|
||||
debug: 4.3.4
|
||||
debug: 4.3.5
|
||||
globrex: 0.1.2
|
||||
tsconfck: 3.0.3(typescript@5.4.5)
|
||||
tsconfck: 3.1.0(typescript@5.4.5)
|
||||
vite: 5.2.12
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
1096
src-tauri/Cargo.lock
generated
1096
src-tauri/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,7 @@ description = "nostr client"
|
||||
authors = ["npub1zfss807aer0j26mwp2la0ume0jqde3823rmu97ra6sgyyg956e0s6xw445"]
|
||||
repository = "https://github.com/lumehq/lume"
|
||||
edition = "2021"
|
||||
rust-version = "1.68"
|
||||
rust-version = "1.70"
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "2.0.0-beta", features = [] }
|
||||
@@ -15,6 +15,7 @@ nostr-sdk = { version = "0.31", features = ["sqlite"] }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
monitor = { git = "https://github.com/ahkohd/tauri-toolkit", branch = "v2" }
|
||||
tauri = { version = "2.0.0-beta", features = [
|
||||
"unstable",
|
||||
"tray-icon",
|
||||
@@ -22,24 +23,23 @@ tauri = { version = "2.0.0-beta", features = [
|
||||
"native-tls-vendored",
|
||||
"protocol-asset",
|
||||
] }
|
||||
tauri-plugin-clipboard-manager = "2.1.0-beta"
|
||||
tauri-plugin-dialog = "2.0.0-beta"
|
||||
tauri-plugin-fs = "2.0.0-beta"
|
||||
tauri-plugin-http = "2.0.0-beta"
|
||||
tauri-plugin-notification = "2.0.0-beta"
|
||||
tauri-plugin-os = "2.0.0-beta"
|
||||
tauri-plugin-process = "2.0.0-beta"
|
||||
tauri-plugin-shell = "2.0.0-beta"
|
||||
tauri-plugin-updater = "2.0.0-beta"
|
||||
tauri-plugin-upload = "2.0.0-beta"
|
||||
tauri-plugin-window-state = "2.0.0-beta"
|
||||
tauri-plugin-clipboard-manager = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
|
||||
tauri-plugin-dialog = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
|
||||
tauri-plugin-fs = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
|
||||
tauri-plugin-http = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
|
||||
tauri-plugin-notification = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
|
||||
tauri-plugin-os = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
|
||||
tauri-plugin-process = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
|
||||
tauri-plugin-shell = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
|
||||
tauri-plugin-updater = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
|
||||
tauri-plugin-upload = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
|
||||
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"
|
||||
webpage = { version = "2.0", features = ["serde"] }
|
||||
specta = "^2.0.0-rc.12"
|
||||
keyring = "2"
|
||||
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]
|
||||
cocoa = "0.25.0"
|
||||
|
||||
@@ -1,85 +1,86 @@
|
||||
{
|
||||
"$schema": "../gen/schemas/desktop-schema.json",
|
||||
"identifier": "desktop-capability",
|
||||
"description": "Capability for the desktop",
|
||||
"platforms": ["linux", "macOS", "windows"],
|
||||
"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/*"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
"$schema": "../gen/schemas/desktop-schema.json",
|
||||
"identifier": "desktop-capability",
|
||||
"description": "Capability for the desktop",
|
||||
"platforms": ["linux", "macOS", "windows"],
|
||||
"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/*"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -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"]}}
|
||||
@@ -5863,6 +5863,13 @@
|
||||
"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.",
|
||||
"type": "string",
|
||||
@@ -5870,6 +5877,13 @@
|
||||
"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.",
|
||||
"type": "string",
|
||||
@@ -5877,6 +5891,13 @@
|
||||
"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.",
|
||||
"type": "string",
|
||||
@@ -5884,6 +5905,13 @@
|
||||
"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",
|
||||
"enum": [
|
||||
@@ -6261,6 +6289,13 @@
|
||||
"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.",
|
||||
"type": "string",
|
||||
@@ -6695,6 +6730,13 @@
|
||||
"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.",
|
||||
"type": "string",
|
||||
@@ -6967,54 +7009,6 @@
|
||||
"enum": [
|
||||
"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"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -5863,6 +5863,13 @@
|
||||
"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.",
|
||||
"type": "string",
|
||||
@@ -5870,6 +5877,13 @@
|
||||
"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.",
|
||||
"type": "string",
|
||||
@@ -5877,6 +5891,13 @@
|
||||
"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.",
|
||||
"type": "string",
|
||||
@@ -5884,6 +5905,13 @@
|
||||
"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",
|
||||
"enum": [
|
||||
@@ -6261,6 +6289,13 @@
|
||||
"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.",
|
||||
"type": "string",
|
||||
@@ -6695,6 +6730,13 @@
|
||||
"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.",
|
||||
"type": "string",
|
||||
@@ -6967,54 +7009,6 @@
|
||||
"enum": [
|
||||
"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
src-tauri/src/fns.rs
Normal file
180
src-tauri/src/fns.rs
Normal 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()
|
||||
}
|
||||
@@ -4,8 +4,8 @@
|
||||
)]
|
||||
|
||||
pub mod commands;
|
||||
pub mod fns;
|
||||
pub mod nostr;
|
||||
pub mod tray;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
extern crate cocoa;
|
||||
@@ -17,8 +17,18 @@ extern crate objc;
|
||||
use nostr_sdk::prelude::*;
|
||||
use std::fs;
|
||||
use tauri::Manager;
|
||||
use tauri_nspanel::ManagerExt;
|
||||
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 {
|
||||
client: Client,
|
||||
}
|
||||
@@ -34,13 +44,11 @@ fn main() {
|
||||
nostr::keys::create_account,
|
||||
nostr::keys::save_account,
|
||||
nostr::keys::get_encrypted_key,
|
||||
nostr::keys::nostr_connect,
|
||||
nostr::keys::connect_remote_account,
|
||||
nostr::keys::load_account,
|
||||
nostr::keys::event_to_bech32,
|
||||
nostr::keys::user_to_bech32,
|
||||
nostr::keys::to_npub,
|
||||
nostr::keys::verify_nip05,
|
||||
nostr::metadata::get_activities,
|
||||
nostr::metadata::get_current_user_profile,
|
||||
nostr::metadata::get_profile,
|
||||
nostr::metadata::get_contact_list,
|
||||
@@ -56,6 +64,7 @@ fn main() {
|
||||
nostr::metadata::zap_profile,
|
||||
nostr::metadata::zap_event,
|
||||
nostr::metadata::friend_to_friend,
|
||||
nostr::metadata::get_notifications,
|
||||
nostr::event::get_event,
|
||||
nostr::event::get_replies,
|
||||
nostr::event::get_events_by,
|
||||
@@ -91,20 +100,46 @@ fn main() {
|
||||
#[cfg(target_os = "macos")]
|
||||
main_window.set_traffic_lights_inset(8.0, 16.0).unwrap();
|
||||
|
||||
// Setup app tray
|
||||
let handle = app.handle().clone();
|
||||
tray::create_tray(app.handle()).unwrap();
|
||||
// Create panel
|
||||
#[cfg(target_os = "macos")]
|
||||
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
|
||||
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 {
|
||||
// Create nostr database connection
|
||||
let sqlite = SQLiteDatabase::open(home_dir.join("Lume/lume.db")).await;
|
||||
|
||||
// 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(),
|
||||
Err(_) => ClientBuilder::default().build(),
|
||||
};
|
||||
@@ -130,11 +165,12 @@ fn main() {
|
||||
client.connect().await;
|
||||
|
||||
// Update global state
|
||||
handle.manage(Nostr { client })
|
||||
app.handle().manage(Nostr { client })
|
||||
});
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.plugin(tauri_nspanel::init())
|
||||
.plugin(tauri_plugin_theme::init(ctx.config_mut()))
|
||||
.plugin(tauri_plugin_decorum::init())
|
||||
.plugin(tauri_plugin_clipboard_manager::init())
|
||||
|
||||
@@ -13,9 +13,8 @@ pub async fn get_event(id: &str, state: State<'_, Nostr>) -> Result<String, Stri
|
||||
Nip19::Event(event) => {
|
||||
let relays = event.relays;
|
||||
for relay in relays.into_iter() {
|
||||
let url = Url::from_str(&relay).unwrap();
|
||||
let _ = client.add_relay(&url).await.unwrap_or_default();
|
||||
client.connect_relay(&url).await.unwrap_or_default();
|
||||
let _ = client.add_relay(&relay).await.unwrap_or_default();
|
||||
client.connect_relay(&relay).await.unwrap_or_default();
|
||||
}
|
||||
Some(event.event_id)
|
||||
}
|
||||
@@ -122,11 +121,11 @@ pub async fn get_local_events(
|
||||
let filter = Filter::new()
|
||||
.kinds(vec![Kind::TextNote, Kind::Repost])
|
||||
.limit(20)
|
||||
.authors(authors)
|
||||
.until(as_of);
|
||||
.until(as_of)
|
||||
.authors(authors);
|
||||
|
||||
match client
|
||||
.get_events_of(vec![filter], Some(Duration::from_secs(8)))
|
||||
.get_events_of(vec![filter], Some(Duration::from_secs(10)))
|
||||
.await
|
||||
{
|
||||
Ok(events) => Ok(events.into_iter().map(|ev| ev.as_json()).collect()),
|
||||
|
||||
@@ -6,7 +6,8 @@ use serde::Serialize;
|
||||
use specta::Type;
|
||||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
use tauri::{Manager, State};
|
||||
use tauri::{EventTarget, Manager, State};
|
||||
use tauri_plugin_notification::NotificationExt;
|
||||
|
||||
#[derive(Serialize, Type)]
|
||||
pub struct Account {
|
||||
@@ -102,158 +103,236 @@ pub async fn save_account(
|
||||
#[specta::specta]
|
||||
pub async fn load_account(
|
||||
npub: &str,
|
||||
bunker: Option<&str>,
|
||||
state: State<'_, Nostr>,
|
||||
app: tauri::AppHandle,
|
||||
) -> Result<bool, String> {
|
||||
let handle = app.clone();
|
||||
let client = &state.client;
|
||||
let keyring = Entry::new(npub, "nostr_secret").unwrap();
|
||||
|
||||
match keyring.get_password() {
|
||||
Ok(password) => {
|
||||
if password.starts_with("bunker://") {
|
||||
let local_keyring = Entry::new(npub, "bunker_local_account").unwrap();
|
||||
if let Ok(password) = keyring.get_password() {
|
||||
match bunker {
|
||||
Some(uri) => {
|
||||
let app_keys = Keys::parse(password).expect("Secret Key is modified, please check again.");
|
||||
|
||||
match local_keyring.get_password() {
|
||||
Ok(local_password) => {
|
||||
let secret_key = SecretKey::from_bech32(local_password).unwrap();
|
||||
let app_keys = Keys::new(secret_key);
|
||||
let bunker_uri = NostrConnectURI::parse(password).unwrap();
|
||||
let signer = Nip46Signer::new(bunker_uri, app_keys, Duration::from_secs(60), None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Update signer
|
||||
client.set_signer(Some(signer.into())).await;
|
||||
match NostrConnectURI::parse(uri) {
|
||||
Ok(bunker_uri) => {
|
||||
match Nip46Signer::new(bunker_uri, app_keys, Duration::from_secs(30), None).await {
|
||||
Ok(signer) => client.set_signer(Some(signer.into())).await,
|
||||
Err(err) => return Err(err.to_string()),
|
||||
}
|
||||
}
|
||||
Err(_) => todo!(),
|
||||
Err(err) => return Err(err.to_string()),
|
||||
}
|
||||
} else {
|
||||
let secret_key = SecretKey::from_bech32(password).expect("Get secret key failed");
|
||||
let keys = Keys::new(secret_key);
|
||||
}
|
||||
None => {
|
||||
let keys = Keys::parse(password).expect("Secret Key is modified, please check again.");
|
||||
let signer = NostrSigner::Keys(keys);
|
||||
|
||||
// Update signer
|
||||
client.set_signer(Some(signer)).await;
|
||||
}
|
||||
}
|
||||
|
||||
// Verify signer
|
||||
let signer = client.signer().await.unwrap();
|
||||
let public_key = signer.public_key().await.unwrap();
|
||||
// Verify signer
|
||||
let signer = client.signer().await.unwrap();
|
||||
let public_key = signer.public_key().await.unwrap();
|
||||
|
||||
let filter = Filter::new()
|
||||
.author(public_key)
|
||||
.kind(Kind::RelayList)
|
||||
.limit(1);
|
||||
let filter = Filter::new()
|
||||
.author(public_key)
|
||||
.kind(Kind::RelayList)
|
||||
.limit(1);
|
||||
|
||||
// Connect to user's relay (NIP-65)
|
||||
// #TODO: Let rust-nostr handle it
|
||||
match client
|
||||
.get_events_of(vec![filter], Some(Duration::from_secs(10)))
|
||||
.await
|
||||
{
|
||||
Ok(events) => {
|
||||
if let Some(event) = events.first() {
|
||||
let relay_list = nip65::extract_relay_list(event);
|
||||
for item in relay_list.into_iter() {
|
||||
println!("connecting to relay: {} - {:?}", item.0, item.1);
|
||||
// Connect to user's relay (NIP-65)
|
||||
// #TODO: Let rust-nostr handle it
|
||||
if let Ok(events) = client
|
||||
.get_events_of(vec![filter], Some(Duration::from_secs(10)))
|
||||
.await
|
||||
{
|
||||
if let Some(event) = events.first() {
|
||||
let relay_list = nip65::extract_relay_list(event);
|
||||
for item in relay_list.into_iter() {
|
||||
println!("connecting to relay: {} - {:?}", item.0, item.1);
|
||||
|
||||
let relay_url = item.0.to_string();
|
||||
let opts = match item.1 {
|
||||
Some(val) => {
|
||||
if val == &RelayMetadata::Read {
|
||||
RelayOptions::new().read(true).write(false)
|
||||
} else {
|
||||
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());
|
||||
let relay_url = item.0.to_string();
|
||||
let opts = match item.1 {
|
||||
Some(val) => {
|
||||
if val == &RelayMetadata::Read {
|
||||
RelayOptions::new().read(true).write(false)
|
||||
} else {
|
||||
RelayOptions::new().write(true).read(false)
|
||||
}
|
||||
}
|
||||
Ok(false)
|
||||
})
|
||||
.await;
|
||||
});
|
||||
None => RelayOptions::new(),
|
||||
};
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
Err(err) => Err(err.to_string()),
|
||||
// Add relay to relay pool
|
||||
let _ = client
|
||||
.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]
|
||||
#[specta::specta]
|
||||
pub async fn nostr_connect(
|
||||
npub: &str,
|
||||
uri: &str,
|
||||
state: State<'_, Nostr>,
|
||||
) -> Result<String, String> {
|
||||
pub async fn connect_remote_account(uri: &str, state: State<'_, Nostr>) -> Result<String, String> {
|
||||
let client = &state.client;
|
||||
let local_key = Keys::generate();
|
||||
|
||||
match NostrConnectURI::parse(uri) {
|
||||
Ok(bunker_uri) => {
|
||||
match Nip46Signer::new(
|
||||
bunker_uri,
|
||||
local_key.clone(),
|
||||
Duration::from_secs(120),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
{
|
||||
let app_keys = Keys::generate();
|
||||
let app_secret = app_keys.secret_key().unwrap().to_string();
|
||||
|
||||
// Get remote user
|
||||
let remote_user = bunker_uri.signer_public_key().unwrap();
|
||||
let remote_npub = remote_user.to_bech32().unwrap();
|
||||
|
||||
match Nip46Signer::new(bunker_uri, app_keys, Duration::from_secs(120), None).await {
|
||||
Ok(signer) => {
|
||||
let local_secret = local_key.secret_key().unwrap().to_bech32().unwrap();
|
||||
let secret_keyring = Entry::new(&npub, "nostr_secret").unwrap();
|
||||
let account_keyring = Entry::new(&npub, "bunker_local_account").unwrap();
|
||||
let _ = secret_keyring.set_password(uri);
|
||||
let _ = account_keyring.set_password(&local_secret);
|
||||
let keyring = Entry::new(&remote_npub, "nostr_secret").unwrap();
|
||||
let _ = keyring.set_password(&app_secret);
|
||||
|
||||
// Update signer
|
||||
let _ = client.set_signer(Some(signer.into())).await;
|
||||
|
||||
Ok(npub.into())
|
||||
Ok(remote_npub)
|
||||
}
|
||||
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())
|
||||
}
|
||||
|
||||
#[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]
|
||||
#[specta::specta]
|
||||
pub async fn verify_nip05(key: &str, nip05: &str) -> Result<bool, String> {
|
||||
|
||||
@@ -1,76 +1,9 @@
|
||||
use super::get_latest_event;
|
||||
use crate::Nostr;
|
||||
use keyring::Entry;
|
||||
use nostr_sdk::prelude::*;
|
||||
use std::{str::FromStr, time::Duration};
|
||||
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]
|
||||
#[specta::specta]
|
||||
@@ -88,7 +21,7 @@ pub async fn get_current_user_profile(state: State<'_, Nostr>) -> Result<String,
|
||||
.await
|
||||
{
|
||||
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) {
|
||||
Ok(metadata.as_json())
|
||||
} 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) {
|
||||
Ok(val) => match val {
|
||||
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,
|
||||
},
|
||||
Err(_) => match PublicKey::from_str(id) {
|
||||
@@ -464,3 +404,132 @@ pub async fn zap_event(
|
||||
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 metadata;
|
||||
pub mod relay;
|
||||
mod utils;
|
||||
pub use utils::get_latest_event;
|
||||
5
src-tauri/src/nostr/utils.rs
Normal file
5
src-tauri/src/nostr/utils.rs
Normal 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())
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -1,123 +1,124 @@
|
||||
{
|
||||
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
|
||||
"productName": "Lume",
|
||||
"version": "4.0.7",
|
||||
"identifier": "nu.lume.Lume",
|
||||
"build": {
|
||||
"beforeBuildCommand": "pnpm desktop:build",
|
||||
"beforeDevCommand": "pnpm desktop:dev",
|
||||
"devUrl": "http://localhost:3000",
|
||||
"frontendDist": "../dist"
|
||||
},
|
||||
"app": {
|
||||
"macOSPrivateApi": true,
|
||||
"withGlobalTauri": true,
|
||||
"trayIcon": {
|
||||
"id": "main_tray",
|
||||
"iconPath": "./icons/tray.png",
|
||||
"iconAsTemplate": true
|
||||
},
|
||||
"security": {
|
||||
"assetProtocol": {
|
||||
"enable": true,
|
||||
"scope": [
|
||||
"$APPDATA/*",
|
||||
"$DATA/*",
|
||||
"$LOCALDATA/*",
|
||||
"$DESKTOP/*",
|
||||
"$DOCUMENT/*",
|
||||
"$DOWNLOAD/*",
|
||||
"$HOME/*",
|
||||
"$PICTURE/*",
|
||||
"$PUBLIC/*",
|
||||
"$VIDEO/*",
|
||||
"$APPCONFIG/*",
|
||||
"$RESOURCE/*"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"bundle": {
|
||||
"licenseFile": "../LICENSE",
|
||||
"longDescription": "nostr client for desktop",
|
||||
"shortDescription": "nostr client",
|
||||
"targets": "all",
|
||||
"active": true,
|
||||
"category": "SocialNetworking",
|
||||
"resources": ["resources/*", "locales/*"],
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
],
|
||||
"linux": {
|
||||
"appimage": {
|
||||
"bundleMediaFramework": true,
|
||||
"files": {}
|
||||
},
|
||||
"deb": {
|
||||
"files": {}
|
||||
},
|
||||
"rpm": {
|
||||
"epoch": 0,
|
||||
"files": {},
|
||||
"release": "1"
|
||||
}
|
||||
},
|
||||
"macOS": {
|
||||
"dmg": {
|
||||
"appPosition": {
|
||||
"x": 180,
|
||||
"y": 170
|
||||
},
|
||||
"applicationFolderPosition": {
|
||||
"x": 480,
|
||||
"y": 170
|
||||
},
|
||||
"windowSize": {
|
||||
"height": 400,
|
||||
"width": 660
|
||||
}
|
||||
},
|
||||
"files": {},
|
||||
"minimumSystemVersion": "10.15"
|
||||
},
|
||||
"windows": {
|
||||
"allowDowngrades": true,
|
||||
"certificateThumbprint": null,
|
||||
"digestAlgorithm": "sha256",
|
||||
"nsis": null,
|
||||
"timestampUrl": null,
|
||||
"tsp": false,
|
||||
"webviewFixedRuntimePath": null,
|
||||
"webviewInstallMode": {
|
||||
"silent": true,
|
||||
"type": "downloadBootstrapper"
|
||||
},
|
||||
"wix": null
|
||||
},
|
||||
"fileAssociations": [
|
||||
{
|
||||
"name": "bech32",
|
||||
"description": "Nostr Bech32",
|
||||
"ext": ["npub", "nsec", "nprofile", "nevent", "naddr", "nrelay"],
|
||||
"role": "Viewer"
|
||||
}
|
||||
]
|
||||
},
|
||||
"plugins": {
|
||||
"updater": {
|
||||
"active": true,
|
||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEU3OTdCMkM3RjU5QzE2NzkKUldSNUZwejF4N0tYNTVHYjMrU0JkL090SlEyNUVLYU5TM2hTU3RXSWtEWngrZWJ4a0pydUhXZHEK",
|
||||
"windows": {
|
||||
"installMode": "quiet"
|
||||
},
|
||||
"endpoints": [
|
||||
"https://lus.reya3772.workers.dev/v1/{{target}}/{{arch}}/{{current_version}}",
|
||||
"https://lus.reya3772.workers.dev/{{target}}/{{current_version}}"
|
||||
]
|
||||
}
|
||||
}
|
||||
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
|
||||
"productName": "Lume",
|
||||
"version": "4.0.9",
|
||||
"identifier": "nu.lume.Lume",
|
||||
"build": {
|
||||
"beforeBuildCommand": "pnpm desktop:build",
|
||||
"beforeDevCommand": "pnpm desktop:dev",
|
||||
"devUrl": "http://localhost:3000",
|
||||
"frontendDist": "../dist"
|
||||
},
|
||||
"app": {
|
||||
"macOSPrivateApi": true,
|
||||
"withGlobalTauri": true,
|
||||
"trayIcon": {
|
||||
"id": "tray_panel",
|
||||
"iconPath": "./icons/tray.png",
|
||||
"iconAsTemplate": true,
|
||||
"menuOnLeftClick": false
|
||||
},
|
||||
"security": {
|
||||
"assetProtocol": {
|
||||
"enable": true,
|
||||
"scope": [
|
||||
"$APPDATA/*",
|
||||
"$DATA/*",
|
||||
"$LOCALDATA/*",
|
||||
"$DESKTOP/*",
|
||||
"$DOCUMENT/*",
|
||||
"$DOWNLOAD/*",
|
||||
"$HOME/*",
|
||||
"$PICTURE/*",
|
||||
"$PUBLIC/*",
|
||||
"$VIDEO/*",
|
||||
"$APPCONFIG/*",
|
||||
"$RESOURCE/*"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"bundle": {
|
||||
"licenseFile": "../LICENSE",
|
||||
"longDescription": "nostr client for desktop",
|
||||
"shortDescription": "nostr client",
|
||||
"targets": "all",
|
||||
"active": true,
|
||||
"category": "SocialNetworking",
|
||||
"resources": ["resources/*", "locales/*"],
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
],
|
||||
"linux": {
|
||||
"appimage": {
|
||||
"bundleMediaFramework": true,
|
||||
"files": {}
|
||||
},
|
||||
"deb": {
|
||||
"files": {}
|
||||
},
|
||||
"rpm": {
|
||||
"epoch": 0,
|
||||
"files": {},
|
||||
"release": "1"
|
||||
}
|
||||
},
|
||||
"macOS": {
|
||||
"dmg": {
|
||||
"appPosition": {
|
||||
"x": 180,
|
||||
"y": 170
|
||||
},
|
||||
"applicationFolderPosition": {
|
||||
"x": 480,
|
||||
"y": 170
|
||||
},
|
||||
"windowSize": {
|
||||
"height": 400,
|
||||
"width": 660
|
||||
}
|
||||
},
|
||||
"files": {},
|
||||
"minimumSystemVersion": "10.15"
|
||||
},
|
||||
"windows": {
|
||||
"allowDowngrades": true,
|
||||
"certificateThumbprint": null,
|
||||
"digestAlgorithm": "sha256",
|
||||
"nsis": null,
|
||||
"timestampUrl": null,
|
||||
"tsp": false,
|
||||
"webviewFixedRuntimePath": null,
|
||||
"webviewInstallMode": {
|
||||
"silent": true,
|
||||
"type": "downloadBootstrapper"
|
||||
},
|
||||
"wix": null
|
||||
},
|
||||
"fileAssociations": [
|
||||
{
|
||||
"name": "bech32",
|
||||
"description": "Nostr Bech32",
|
||||
"ext": ["npub", "nsec", "nprofile", "nevent", "naddr", "nrelay"],
|
||||
"role": "Viewer"
|
||||
}
|
||||
]
|
||||
},
|
||||
"plugins": {
|
||||
"updater": {
|
||||
"active": true,
|
||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEU3OTdCMkM3RjU5QzE2NzkKUldSNUZwejF4N0tYNTVHYjMrU0JkL090SlEyNUVLYU5TM2hTU3RXSWtEWngrZWJ4a0pydUhXZHEK",
|
||||
"windows": {
|
||||
"installMode": "quiet"
|
||||
},
|
||||
"endpoints": [
|
||||
"https://lus.reya3772.workers.dev/v1/{{target}}/{{arch}}/{{current_version}}",
|
||||
"https://lus.reya3772.workers.dev/{{target}}/{{current_version}}"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,21 @@
|
||||
"windowEffects": {
|
||||
"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"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user