Event Subscriptions (#218)

* feat: improve create column command

* refactor: thread

* feat: add window virtualized to event screen

* chore: update deps

* fix: window decoration

* feat: improve mention ntoe

* feat: add subscription to event screen
This commit is contained in:
雨宮蓮 2024-06-26 14:51:50 +07:00 committed by GitHub
parent a4540a0802
commit 717c3e17df
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
45 changed files with 2504 additions and 2150 deletions

View File

@ -1,14 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Lume Desktop</title>
</head>
<body
class="relative h-screen w-screen cursor-default select-none overflow-hidden font-sans text-black antialiased dark:text-white"
>
<div id="root" class="h-full w-full"></div>
<script type="module" src="/src/app.tsx"></script>
</body>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Lume Desktop</title>
</head>
<body
class="relative h-screen w-screen cursor-default select-none overflow-hidden font-sans text-black antialiased dark:text-white"
>
<div id="root" class="h-full w-full"></div>
<script type="module" src="/src/app.tsx"></script>
</body>
</html>

View File

@ -14,16 +14,16 @@
"@lume/system": "workspace:^",
"@lume/ui": "workspace:^",
"@lume/utils": "workspace:^",
"@radix-ui/react-avatar": "^1.0.4",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-avatar": "^1.1.0",
"@radix-ui/react-checkbox": "^1.1.0",
"@radix-ui/react-popover": "^1.1.1",
"@radix-ui/react-scroll-area": "^1.1.0",
"@radix-ui/react-switch": "^1.0.3",
"@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-tooltip": "^1.0.7",
"@tanstack/query-persist-client-core": "^5.45.0",
"@tanstack/react-query": "^5.45.0",
"@tanstack/react-router": "^1.38.1",
"@radix-ui/react-switch": "^1.1.0",
"@radix-ui/react-tabs": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.1",
"@tanstack/query-persist-client-core": "^5.48.0",
"@tanstack/react-query": "^5.48.0",
"@tanstack/react-router": "^1.40.0",
"embla-carousel-react": "^8.1.5",
"i18next": "^23.11.5",
"i18next-resources-to-backend": "^1.2.1",
@ -45,15 +45,15 @@
"@lume/tailwindcss": "workspace:^",
"@lume/tsconfig": "workspace:^",
"@lume/types": "workspace:^",
"@tanstack/router-devtools": "^1.38.1",
"@tanstack/router-vite-plugin": "^1.38.0",
"@tanstack/router-devtools": "^1.40.0",
"@tanstack/router-vite-plugin": "^1.39.13",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react-swc": "^3.7.0",
"autoprefixer": "^10.4.19",
"postcss": "^8.4.38",
"tailwindcss": "^3.4.4",
"typescript": "^5.4.5",
"typescript": "^5.5.2",
"vite": "^5.3.1",
"vite-plugin-top-level-await": "^1.4.1",
"vite-tsconfig-paths": "^4.3.2"

View File

@ -1,15 +1,17 @@
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { RouterProvider, createRouter } from "@tanstack/react-router";
import { StrictMode } from "react";
import { type } from "@tauri-apps/plugin-os";
import ReactDOM from "react-dom/client";
import { routeTree } from "./router.gen"; // auto generated file
import "./app.css";
// Set up a Router instance
const queryClient = new QueryClient();
const platform = type();
const router = createRouter({
routeTree,
context: { queryClient },
context: { queryClient, platform },
Wrap: ({ children }) => {
return (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>

View File

@ -60,15 +60,17 @@ export function Column({
const rect = container.current.getBoundingClientRect();
const url = `${column.content}?account=${account}&label=${column.label}&name=${column.name}`;
// create new webview
invoke("create_column", {
const prop = {
label: webviewLabel,
x: rect.x,
y: rect.y,
width: rect.width,
height: rect.height,
url,
}).then(() => {
};
// create new webview
invoke("create_column", { column: prop }).then(() => {
console.log("created: ", webviewLabel);
setIsCreated(true);
});
@ -87,7 +89,7 @@ export function Column({
className={cn(
"flex flex-col w-full h-full rounded-xl",
column.label !== "open"
? "bg-black/5 dark:bg-white/5 backdrop-blur-sm"
? "bg-black/5 dark:bg-white/10 backdrop-blur"
: "",
)}
>

View File

@ -17,7 +17,7 @@ export function NoteReply({ large = false }: { large?: boolean }) {
className={cn(
"inline-flex items-center justify-center text-neutral-800 dark:text-neutral-200",
large
? "rounded-full bg-neutral-100 dark:bg-white/10 h-7 gap-1.5 w-24 text-sm font-medium hover:text-blue-500 hover:bg-neutral-200 dark:hover:bg-white/20"
? "rounded-full h-7 gap-1.5 w-20 text-sm font-medium hover:bg-black/10 dark:hover:bg-white/10"
: "size-7",
)}
>

View File

@ -64,9 +64,9 @@ export function NoteRepost({ large = false }: { large?: boolean }) {
type="button"
onClick={(e) => showContextMenu(e)}
className={cn(
"inline-flex items-center justify-center text-neutral-800 dark:text-neutral-200 rounded-full",
"inline-flex items-center justify-center text-neutral-800 dark:text-neutral-200",
large
? "bg-neutral-100 dark:bg-white/10 h-7 gap-1.5 w-24 text-sm font-medium hover:text-blue-500 hover:bg-neutral-200 dark:hover:bg-white/20"
? "rounded-full h-7 gap-1.5 w-24 text-sm font-medium hover:bg-black/10 dark:hover:bg-white/10"
: "size-7",
)}
>

View File

@ -13,11 +13,11 @@ export function NoteZap({ large = false }: { large?: boolean }) {
return (
<button
type="button"
onClick={() => LumeWindow.openZap(event.id, event.pubkey)}
onClick={() => LumeWindow.openZap(event.id)}
className={cn(
"inline-flex items-center justify-center text-neutral-800 dark:text-neutral-200",
large
? "rounded-full bg-neutral-100 dark:bg-white/10 h-7 gap-1.5 w-24 text-sm font-medium hover:text-blue-500 hover:bg-neutral-200 dark:hover:bg-white/20"
? "rounded-full h-7 gap-1.5 w-20 text-sm font-medium hover:bg-black/10 dark:hover:bg-white/10"
: "size-7",
)}
>

View File

@ -15,60 +15,63 @@ export function MentionNote({
if (isLoading) {
return (
<div className="flex items-center justify-center w-full h-20 mt-2 border rounded-xl border-black/10 dark:border-white/10">
<Spinner className="size-5" />
<div className="py-2">
<div className="pl-4 py-3 flex flex-col w-full border-l-2 border-black/5 dark:border-white/5">
<Spinner className="size-5" />
</div>
</div>
);
}
if (isError || !data) {
return (
<div className="w-full p-3 mt-2 border rounded-xl border-black/10 dark:border-white/10">
Event not found with your current relay set
<div className="py-2">
<div className="pl-4 py-3 flex flex-col w-full border-l-2 border-black/5 dark:border-white/5">
<p className="text-sm font-medium text-red-500">
Event not found with your current relay set
</p>
</div>
</div>
);
}
return (
<div className="flex flex-col w-full border rounded-lg cursor-default border-black/10 dark:border-white/10">
<User.Provider pubkey={data.pubkey}>
<User.Root className="flex items-center gap-2 px-3 h-11">
<User.Avatar className="object-cover rounded-full size-6 shrink-0" />
<div className="inline-flex items-center flex-1 gap-2">
<User.Name className="font-semibold text-neutral-900 dark:text-neutral-100" />
<span className="text-neutral-600 dark:text-neutral-400">·</span>
<User.Time
time={data.created_at}
className="text-neutral-600 dark:text-neutral-400"
/>
</div>
</User.Root>
</User.Provider>
<div
className={cn(
"px-3 select-text whitespace-normal text-pretty content-break leading-normal",
data.content.length > 400 ? "max-h-[150px] gradient-mask-b-0" : "",
)}
>
{data.content}
</div>
{openable ? (
<div className="flex items-center justify-end px-2 h-11">
<button
type="button"
onClick={(e) => {
e.stopPropagation();
LumeWindow.openEvent(data);
}}
className="z-10 inline-flex items-center justify-center gap-1 text-sm rounded-full h-7 w-28 bg-black/10 dark:bg-white/10 text-neutral-600 hover:text-blue-500 dark:text-neutral-400"
>
View post
<LinkIcon className="size-4" />
</button>
<div className="py-2">
<div className="pl-4 py-3 flex flex-col w-full border-l-2 border-black/5 dark:border-white/5">
<User.Provider pubkey={data.pubkey}>
<User.Root className="flex items-center gap-2 h-8">
<User.Avatar className="object-cover rounded-full size-6 shrink-0" />
<div className="inline-flex items-center flex-1 gap-2">
<User.Name className="font-semibold text-neutral-900 dark:text-neutral-100" />
<span className="text-neutral-600 dark:text-neutral-400">·</span>
<User.Time
time={data.created_at}
className="text-neutral-600 dark:text-neutral-400"
/>
</div>
</User.Root>
</User.Provider>
<div className="select-text text-pretty line-clamp-3 content-break leading-normal">
{data.content}
</div>
) : (
<div className="h-3" />
)}
{openable ? (
<div className="flex items-center justify-start mt-3">
<button
type="button"
onClick={(e) => {
e.stopPropagation();
LumeWindow.openEvent(data);
}}
className="inline-flex items-center gap-1 text-blue-500 text-sm"
>
View post
<LinkIcon className="size-3" />
</button>
</div>
) : (
<div className="h-3" />
)}
</div>
</div>
);
}

View File

@ -35,7 +35,7 @@ export function ImagePreview({ url }: { url: string }) {
loading="lazy"
decoding="async"
style={{ contentVisibility: "auto" }}
className="max-h-[600px] w-auto object-cover rounded-lg outline outline-1 -outline-offset-1 outline-black/15"
className="max-h-[400px] max-w-[400px] h-auto w-auto object-cover rounded-lg outline outline-1 -outline-offset-1 outline-black/15"
onClick={() => open(url)}
onKeyDown={() => open(url)}
onError={({ currentTarget }) => {

View File

@ -97,7 +97,7 @@ export function Images({ urls }: { urls: string[] }) {
return (
<div className="relative pl-2 overflow-hidden group">
<div ref={emblaRef} className="w-full">
<div ref={emblaRef} className="w-full h-[320px]">
<div className="flex w-full gap-2 scrollbar-none">
{imageUrls.map((url, index) => (
<LazyImage
@ -109,10 +109,7 @@ export function Images({ urls }: { urls: string[] }) {
))}
</div>
</div>
<div
aria-hidden
className="absolute z-10 items-center justify-between hidden w-full px-5 transform -translate-x-1/2 -translate-y-1/2 group-hover:flex left-1/2 top-1/2"
>
<div className="absolute z-10 items-center justify-between hidden w-full px-5 transform -translate-x-1/2 -translate-y-1/2 group-hover:flex left-1/2 top-1/2">
<button
type="button"
disabled={!emblaApi?.canScrollPrev}

View File

@ -6,6 +6,7 @@ export function Videos({ urls }: { urls: string[] }) {
<div className="group px-3">
<video
className="w-full h-auto object-cover rounded-lg outline outline-1 -outline-offset-1 outline-black/15"
preload="metadata"
controls
muted
>
@ -23,6 +24,7 @@ export function Videos({ urls }: { urls: string[] }) {
<CarouselItem key={item} isSnapPoint={isSnapPoint}>
<video
className="w-full h-full object-cover rounded-lg outline outline-1 -outline-offset-1 outline-black/15"
preload="metadata"
controls={false}
muted
>

View File

@ -13,7 +13,7 @@ export function TextNote({
<Note.Provider event={event}>
<Note.Root
className={cn(
"bg-white dark:bg-black/20 backdrop-blur-lg rounded-xl shadow-primary dark:ring-1 ring-neutral-800/50",
"bg-white dark:bg-black/20 backdrop-blur rounded-xl shadow-primary dark:ring-1 dark:ring-white/5",
className,
)}
>

View File

@ -8,30 +8,22 @@ import { Link } from "@tanstack/react-router";
import { Menu, MenuItem } from "@tauri-apps/api/menu";
import { getCurrent } from "@tauri-apps/api/window";
import { message } from "@tauri-apps/plugin-dialog";
import { type } from "@tauri-apps/plugin-os";
import { useCallback, useEffect, useMemo, useState } from "react";
export const Route = createFileRoute("/$account")({
beforeLoad: async () => {
const accounts = await NostrAccount.getAccounts();
const os = await type();
return { accounts, os };
return { accounts };
},
component: Screen,
});
function Screen() {
const { os } = Route.useRouteContext();
return (
<div className="flex flex-col w-screen h-screen">
<div
data-tauri-drag-region
className={cn(
"flex h-11 shrink-0 items-center justify-between pr-2",
os === "macos" ? "ml-2 pl-20" : "pl-4",
)}
className="flex h-11 shrink-0 items-center justify-between pr-2 ml-2 pl-20"
>
<div className="flex items-center gap-3">
<Accounts />

View File

@ -1,9 +1,11 @@
import { Spinner } from "@lume/ui";
import type { QueryClient } from "@tanstack/react-query";
import { Outlet, createRootRouteWithContext } from "@tanstack/react-router";
import type { OsType } from "@tauri-apps/plugin-os";
interface RouterContext {
queryClient: QueryClient;
platform: OsType;
}
export const Route = createRootRouteWithContext<RouterContext>()({

View File

@ -1,82 +0,0 @@
import { Note } from "@/components/note";
import { type LumeEvent, NostrQuery } from "@lume/system";
import { Box, Container, Spinner } from "@lume/ui";
import { createFileRoute } from "@tanstack/react-router";
import { useEffect, useState } from "react";
import { WindowVirtualizer } from "virtua";
import { Reply } from "./-components/reply";
export const Route = createFileRoute("/events/$eventId")({
beforeLoad: async () => {
const settings = await NostrQuery.getUserSettings();
return { settings };
},
loader: async ({ params }) => {
const event = await NostrQuery.getEvent(params.eventId);
return event;
},
component: Screen,
});
function Screen() {
const event = Route.useLoaderData();
const [reload, setReload] = useState(false);
const [replies, setReplies] = useState<LumeEvent[]>(null);
useEffect(() => {
let mounted = true;
if (event) {
event.getAllReplies().then((data) => {
if (mounted) setReplies(data);
});
}
return () => {
mounted = false;
};
}, [event]);
return (
<Container withDrag>
<Box className="scrollbar-none">
<WindowVirtualizer>
<Note.Provider event={event}>
<Note.Root>
<div className="flex items-center justify-between px-3 h-14">
<Note.User />
<Note.Menu />
</div>
<Note.ContentLarge className="px-3" />
<div className="flex items-center justify-end gap-2 px-3 mt-4 h-11">
<Note.Reply large />
<Note.Repost large />
<Note.Zap large />
</div>
</Note.Root>
</Note.Provider>
<div className="flex flex-col">
<div className="flex items-center px-3 text-sm font-semibold border-t h-11 text-neutral-700 dark:text-neutral-300 border-neutral-100 dark:border-neutral-900">
Replies ({replies?.length ?? 0})
</div>
{!replies ? (
<Spinner />
) : !replies.length ? (
<div className="flex items-center justify-center w-full">
<div className="flex flex-col items-center justify-center gap-2 py-6">
<h3 className="text-3xl">👋</h3>
<p className="leading-none text-neutral-600 dark:text-neutral-400">
Be the first to Reply!
</p>
</div>
</div>
) : (
replies.map((event) => <Reply key={event.id} event={event} />)
)}
</div>
</WindowVirtualizer>
</Box>
</Container>
);
}

View File

@ -0,0 +1,143 @@
import { Note } from "@/components/note";
import { LumeEvent, NostrQuery } from "@lume/system";
import { createFileRoute } from "@tanstack/react-router";
import { Virtualizer } from "virtua";
import NoteParent from "./-components/parent";
import * as ScrollArea from "@radix-ui/react-scroll-area";
import { useEffect, useRef, useState } from "react";
import { getCurrent } from "@tauri-apps/api/window";
import type { Meta } from "@lume/types";
type Payload = {
raw: string;
parsed: Meta;
};
export const Route = createFileRoute("/events/$id")({
beforeLoad: async () => {
const settings = await NostrQuery.getUserSettings();
return { settings };
},
loader: async ({ params }) => {
const event = await NostrQuery.getEvent(params.id);
return event;
},
component: Screen,
});
function Screen() {
const ref = useRef<HTMLDivElement>(null);
return (
<div className="h-full flex flex-col">
<div
data-tauri-drag-region
className="shrink-0 h-8 w-full border-b border-black/5 dark:border-white/5"
/>
<ScrollArea.Root
type={"scroll"}
scrollHideDelay={300}
className="overflow-hidden size-full flex-1"
>
<ScrollArea.Viewport ref={ref} className="h-full p-3">
<RootEvent />
<Virtualizer scrollRef={ref}>
<ReplyList />
</Virtualizer>
</ScrollArea.Viewport>
<ScrollArea.Scrollbar
className="flex select-none touch-none p-0.5 duration-[160ms] ease-out data-[orientation=vertical]:w-2"
orientation="vertical"
>
<ScrollArea.Thumb className="flex-1 bg-black/10 dark:bg-white/10 rounded-full relative before:content-[''] before:absolute before:top-1/2 before:left-1/2 before:-translate-x-1/2 before:-translate-y-1/2 before:w-full before:h-full before:min-w-[44px] before:min-h-[44px]" />
</ScrollArea.Scrollbar>
<ScrollArea.Corner className="bg-transparent" />
</ScrollArea.Root>
</div>
);
}
function RootEvent() {
const event = Route.useLoaderData();
return (
<Note.Provider event={event}>
<Note.Root className="bg-white dark:bg-black/10 backdrop-blur rounded-xl shadow-primary dark:ring-1 dark:ring-white/5">
<div className="flex items-center justify-between px-3 h-14">
<Note.User />
<Note.Menu />
</div>
<Note.ContentLarge className="px-3" />
<div className="flex items-center gap-2 px-3 mt-6 h-12 rounded-b-xl bg-neutral-50 dark:bg-white/5">
<Note.Reply large />
<Note.Repost large />
<Note.Zap large />
</div>
</Note.Root>
</Note.Provider>
);
}
function ReplyList() {
const event = Route.useLoaderData();
const [replies, setReplies] = useState<LumeEvent[]>([]);
useEffect(() => {
const unlistenEvent = getCurrent().listen<Payload>("new_reply", (data) => {
const event = LumeEvent.from(data.payload.raw, data.payload.parsed);
setReplies((prev) => [event, ...prev]);
});
const unlistenWindow = getCurrent().onCloseRequested(async () => {
await event.unlistenEventReply();
await getCurrent().destroy();
});
return () => {
unlistenEvent.then((f) => f());
unlistenWindow.then((f) => f());
};
}, []);
useEffect(() => {
let mounted = true;
async function getReplies() {
const data = await event.getEventReplies();
if (mounted) {
setReplies(data);
// Start listen for new reply
event.listenEventReply();
}
}
getReplies();
return () => {
mounted = false;
};
}, []);
return (
<div>
<div className="flex items-center text-sm font-semibold h-14 text-neutral-600 dark:text-white/30">
All replies
</div>
<div className="flex flex-col gap-3">
{!replies.length ? (
<div className="flex items-center justify-center w-full">
<div className="flex flex-col items-center justify-center gap-2 py-4">
<h3 className="text-3xl">👋</h3>
<p className="leading-none text-neutral-600 dark:text-neutral-400">
Be the first to Reply!
</p>
</div>
</div>
) : (
replies.map((event) => <NoteParent key={event.id} event={event} />)
)}
</div>
</div>
);
}

View File

@ -0,0 +1,41 @@
import { Note } from "@/components/note";
import type { LumeEvent } from "@lume/system";
import NoteParent from "./parent";
import { memo } from "react";
const NoteChild = memo(function NoteChild({ event }: { event: LumeEvent }) {
return (
<Note.Provider event={event}>
<Note.Root className="flex flex-col gap-6 mb-3">
<div>
<div className="flex items-center justify-between">
<Note.User />
<Note.Menu />
</div>
<div className="flex gap-2">
<div className="w-8 shrink-0" />
<div className="flex-1 flex flex-col gap-2">
<Note.ContentLarge />
<div className="flex items-center gap-1">
<Note.Reply />
<Note.Repost />
<Note.Zap />
</div>
</div>
</div>
</div>
{event.replies?.length ? (
<div className="flex flex-col gap-3 pl-4">
<div className="flex flex-col pl-6 border-l border-black/10 dark:border-white/10">
{event.replies?.map((childEvent) => (
<NoteParent key={childEvent.id} event={childEvent} />
))}
</div>
</div>
) : null}
</Note.Root>
</Note.Provider>
);
});
export default NoteChild;

View File

@ -0,0 +1,41 @@
import { Note } from "@/components/note";
import type { LumeEvent } from "@lume/system";
import NoteChild from "./child";
import { memo } from "react";
const NoteParent = memo(function NoteParent({ event }: { event: LumeEvent }) {
return (
<Note.Provider event={event}>
<Note.Root className="flex flex-col gap-6 mb-3">
<div>
<div className="flex items-center justify-between">
<Note.User />
<Note.Menu />
</div>
<div className="flex gap-2">
<div className="w-8 shrink-0" />
<div className="flex-1 flex flex-col gap-2">
<Note.ContentLarge />
<div className="flex items-center gap-1">
<Note.Reply />
<Note.Repost />
<Note.Zap />
</div>
</div>
</div>
</div>
{event.replies?.length ? (
<div className="flex flex-col gap-3 pl-4">
<div className="flex flex-col gap-3 pl-6 border-l border-black/10 dark:border-white/10">
{event.replies?.map((childEvent) => (
<NoteChild key={childEvent.id} event={childEvent} />
))}
</div>
</div>
) : null}
</Note.Root>
</Note.Provider>
);
});
export default NoteParent;

View File

@ -1,36 +0,0 @@
import { Note } from "@/components/note";
import type { LumeEvent } from "@lume/system";
import { cn } from "@lume/utils";
import { SubReply } from "./subReply";
export function Reply({ event }: { event: LumeEvent }) {
return (
<Note.Provider event={event}>
<Note.Root className="border-t border-neutral-100 dark:border-neutral-900">
<div className="flex items-center justify-between px-3 h-14">
<Note.User />
<Note.Menu />
</div>
<Note.ContentLarge className="px-3" />
<div className="flex items-center gap-4 px-3 mt-3 h-14">
<Note.Reply />
<Note.Repost />
<Note.Zap />
</div>
<div
className={cn(
event.replies?.length > 0
? "py-2 pl-3 flex flex-col gap-3 divide-y divide-neutral-100 bg-neutral-50 dark:bg-white/5 border-l-2 border-blue-500 dark:divide-neutral-900"
: "",
)}
>
{event.replies?.length > 0
? event.replies?.map((childEvent) => (
<SubReply key={childEvent.id} event={childEvent} />
))
: null}
</div>
</Note.Root>
</Note.Provider>
);
}

View File

@ -1,26 +0,0 @@
import type { NostrEvent } from "@lume/types";
import { Note } from "@/components/note";
export function SubReply({
event,
}: {
event: NostrEvent;
rootEventId?: string;
}) {
return (
<Note.Provider event={event}>
<Note.Root>
<div className="px-3 h-14 flex items-center justify-between">
<Note.User />
<Note.Menu />
</div>
<Note.ContentLarge className="px-3" />
<div className="mt-3 flex items-center gap-4 px-3">
<Note.Reply />
<Note.Repost />
<Note.Zap />
</div>
</Note.Root>
</Note.Provider>
);
}

View File

@ -67,8 +67,12 @@ function Screen() {
return (
<div
data-tauri-drag-region
className="flex flex-col items-center justify-between w-full h-full"
className="relative flex flex-col items-center justify-between w-full h-full"
>
<div
data-tauri-drag-region
className="absolute top-0 left-0 h-14 w-full"
/>
<div className="flex items-end justify-center flex-1 w-full px-4 pb-10">
<div className="text-center">
<h2 className="mb-1 text-lg text-neutral-700 dark:text-neutral-300">

View File

@ -13,12 +13,12 @@
"@astrojs/check": "^0.5.10",
"@astrojs/tailwind": "^5.1.0",
"@fontsource/alice": "^5.0.13",
"astro": "^4.10.2",
"astro": "^4.11.1",
"astro-seo-meta": "^4.1.1",
"astro-seo-schema": "^4.0.2",
"schema-dts": "^1.1.2",
"tailwindcss": "^3.4.4",
"typescript": "^5.4.5"
"typescript": "^5.5.2"
},
"devDependencies": {
"@tailwindcss/typography": "^0.5.13"

View File

@ -11,7 +11,7 @@
"tauri": "tauri"
},
"devDependencies": {
"@biomejs/biome": "^1.8.1",
"@biomejs/biome": "^1.8.2",
"@tauri-apps/cli": "2.0.0-beta.20",
"turbo": "^1.13.4"
},
@ -26,7 +26,7 @@
"@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-os": "github:tauri-apps/tauri-plugin-os#v2",
"@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",

View File

@ -9,6 +9,6 @@
"devDependencies": {
"@lume/tsconfig": "workspace:*",
"@types/react": "^18.3.3",
"typescript": "^5.4.5"
"typescript": "^5.5.2"
}
}

View File

@ -5,8 +5,8 @@
"main": "./src/index.ts",
"dependencies": {
"@lume/utils": "workspace:^",
"@tanstack/query-persist-client-core": "^5.45.0",
"@tanstack/react-query": "^5.45.0",
"@tanstack/query-persist-client-core": "^5.48.0",
"@tanstack/react-query": "^5.48.0",
"nostr-tools": "^2.7.0",
"react": "^18.3.1"
},
@ -14,6 +14,6 @@
"@lume/tsconfig": "workspace:^",
"@lume/types": "workspace:^",
"@types/react": "^18.3.3",
"typescript": "^5.4.5"
"typescript": "^5.5.2"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,4 @@
import type {
EventTag,
EventWithReplies,
Kind,
Meta,
NostrEvent,
} from "@lume/types";
import type { EventTag, Kind, Meta, NostrEvent } from "@lume/types";
import { type Result, commands } from "./commands";
export class LumeEvent {
@ -110,52 +104,82 @@ export class LumeEvent {
}
}
public async getAllReplies() {
public async getEventReplies() {
const query = await commands.getReplies(this.id);
if (query.status === "ok") {
const events = query.data.map((item) => {
const nostrEvent: NostrEvent = JSON.parse(item.raw);
const events = query.data
// Create Lume Events
.map((item) => LumeEvent.from(item.raw, item.parsed))
// Filter quote event
.filter(
(ev) =>
!ev.tags.filter((t) => t[0] === "q" || t[3] === "mention").length,
);
if (item.parsed) {
nostrEvent.meta = item.parsed;
} else {
nostrEvent.meta = null;
}
const lumeEvent = new LumeEvent(nostrEvent);
return lumeEvent;
});
if (events.length > 0) {
const replies = new Set();
if (events.length > 1) {
const removeQueues = new Set();
for (const event of events) {
const tags = event.tags.filter(
(el) => el[0] === "e" && el[1] !== this.id && el[3] !== "mention",
(t) => t[0] === "e" && t[1] !== this.id,
);
if (tags.length > 0) {
for (const tag of tags) {
const rootIndex = events.findIndex((el) => el.id === tag[1]);
if (tags.length === 1) {
const index = events.findIndex((ev) => ev.id === tags[0][1]);
if (rootIndex !== -1) {
const rootEvent = events[rootIndex];
if (index !== -1) {
const rootEvent = events[index];
if (rootEvent?.replies) {
rootEvent.replies.push(event);
} else {
rootEvent.replies = [event];
if (rootEvent.replies?.length) {
rootEvent.replies.push(event);
} else {
rootEvent.replies = [event];
}
// Add current event to queue
removeQueues.add(event.id);
continue;
}
}
for (const tag of tags) {
const id = tag[1];
const rootIndex = events.findIndex((ev) => ev.id === id);
if (rootIndex !== -1) {
const rootEvent = events[rootIndex];
if (rootEvent.replies?.length) {
const childIndex = rootEvent.replies.findIndex(
(ev) => ev.id === id,
);
if (childIndex !== -1) {
const childEvent = rootEvent.replies[rootIndex];
if (childEvent.replies?.length) {
childEvent.replies.push(event);
} else {
childEvent.replies = [event];
}
// Add current event to queue
removeQueues.add(event.id);
}
replies.add(event.id);
} else {
rootEvent.replies = [event];
// Add current event to queue
removeQueues.add(event.id);
}
}
break;
}
}
return events.filter((ev) => !replies.has(ev.id));
return events.filter((ev) => !removeQueues.has(ev.id));
}
return events;
@ -165,6 +189,26 @@ export class LumeEvent {
}
}
public async listenEventReply() {
const query = await commands.listenEventReply(this.id);
if (query.status === "ok") {
return query.data;
} else {
throw new Error(query.error);
}
}
public async unlistenEventReply() {
const query = await commands.unlistenEventReply(this.id);
if (query.status === "ok") {
return query.data;
} else {
throw new Error(query.error);
}
}
public async zap(amount: number, message: string) {
const query = await commands.zapEvent(this.id, amount.toString(), message);
@ -226,4 +270,16 @@ export class LumeEvent {
throw new Error(query.error);
}
}
static from(raw: string, parsed?: Meta) {
const nostrEvent: NostrEvent = JSON.parse(raw);
if (parsed) {
nostrEvent.meta = parsed;
} else {
nostrEvent.meta = null;
}
return new this(nostrEvent);
}
}

View File

@ -15,8 +15,8 @@ export class LumeWindow {
const reply: string =
eTags.find((el) => el[3] === "reply")?.[1] ?? eTags[1]?.[1];
const label = `event-${event.id}`;
const url = `/events/${root ?? reply ?? event.id}`;
const label = `event-${root ?? reply ?? event.id}`;
const query = await commands.openWindow({
label,
@ -26,6 +26,7 @@ export class LumeWindow {
height: 800,
maximizable: true,
minimizable: true,
hidden_title: false,
});
if (query.status === "ok") {
@ -45,6 +46,7 @@ export class LumeWindow {
height: 800,
maximizable: true,
minimizable: true,
hidden_title: true,
});
if (query.status === "ok") {
@ -76,8 +78,9 @@ export class LumeWindow {
title: "Editor",
width: 560,
height: 340,
maximizable: true,
maximizable: false,
minimizable: false,
hidden_title: true,
});
if (query.status === "ok") {
@ -99,6 +102,7 @@ export class LumeWindow {
height: 460,
maximizable: false,
minimizable: false,
hidden_title: true,
});
} else {
await LumeWindow.openSettings("bitcoin-connect");
@ -115,6 +119,7 @@ export class LumeWindow {
height: 500,
maximizable: false,
minimizable: false,
hidden_title: true,
});
if (query.status === "ok") {
@ -134,6 +139,7 @@ export class LumeWindow {
height: 600,
maximizable: false,
minimizable: false,
hidden_title: true,
});
if (query.status === "ok") {

View File

@ -9,6 +9,6 @@
"access": "public"
},
"devDependencies": {
"typescript": "^5.4.5"
"typescript": "^5.5.2"
}
}

View File

@ -16,6 +16,6 @@
"@lume/types": "workspace:^",
"@types/react": "^18.3.3",
"tailwindcss": "^3.4.4",
"typescript": "^5.4.5"
"typescript": "^5.5.2"
}
}

View File

@ -24,6 +24,6 @@
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"tailwind-merge": "^2.3.0",
"typescript": "^5.4.5"
"typescript": "^5.5.2"
}
}

File diff suppressed because it is too large Load Diff

553
src-tauri/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -37,6 +37,7 @@
"window:allow-start-dragging",
"window:allow-create",
"window:allow-close",
"window:allow-destroy",
"window:allow-set-focus",
"window:allow-center",
"window:allow-minimize",

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
{"desktop-capability":{"identifier":"desktop-capability","description":"Capability for the desktop","local":true,"windows":["main","panel","splash","settings","search","nwc","activity","zap-*","event-*","user-*","editor-*","column-*"],"permissions":["path:default","event:default","window:default","app:default","resources:default","menu:default","tray:default","notification:allow-is-permission-granted","notification:allow-request-permission","notification:default","os:allow-locale","os:allow-platform","os:allow-os-type","updater:default","updater:allow-check","updater:allow-download-and-install","window:allow-start-dragging","window:allow-create","window:allow-close","window:allow-set-focus","window:allow-center","window:allow-minimize","window:allow-maximize","window:allow-set-size","window:allow-set-focus","window:allow-start-dragging","decorum:allow-show-snap-overlay","clipboard-manager:allow-write-text","clipboard-manager:allow-read-text","webview:allow-create-webview-window","webview:allow-create-webview","webview:allow-set-webview-size","webview:allow-set-webview-position","webview:allow-webview-close","dialog:allow-open","dialog:allow-ask","dialog:allow-message","process:allow-restart","fs:allow-read-file","theme:allow-set-theme","theme:allow-get-theme","menu:allow-new","menu:allow-popup","http:default","shell:allow-open",{"identifier":"http:default","allow":[{"url":"http://**/"},{"url":"https://**/"}]},{"identifier":"fs:allow-read-text-file","allow":[{"path":"$RESOURCE/locales/*"},{"path":"$RESOURCE/resources/*"}]}],"platforms":["linux","macOS","windows"]}}
{"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-destroy","window:allow-set-focus","window:allow-center","window:allow-minimize","window:allow-maximize","window:allow-set-size","window:allow-set-focus","window:allow-start-dragging","decorum:allow-show-snap-overlay","clipboard-manager:allow-write-text","clipboard-manager:allow-read-text","webview:allow-create-webview-window","webview:allow-create-webview","webview:allow-set-webview-size","webview:allow-set-webview-position","webview:allow-webview-close","dialog:allow-open","dialog:allow-ask","dialog:allow-message","process:allow-restart","fs:allow-read-file","theme:allow-set-theme","theme:allow-get-theme","menu:allow-new","menu:allow-popup","http:default","shell:allow-open",{"identifier":"http:default","allow":[{"url":"http://**/"},{"url":"https://**/"}]},{"identifier":"fs:allow-read-text-file","allow":[{"path":"$RESOURCE/locales/*"},{"path":"$RESOURCE/resources/*"}]}],"platforms":["linux","macOS","windows"]}}

View File

@ -1605,7 +1605,7 @@
]
},
{
"description": "fs:scope-app-recursive -> This scope recursive access to the complete `$APP` folder, including sub directories and files.",
"description": "fs:scope-app-recursive -> This scope permits recursive access to the complete `$APP` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-app-recursive"
@ -1626,7 +1626,7 @@
]
},
{
"description": "fs:scope-appcache-recursive -> This scope recursive access to the complete `$APPCACHE` folder, including sub directories and files.",
"description": "fs:scope-appcache-recursive -> This scope permits recursive access to the complete `$APPCACHE` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-appcache-recursive"
@ -1647,7 +1647,7 @@
]
},
{
"description": "fs:scope-appconfig-recursive -> This scope recursive access to the complete `$APPCONFIG` folder, including sub directories and files.",
"description": "fs:scope-appconfig-recursive -> This scope permits recursive access to the complete `$APPCONFIG` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-appconfig-recursive"
@ -1668,7 +1668,7 @@
]
},
{
"description": "fs:scope-appdata-recursive -> This scope recursive access to the complete `$APPDATA` folder, including sub directories and files.",
"description": "fs:scope-appdata-recursive -> This scope permits recursive access to the complete `$APPDATA` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-appdata-recursive"
@ -1689,7 +1689,7 @@
]
},
{
"description": "fs:scope-applocaldata-recursive -> This scope recursive access to the complete `$APPLOCALDATA` folder, including sub directories and files.",
"description": "fs:scope-applocaldata-recursive -> This scope permits recursive access to the complete `$APPLOCALDATA` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-applocaldata-recursive"
@ -1710,7 +1710,7 @@
]
},
{
"description": "fs:scope-applog-recursive -> This scope recursive access to the complete `$APPLOG` folder, including sub directories and files.",
"description": "fs:scope-applog-recursive -> This scope permits recursive access to the complete `$APPLOG` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-applog-recursive"
@ -1731,7 +1731,7 @@
]
},
{
"description": "fs:scope-audio-recursive -> This scope recursive access to the complete `$AUDIO` folder, including sub directories and files.",
"description": "fs:scope-audio-recursive -> This scope permits recursive access to the complete `$AUDIO` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-audio-recursive"
@ -1752,7 +1752,7 @@
]
},
{
"description": "fs:scope-cache-recursive -> This scope recursive access to the complete `$CACHE` folder, including sub directories and files.",
"description": "fs:scope-cache-recursive -> This scope permits recursive access to the complete `$CACHE` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-cache-recursive"
@ -1773,7 +1773,7 @@
]
},
{
"description": "fs:scope-config-recursive -> This scope recursive access to the complete `$CONFIG` folder, including sub directories and files.",
"description": "fs:scope-config-recursive -> This scope permits recursive access to the complete `$CONFIG` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-config-recursive"
@ -1794,7 +1794,7 @@
]
},
{
"description": "fs:scope-data-recursive -> This scope recursive access to the complete `$DATA` folder, including sub directories and files.",
"description": "fs:scope-data-recursive -> This scope permits recursive access to the complete `$DATA` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-data-recursive"
@ -1815,7 +1815,7 @@
]
},
{
"description": "fs:scope-desktop-recursive -> This scope recursive access to the complete `$DESKTOP` folder, including sub directories and files.",
"description": "fs:scope-desktop-recursive -> This scope permits recursive access to the complete `$DESKTOP` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-desktop-recursive"
@ -1836,7 +1836,7 @@
]
},
{
"description": "fs:scope-document-recursive -> This scope recursive access to the complete `$DOCUMENT` folder, including sub directories and files.",
"description": "fs:scope-document-recursive -> This scope permits recursive access to the complete `$DOCUMENT` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-document-recursive"
@ -1857,7 +1857,7 @@
]
},
{
"description": "fs:scope-download-recursive -> This scope recursive access to the complete `$DOWNLOAD` folder, including sub directories and files.",
"description": "fs:scope-download-recursive -> This scope permits recursive access to the complete `$DOWNLOAD` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-download-recursive"
@ -1878,7 +1878,7 @@
]
},
{
"description": "fs:scope-exe-recursive -> This scope recursive access to the complete `$EXE` folder, including sub directories and files.",
"description": "fs:scope-exe-recursive -> This scope permits recursive access to the complete `$EXE` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-exe-recursive"
@ -1899,7 +1899,7 @@
]
},
{
"description": "fs:scope-font-recursive -> This scope recursive access to the complete `$FONT` folder, including sub directories and files.",
"description": "fs:scope-font-recursive -> This scope permits recursive access to the complete `$FONT` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-font-recursive"
@ -1920,7 +1920,7 @@
]
},
{
"description": "fs:scope-home-recursive -> This scope recursive access to the complete `$HOME` folder, including sub directories and files.",
"description": "fs:scope-home-recursive -> This scope permits recursive access to the complete `$HOME` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-home-recursive"
@ -1941,7 +1941,7 @@
]
},
{
"description": "fs:scope-localdata-recursive -> This scope recursive access to the complete `$LOCALDATA` folder, including sub directories and files.",
"description": "fs:scope-localdata-recursive -> This scope permits recursive access to the complete `$LOCALDATA` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-localdata-recursive"
@ -1962,7 +1962,7 @@
]
},
{
"description": "fs:scope-log-recursive -> This scope recursive access to the complete `$LOG` folder, including sub directories and files.",
"description": "fs:scope-log-recursive -> This scope permits recursive access to the complete `$LOG` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-log-recursive"
@ -1983,7 +1983,7 @@
]
},
{
"description": "fs:scope-picture-recursive -> This scope recursive access to the complete `$PICTURE` folder, including sub directories and files.",
"description": "fs:scope-picture-recursive -> This scope permits recursive access to the complete `$PICTURE` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-picture-recursive"
@ -2004,7 +2004,7 @@
]
},
{
"description": "fs:scope-public-recursive -> This scope recursive access to the complete `$PUBLIC` folder, including sub directories and files.",
"description": "fs:scope-public-recursive -> This scope permits recursive access to the complete `$PUBLIC` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-public-recursive"
@ -2025,7 +2025,7 @@
]
},
{
"description": "fs:scope-resource-recursive -> This scope recursive access to the complete `$RESOURCE` folder, including sub directories and files.",
"description": "fs:scope-resource-recursive -> This scope permits recursive access to the complete `$RESOURCE` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-resource-recursive"
@ -2046,7 +2046,7 @@
]
},
{
"description": "fs:scope-runtime-recursive -> This scope recursive access to the complete `$RUNTIME` folder, including sub directories and files.",
"description": "fs:scope-runtime-recursive -> This scope permits recursive access to the complete `$RUNTIME` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-runtime-recursive"
@ -2067,7 +2067,7 @@
]
},
{
"description": "fs:scope-temp-recursive -> This scope recursive access to the complete `$TEMP` folder, including sub directories and files.",
"description": "fs:scope-temp-recursive -> This scope permits recursive access to the complete `$TEMP` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-temp-recursive"
@ -2088,7 +2088,7 @@
]
},
{
"description": "fs:scope-template-recursive -> This scope recursive access to the complete `$TEMPLATE` folder, including sub directories and files.",
"description": "fs:scope-template-recursive -> This scope permits recursive access to the complete `$TEMPLATE` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-template-recursive"
@ -2109,7 +2109,7 @@
]
},
{
"description": "fs:scope-video-recursive -> This scope recursive access to the complete `$VIDEO` folder, including sub directories and files.",
"description": "fs:scope-video-recursive -> This scope permits recursive access to the complete `$VIDEO` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-video-recursive"
@ -4258,7 +4258,7 @@
]
},
{
"description": "fs:scope-app-recursive -> This scope recursive access to the complete `$APP` folder, including sub directories and files.",
"description": "fs:scope-app-recursive -> This scope permits recursive access to the complete `$APP` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-app-recursive"
@ -4279,7 +4279,7 @@
]
},
{
"description": "fs:scope-appcache-recursive -> This scope recursive access to the complete `$APPCACHE` folder, including sub directories and files.",
"description": "fs:scope-appcache-recursive -> This scope permits recursive access to the complete `$APPCACHE` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-appcache-recursive"
@ -4300,7 +4300,7 @@
]
},
{
"description": "fs:scope-appconfig-recursive -> This scope recursive access to the complete `$APPCONFIG` folder, including sub directories and files.",
"description": "fs:scope-appconfig-recursive -> This scope permits recursive access to the complete `$APPCONFIG` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-appconfig-recursive"
@ -4321,7 +4321,7 @@
]
},
{
"description": "fs:scope-appdata-recursive -> This scope recursive access to the complete `$APPDATA` folder, including sub directories and files.",
"description": "fs:scope-appdata-recursive -> This scope permits recursive access to the complete `$APPDATA` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-appdata-recursive"
@ -4342,7 +4342,7 @@
]
},
{
"description": "fs:scope-applocaldata-recursive -> This scope recursive access to the complete `$APPLOCALDATA` folder, including sub directories and files.",
"description": "fs:scope-applocaldata-recursive -> This scope permits recursive access to the complete `$APPLOCALDATA` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-applocaldata-recursive"
@ -4363,7 +4363,7 @@
]
},
{
"description": "fs:scope-applog-recursive -> This scope recursive access to the complete `$APPLOG` folder, including sub directories and files.",
"description": "fs:scope-applog-recursive -> This scope permits recursive access to the complete `$APPLOG` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-applog-recursive"
@ -4384,7 +4384,7 @@
]
},
{
"description": "fs:scope-audio-recursive -> This scope recursive access to the complete `$AUDIO` folder, including sub directories and files.",
"description": "fs:scope-audio-recursive -> This scope permits recursive access to the complete `$AUDIO` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-audio-recursive"
@ -4405,7 +4405,7 @@
]
},
{
"description": "fs:scope-cache-recursive -> This scope recursive access to the complete `$CACHE` folder, including sub directories and files.",
"description": "fs:scope-cache-recursive -> This scope permits recursive access to the complete `$CACHE` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-cache-recursive"
@ -4426,7 +4426,7 @@
]
},
{
"description": "fs:scope-config-recursive -> This scope recursive access to the complete `$CONFIG` folder, including sub directories and files.",
"description": "fs:scope-config-recursive -> This scope permits recursive access to the complete `$CONFIG` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-config-recursive"
@ -4447,7 +4447,7 @@
]
},
{
"description": "fs:scope-data-recursive -> This scope recursive access to the complete `$DATA` folder, including sub directories and files.",
"description": "fs:scope-data-recursive -> This scope permits recursive access to the complete `$DATA` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-data-recursive"
@ -4468,7 +4468,7 @@
]
},
{
"description": "fs:scope-desktop-recursive -> This scope recursive access to the complete `$DESKTOP` folder, including sub directories and files.",
"description": "fs:scope-desktop-recursive -> This scope permits recursive access to the complete `$DESKTOP` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-desktop-recursive"
@ -4489,7 +4489,7 @@
]
},
{
"description": "fs:scope-document-recursive -> This scope recursive access to the complete `$DOCUMENT` folder, including sub directories and files.",
"description": "fs:scope-document-recursive -> This scope permits recursive access to the complete `$DOCUMENT` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-document-recursive"
@ -4510,7 +4510,7 @@
]
},
{
"description": "fs:scope-download-recursive -> This scope recursive access to the complete `$DOWNLOAD` folder, including sub directories and files.",
"description": "fs:scope-download-recursive -> This scope permits recursive access to the complete `$DOWNLOAD` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-download-recursive"
@ -4531,7 +4531,7 @@
]
},
{
"description": "fs:scope-exe-recursive -> This scope recursive access to the complete `$EXE` folder, including sub directories and files.",
"description": "fs:scope-exe-recursive -> This scope permits recursive access to the complete `$EXE` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-exe-recursive"
@ -4552,7 +4552,7 @@
]
},
{
"description": "fs:scope-font-recursive -> This scope recursive access to the complete `$FONT` folder, including sub directories and files.",
"description": "fs:scope-font-recursive -> This scope permits recursive access to the complete `$FONT` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-font-recursive"
@ -4573,7 +4573,7 @@
]
},
{
"description": "fs:scope-home-recursive -> This scope recursive access to the complete `$HOME` folder, including sub directories and files.",
"description": "fs:scope-home-recursive -> This scope permits recursive access to the complete `$HOME` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-home-recursive"
@ -4594,7 +4594,7 @@
]
},
{
"description": "fs:scope-localdata-recursive -> This scope recursive access to the complete `$LOCALDATA` folder, including sub directories and files.",
"description": "fs:scope-localdata-recursive -> This scope permits recursive access to the complete `$LOCALDATA` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-localdata-recursive"
@ -4615,7 +4615,7 @@
]
},
{
"description": "fs:scope-log-recursive -> This scope recursive access to the complete `$LOG` folder, including sub directories and files.",
"description": "fs:scope-log-recursive -> This scope permits recursive access to the complete `$LOG` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-log-recursive"
@ -4636,7 +4636,7 @@
]
},
{
"description": "fs:scope-picture-recursive -> This scope recursive access to the complete `$PICTURE` folder, including sub directories and files.",
"description": "fs:scope-picture-recursive -> This scope permits recursive access to the complete `$PICTURE` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-picture-recursive"
@ -4657,7 +4657,7 @@
]
},
{
"description": "fs:scope-public-recursive -> This scope recursive access to the complete `$PUBLIC` folder, including sub directories and files.",
"description": "fs:scope-public-recursive -> This scope permits recursive access to the complete `$PUBLIC` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-public-recursive"
@ -4678,7 +4678,7 @@
]
},
{
"description": "fs:scope-resource-recursive -> This scope recursive access to the complete `$RESOURCE` folder, including sub directories and files.",
"description": "fs:scope-resource-recursive -> This scope permits recursive access to the complete `$RESOURCE` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-resource-recursive"
@ -4699,7 +4699,7 @@
]
},
{
"description": "fs:scope-runtime-recursive -> This scope recursive access to the complete `$RUNTIME` folder, including sub directories and files.",
"description": "fs:scope-runtime-recursive -> This scope permits recursive access to the complete `$RUNTIME` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-runtime-recursive"
@ -4720,7 +4720,7 @@
]
},
{
"description": "fs:scope-temp-recursive -> This scope recursive access to the complete `$TEMP` folder, including sub directories and files.",
"description": "fs:scope-temp-recursive -> This scope permits recursive access to the complete `$TEMP` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-temp-recursive"
@ -4741,7 +4741,7 @@
]
},
{
"description": "fs:scope-template-recursive -> This scope recursive access to the complete `$TEMPLATE` folder, including sub directories and files.",
"description": "fs:scope-template-recursive -> This scope permits recursive access to the complete `$TEMPLATE` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-template-recursive"
@ -4762,7 +4762,7 @@
]
},
{
"description": "fs:scope-video-recursive -> This scope recursive access to the complete `$VIDEO` folder, including sub directories and files.",
"description": "fs:scope-video-recursive -> This scope permits recursive access to the complete `$VIDEO` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-video-recursive"
@ -5258,6 +5258,20 @@
"notification:allow-notify"
]
},
{
"description": "notification:allow-register-action-types -> Enables the register_action_types command without any pre-configured scope.",
"type": "string",
"enum": [
"notification:allow-register-action-types"
]
},
{
"description": "notification:allow-register-listener -> Enables the register_listener command without any pre-configured scope.",
"type": "string",
"enum": [
"notification:allow-register-listener"
]
},
{
"description": "notification:allow-request-permission -> Enables the request_permission command without any pre-configured scope.",
"type": "string",
@ -5279,6 +5293,20 @@
"notification:deny-notify"
]
},
{
"description": "notification:deny-register-action-types -> Denies the register_action_types command without any pre-configured scope.",
"type": "string",
"enum": [
"notification:deny-register-action-types"
]
},
{
"description": "notification:deny-register-listener -> Denies the register_listener command without any pre-configured scope.",
"type": "string",
"enum": [
"notification:deny-register-listener"
]
},
{
"description": "notification:deny-request-permission -> Denies the request_permission command without any pre-configured scope.",
"type": "string",

View File

@ -1605,7 +1605,7 @@
]
},
{
"description": "fs:scope-app-recursive -> This scope recursive access to the complete `$APP` folder, including sub directories and files.",
"description": "fs:scope-app-recursive -> This scope permits recursive access to the complete `$APP` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-app-recursive"
@ -1626,7 +1626,7 @@
]
},
{
"description": "fs:scope-appcache-recursive -> This scope recursive access to the complete `$APPCACHE` folder, including sub directories and files.",
"description": "fs:scope-appcache-recursive -> This scope permits recursive access to the complete `$APPCACHE` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-appcache-recursive"
@ -1647,7 +1647,7 @@
]
},
{
"description": "fs:scope-appconfig-recursive -> This scope recursive access to the complete `$APPCONFIG` folder, including sub directories and files.",
"description": "fs:scope-appconfig-recursive -> This scope permits recursive access to the complete `$APPCONFIG` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-appconfig-recursive"
@ -1668,7 +1668,7 @@
]
},
{
"description": "fs:scope-appdata-recursive -> This scope recursive access to the complete `$APPDATA` folder, including sub directories and files.",
"description": "fs:scope-appdata-recursive -> This scope permits recursive access to the complete `$APPDATA` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-appdata-recursive"
@ -1689,7 +1689,7 @@
]
},
{
"description": "fs:scope-applocaldata-recursive -> This scope recursive access to the complete `$APPLOCALDATA` folder, including sub directories and files.",
"description": "fs:scope-applocaldata-recursive -> This scope permits recursive access to the complete `$APPLOCALDATA` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-applocaldata-recursive"
@ -1710,7 +1710,7 @@
]
},
{
"description": "fs:scope-applog-recursive -> This scope recursive access to the complete `$APPLOG` folder, including sub directories and files.",
"description": "fs:scope-applog-recursive -> This scope permits recursive access to the complete `$APPLOG` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-applog-recursive"
@ -1731,7 +1731,7 @@
]
},
{
"description": "fs:scope-audio-recursive -> This scope recursive access to the complete `$AUDIO` folder, including sub directories and files.",
"description": "fs:scope-audio-recursive -> This scope permits recursive access to the complete `$AUDIO` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-audio-recursive"
@ -1752,7 +1752,7 @@
]
},
{
"description": "fs:scope-cache-recursive -> This scope recursive access to the complete `$CACHE` folder, including sub directories and files.",
"description": "fs:scope-cache-recursive -> This scope permits recursive access to the complete `$CACHE` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-cache-recursive"
@ -1773,7 +1773,7 @@
]
},
{
"description": "fs:scope-config-recursive -> This scope recursive access to the complete `$CONFIG` folder, including sub directories and files.",
"description": "fs:scope-config-recursive -> This scope permits recursive access to the complete `$CONFIG` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-config-recursive"
@ -1794,7 +1794,7 @@
]
},
{
"description": "fs:scope-data-recursive -> This scope recursive access to the complete `$DATA` folder, including sub directories and files.",
"description": "fs:scope-data-recursive -> This scope permits recursive access to the complete `$DATA` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-data-recursive"
@ -1815,7 +1815,7 @@
]
},
{
"description": "fs:scope-desktop-recursive -> This scope recursive access to the complete `$DESKTOP` folder, including sub directories and files.",
"description": "fs:scope-desktop-recursive -> This scope permits recursive access to the complete `$DESKTOP` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-desktop-recursive"
@ -1836,7 +1836,7 @@
]
},
{
"description": "fs:scope-document-recursive -> This scope recursive access to the complete `$DOCUMENT` folder, including sub directories and files.",
"description": "fs:scope-document-recursive -> This scope permits recursive access to the complete `$DOCUMENT` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-document-recursive"
@ -1857,7 +1857,7 @@
]
},
{
"description": "fs:scope-download-recursive -> This scope recursive access to the complete `$DOWNLOAD` folder, including sub directories and files.",
"description": "fs:scope-download-recursive -> This scope permits recursive access to the complete `$DOWNLOAD` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-download-recursive"
@ -1878,7 +1878,7 @@
]
},
{
"description": "fs:scope-exe-recursive -> This scope recursive access to the complete `$EXE` folder, including sub directories and files.",
"description": "fs:scope-exe-recursive -> This scope permits recursive access to the complete `$EXE` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-exe-recursive"
@ -1899,7 +1899,7 @@
]
},
{
"description": "fs:scope-font-recursive -> This scope recursive access to the complete `$FONT` folder, including sub directories and files.",
"description": "fs:scope-font-recursive -> This scope permits recursive access to the complete `$FONT` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-font-recursive"
@ -1920,7 +1920,7 @@
]
},
{
"description": "fs:scope-home-recursive -> This scope recursive access to the complete `$HOME` folder, including sub directories and files.",
"description": "fs:scope-home-recursive -> This scope permits recursive access to the complete `$HOME` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-home-recursive"
@ -1941,7 +1941,7 @@
]
},
{
"description": "fs:scope-localdata-recursive -> This scope recursive access to the complete `$LOCALDATA` folder, including sub directories and files.",
"description": "fs:scope-localdata-recursive -> This scope permits recursive access to the complete `$LOCALDATA` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-localdata-recursive"
@ -1962,7 +1962,7 @@
]
},
{
"description": "fs:scope-log-recursive -> This scope recursive access to the complete `$LOG` folder, including sub directories and files.",
"description": "fs:scope-log-recursive -> This scope permits recursive access to the complete `$LOG` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-log-recursive"
@ -1983,7 +1983,7 @@
]
},
{
"description": "fs:scope-picture-recursive -> This scope recursive access to the complete `$PICTURE` folder, including sub directories and files.",
"description": "fs:scope-picture-recursive -> This scope permits recursive access to the complete `$PICTURE` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-picture-recursive"
@ -2004,7 +2004,7 @@
]
},
{
"description": "fs:scope-public-recursive -> This scope recursive access to the complete `$PUBLIC` folder, including sub directories and files.",
"description": "fs:scope-public-recursive -> This scope permits recursive access to the complete `$PUBLIC` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-public-recursive"
@ -2025,7 +2025,7 @@
]
},
{
"description": "fs:scope-resource-recursive -> This scope recursive access to the complete `$RESOURCE` folder, including sub directories and files.",
"description": "fs:scope-resource-recursive -> This scope permits recursive access to the complete `$RESOURCE` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-resource-recursive"
@ -2046,7 +2046,7 @@
]
},
{
"description": "fs:scope-runtime-recursive -> This scope recursive access to the complete `$RUNTIME` folder, including sub directories and files.",
"description": "fs:scope-runtime-recursive -> This scope permits recursive access to the complete `$RUNTIME` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-runtime-recursive"
@ -2067,7 +2067,7 @@
]
},
{
"description": "fs:scope-temp-recursive -> This scope recursive access to the complete `$TEMP` folder, including sub directories and files.",
"description": "fs:scope-temp-recursive -> This scope permits recursive access to the complete `$TEMP` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-temp-recursive"
@ -2088,7 +2088,7 @@
]
},
{
"description": "fs:scope-template-recursive -> This scope recursive access to the complete `$TEMPLATE` folder, including sub directories and files.",
"description": "fs:scope-template-recursive -> This scope permits recursive access to the complete `$TEMPLATE` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-template-recursive"
@ -2109,7 +2109,7 @@
]
},
{
"description": "fs:scope-video-recursive -> This scope recursive access to the complete `$VIDEO` folder, including sub directories and files.",
"description": "fs:scope-video-recursive -> This scope permits recursive access to the complete `$VIDEO` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-video-recursive"
@ -4258,7 +4258,7 @@
]
},
{
"description": "fs:scope-app-recursive -> This scope recursive access to the complete `$APP` folder, including sub directories and files.",
"description": "fs:scope-app-recursive -> This scope permits recursive access to the complete `$APP` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-app-recursive"
@ -4279,7 +4279,7 @@
]
},
{
"description": "fs:scope-appcache-recursive -> This scope recursive access to the complete `$APPCACHE` folder, including sub directories and files.",
"description": "fs:scope-appcache-recursive -> This scope permits recursive access to the complete `$APPCACHE` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-appcache-recursive"
@ -4300,7 +4300,7 @@
]
},
{
"description": "fs:scope-appconfig-recursive -> This scope recursive access to the complete `$APPCONFIG` folder, including sub directories and files.",
"description": "fs:scope-appconfig-recursive -> This scope permits recursive access to the complete `$APPCONFIG` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-appconfig-recursive"
@ -4321,7 +4321,7 @@
]
},
{
"description": "fs:scope-appdata-recursive -> This scope recursive access to the complete `$APPDATA` folder, including sub directories and files.",
"description": "fs:scope-appdata-recursive -> This scope permits recursive access to the complete `$APPDATA` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-appdata-recursive"
@ -4342,7 +4342,7 @@
]
},
{
"description": "fs:scope-applocaldata-recursive -> This scope recursive access to the complete `$APPLOCALDATA` folder, including sub directories and files.",
"description": "fs:scope-applocaldata-recursive -> This scope permits recursive access to the complete `$APPLOCALDATA` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-applocaldata-recursive"
@ -4363,7 +4363,7 @@
]
},
{
"description": "fs:scope-applog-recursive -> This scope recursive access to the complete `$APPLOG` folder, including sub directories and files.",
"description": "fs:scope-applog-recursive -> This scope permits recursive access to the complete `$APPLOG` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-applog-recursive"
@ -4384,7 +4384,7 @@
]
},
{
"description": "fs:scope-audio-recursive -> This scope recursive access to the complete `$AUDIO` folder, including sub directories and files.",
"description": "fs:scope-audio-recursive -> This scope permits recursive access to the complete `$AUDIO` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-audio-recursive"
@ -4405,7 +4405,7 @@
]
},
{
"description": "fs:scope-cache-recursive -> This scope recursive access to the complete `$CACHE` folder, including sub directories and files.",
"description": "fs:scope-cache-recursive -> This scope permits recursive access to the complete `$CACHE` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-cache-recursive"
@ -4426,7 +4426,7 @@
]
},
{
"description": "fs:scope-config-recursive -> This scope recursive access to the complete `$CONFIG` folder, including sub directories and files.",
"description": "fs:scope-config-recursive -> This scope permits recursive access to the complete `$CONFIG` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-config-recursive"
@ -4447,7 +4447,7 @@
]
},
{
"description": "fs:scope-data-recursive -> This scope recursive access to the complete `$DATA` folder, including sub directories and files.",
"description": "fs:scope-data-recursive -> This scope permits recursive access to the complete `$DATA` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-data-recursive"
@ -4468,7 +4468,7 @@
]
},
{
"description": "fs:scope-desktop-recursive -> This scope recursive access to the complete `$DESKTOP` folder, including sub directories and files.",
"description": "fs:scope-desktop-recursive -> This scope permits recursive access to the complete `$DESKTOP` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-desktop-recursive"
@ -4489,7 +4489,7 @@
]
},
{
"description": "fs:scope-document-recursive -> This scope recursive access to the complete `$DOCUMENT` folder, including sub directories and files.",
"description": "fs:scope-document-recursive -> This scope permits recursive access to the complete `$DOCUMENT` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-document-recursive"
@ -4510,7 +4510,7 @@
]
},
{
"description": "fs:scope-download-recursive -> This scope recursive access to the complete `$DOWNLOAD` folder, including sub directories and files.",
"description": "fs:scope-download-recursive -> This scope permits recursive access to the complete `$DOWNLOAD` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-download-recursive"
@ -4531,7 +4531,7 @@
]
},
{
"description": "fs:scope-exe-recursive -> This scope recursive access to the complete `$EXE` folder, including sub directories and files.",
"description": "fs:scope-exe-recursive -> This scope permits recursive access to the complete `$EXE` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-exe-recursive"
@ -4552,7 +4552,7 @@
]
},
{
"description": "fs:scope-font-recursive -> This scope recursive access to the complete `$FONT` folder, including sub directories and files.",
"description": "fs:scope-font-recursive -> This scope permits recursive access to the complete `$FONT` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-font-recursive"
@ -4573,7 +4573,7 @@
]
},
{
"description": "fs:scope-home-recursive -> This scope recursive access to the complete `$HOME` folder, including sub directories and files.",
"description": "fs:scope-home-recursive -> This scope permits recursive access to the complete `$HOME` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-home-recursive"
@ -4594,7 +4594,7 @@
]
},
{
"description": "fs:scope-localdata-recursive -> This scope recursive access to the complete `$LOCALDATA` folder, including sub directories and files.",
"description": "fs:scope-localdata-recursive -> This scope permits recursive access to the complete `$LOCALDATA` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-localdata-recursive"
@ -4615,7 +4615,7 @@
]
},
{
"description": "fs:scope-log-recursive -> This scope recursive access to the complete `$LOG` folder, including sub directories and files.",
"description": "fs:scope-log-recursive -> This scope permits recursive access to the complete `$LOG` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-log-recursive"
@ -4636,7 +4636,7 @@
]
},
{
"description": "fs:scope-picture-recursive -> This scope recursive access to the complete `$PICTURE` folder, including sub directories and files.",
"description": "fs:scope-picture-recursive -> This scope permits recursive access to the complete `$PICTURE` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-picture-recursive"
@ -4657,7 +4657,7 @@
]
},
{
"description": "fs:scope-public-recursive -> This scope recursive access to the complete `$PUBLIC` folder, including sub directories and files.",
"description": "fs:scope-public-recursive -> This scope permits recursive access to the complete `$PUBLIC` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-public-recursive"
@ -4678,7 +4678,7 @@
]
},
{
"description": "fs:scope-resource-recursive -> This scope recursive access to the complete `$RESOURCE` folder, including sub directories and files.",
"description": "fs:scope-resource-recursive -> This scope permits recursive access to the complete `$RESOURCE` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-resource-recursive"
@ -4699,7 +4699,7 @@
]
},
{
"description": "fs:scope-runtime-recursive -> This scope recursive access to the complete `$RUNTIME` folder, including sub directories and files.",
"description": "fs:scope-runtime-recursive -> This scope permits recursive access to the complete `$RUNTIME` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-runtime-recursive"
@ -4720,7 +4720,7 @@
]
},
{
"description": "fs:scope-temp-recursive -> This scope recursive access to the complete `$TEMP` folder, including sub directories and files.",
"description": "fs:scope-temp-recursive -> This scope permits recursive access to the complete `$TEMP` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-temp-recursive"
@ -4741,7 +4741,7 @@
]
},
{
"description": "fs:scope-template-recursive -> This scope recursive access to the complete `$TEMPLATE` folder, including sub directories and files.",
"description": "fs:scope-template-recursive -> This scope permits recursive access to the complete `$TEMPLATE` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-template-recursive"
@ -4762,7 +4762,7 @@
]
},
{
"description": "fs:scope-video-recursive -> This scope recursive access to the complete `$VIDEO` folder, including sub directories and files.",
"description": "fs:scope-video-recursive -> This scope permits recursive access to the complete `$VIDEO` folder, including sub directories and files.",
"type": "string",
"enum": [
"fs:scope-video-recursive"
@ -5258,6 +5258,20 @@
"notification:allow-notify"
]
},
{
"description": "notification:allow-register-action-types -> Enables the register_action_types command without any pre-configured scope.",
"type": "string",
"enum": [
"notification:allow-register-action-types"
]
},
{
"description": "notification:allow-register-listener -> Enables the register_listener command without any pre-configured scope.",
"type": "string",
"enum": [
"notification:allow-register-listener"
]
},
{
"description": "notification:allow-request-permission -> Enables the request_permission command without any pre-configured scope.",
"type": "string",
@ -5279,6 +5293,20 @@
"notification:deny-notify"
]
},
{
"description": "notification:deny-register-action-types -> Denies the register_action_types command without any pre-configured scope.",
"type": "string",
"enum": [
"notification:deny-register-action-types"
]
},
{
"description": "notification:deny-register-listener -> Denies the register_listener command without any pre-configured scope.",
"type": "string",
"enum": [
"notification:deny-register-listener"
]
},
{
"description": "notification:deny-request-permission -> Denies the request_permission command without any pre-configured scope.",
"type": "string",

View File

@ -5,12 +5,12 @@ use std::str::FromStr;
use cocoa::{appkit::NSApp, base::nil, foundation::NSString};
use serde::{Deserialize, Serialize};
use specta::Type;
use tauri::{LogicalPosition, LogicalSize, Manager, State, WebviewUrl};
use tauri::utils::config::WindowEffectsConfig;
use tauri::window::Effect;
#[cfg(target_os = "macos")]
use tauri::TitleBarStyle;
use tauri::utils::config::WindowEffectsConfig;
use tauri::WebviewWindowBuilder;
use tauri::window::Effect;
use tauri::{LogicalPosition, LogicalSize, Manager, State, WebviewUrl};
use tauri_plugin_decorum::WebviewWindowExt;
use url::Url;
@ -25,39 +25,45 @@ pub struct Window {
height: f64,
maximizable: bool,
minimizable: bool,
hidden_title: bool,
}
#[derive(Serialize, Deserialize, Type)]
pub struct Column {
label: String,
url: String,
x: f32,
y: f32,
width: f32,
height: f32,
}
#[tauri::command]
#[specta::specta]
pub fn create_column(
label: &str,
x: f32,
y: f32,
width: f32,
height: f32,
url: &str,
column: Column,
app_handle: tauri::AppHandle,
state: State<'_, Nostr>,
) -> Result<String, String> {
let settings = state.settings.lock().unwrap().clone();
match app_handle.get_window("main") {
Some(main_window) => match app_handle.get_webview(label) {
Some(_) => Ok(label.into()),
Some(main_window) => match app_handle.get_webview(&column.label) {
Some(_) => Ok(column.label),
None => {
let path = PathBuf::from(url);
let path = PathBuf::from(column.url);
let webview_url = WebviewUrl::App(path);
let builder = match settings.proxy {
Some(url) => {
let proxy = Url::from_str(&url).unwrap();
tauri::webview::WebviewBuilder::new(label, webview_url)
tauri::webview::WebviewBuilder::new(column.label, webview_url)
.user_agent("Lume/4.0")
.zoom_hotkeys_enabled(true)
.enable_clipboard_access()
.transparent(true)
.proxy_url(proxy)
}
None => tauri::webview::WebviewBuilder::new(label, webview_url)
None => tauri::webview::WebviewBuilder::new(column.label, webview_url)
.user_agent("Lume/4.0")
.zoom_hotkeys_enabled(true)
.enable_clipboard_access()
@ -65,8 +71,8 @@ pub fn create_column(
};
match main_window.add_child(
builder,
LogicalPosition::new(x, y),
LogicalSize::new(width, height),
LogicalPosition::new(column.x, column.y),
LogicalSize::new(column.width, column.height),
) {
Ok(webview) => Ok(webview.label().into()),
Err(_) => Err("Create webview failed".into()),
@ -79,7 +85,7 @@ pub fn create_column(
#[tauri::command]
#[specta::specta]
pub fn close_column(label: &str, app_handle: tauri::AppHandle) -> Result<bool, ()> {
pub fn close_column(label: &str, app_handle: tauri::AppHandle) -> Result<bool, String> {
match app_handle.get_webview(label) {
Some(webview) => {
if webview.close().is_ok() {
@ -88,7 +94,7 @@ pub fn close_column(label: &str, app_handle: tauri::AppHandle) -> Result<bool, (
Ok(false)
}
}
None => Ok(true),
None => Err("Column not found.".into()),
}
}
@ -152,14 +158,13 @@ pub fn open_window(window: Window, app_handle: tauri::AppHandle) -> Result<(), S
.title(&window.title)
.min_inner_size(window.width, window.height)
.inner_size(window.width, window.height)
.hidden_title(true)
.hidden_title(window.hidden_title)
.title_bar_style(TitleBarStyle::Overlay)
.transparent(true)
.minimizable(window.minimizable)
.maximizable(window.maximizable)
.effects(WindowEffectsConfig {
state: None,
effects: vec![Effect::WindowBackground],
effects: vec![Effect::UnderWindowBackground],
radius: None,
color: None,
})
@ -171,7 +176,6 @@ pub fn open_window(window: Window, app_handle: tauri::AppHandle) -> Result<(), S
.title(title)
.min_inner_size(width, height)
.inner_size(width, height)
.transparent(true)
.effects(WindowEffectsConfig {
state: None,
effects: vec![Effect::Mica],
@ -189,9 +193,12 @@ pub fn open_window(window: Window, app_handle: tauri::AppHandle) -> Result<(), S
.build()
.unwrap();
#[cfg(target_os = "windows")]
// Create a custom titlebar for Windows
// Set decoration
window.create_overlay_titlebar().unwrap();
// Make main window transparent
#[cfg(target_os = "macos")]
window.make_transparent().unwrap();
}
Ok(())
@ -223,9 +230,13 @@ pub fn open_main_window(app: tauri::AppHandle) {
let _ = window.set_focus();
};
} else {
let _ = WebviewWindowBuilder::from_config(&app, app.config().app.windows.first().unwrap())
let window = WebviewWindowBuilder::from_config(&app, app.config().app.windows.first().unwrap())
.unwrap()
.build()
.unwrap();
// Make main window transparent
#[cfg(target_os = "macos")]
window.make_transparent().unwrap();
}
}

View File

@ -12,6 +12,7 @@ use tauri_nspanel::{
objc::{class, msg_send, runtime::NO, sel, sel_impl},
panel_delegate, ManagerExt, WebviewWindowExt,
};
use tauri_plugin_decorum::WebviewWindowExt as WebviewWindowExt2;
#[allow(non_upper_case_globals)]
const NSWindowStyleMaskNonActivatingPanel: i32 = 1 << 7;
@ -22,9 +23,9 @@ pub fn swizzle_to_menubar_panel(app_handle: &tauri::AppHandle) {
});
let window = app_handle.get_webview_window("panel").unwrap();
window.make_transparent().unwrap();
let panel = window.to_panel().unwrap();
let handle = app_handle.clone();
panel_delegate.set_listener(Box::new(move |delegate_name: String| {

View File

@ -9,20 +9,20 @@ extern crate cocoa;
#[macro_use]
extern crate objc;
use std::sync::Mutex;
use std::time::Duration;
use std::{
fs,
io::{self, BufRead},
str::FromStr,
};
use std::sync::Mutex;
use std::time::Duration;
use nostr_sdk::prelude::*;
use serde::{Deserialize, Serialize};
use specta::Type;
use tauri::{Manager, path::BaseDirectory};
#[cfg(target_os = "macos")]
use tauri::tray::{MouseButtonState, TrayIconEvent};
use tauri::{path::BaseDirectory, Manager};
use tauri_nspanel::ManagerExt;
use tauri_plugin_decorum::WebviewWindowExt;
@ -112,6 +112,8 @@ fn main() {
nostr::event::get_event,
nostr::event::get_event_from,
nostr::event::get_replies,
nostr::event::listen_event_reply,
nostr::event::unlisten_event_reply,
nostr::event::get_events_by,
nostr::event::get_local_events,
nostr::event::get_group_events,
@ -142,10 +144,14 @@ fn main() {
.setup(|app| {
let main_window = app.get_webview_window("main").unwrap();
// Create a custom titlebar for Windows
// Set custom decoration for Windows
#[cfg(target_os = "windows")]
main_window.create_overlay_titlebar().unwrap();
// Make main window transparent
#[cfg(target_os = "macos")]
main_window.make_transparent().unwrap();
// Set a custom inset to the traffic lights
#[cfg(target_os = "macos")]
main_window.set_traffic_lights_inset(8.0, 16.0).unwrap();
@ -192,7 +198,8 @@ fn main() {
// Config
let opts = Options::new()
.automatic_authentication(true)
.connection_timeout(Some(Duration::from_secs(5)));
.connection_timeout(Some(Duration::from_secs(5)))
.timeout(Duration::from_secs(30));
// Setup nostr client
let client = match database {
@ -201,29 +208,30 @@ fn main() {
};
// Get bootstrap relays
let relays_path = app
if let Ok(path) = app
.path()
.resolve("resources/relays.txt", BaseDirectory::Resource)
.expect("Bootstrap relays not found.");
let file = std::fs::File::open(&relays_path).unwrap();
let lines = io::BufReader::new(file).lines();
{
let file = std::fs::File::open(&path).unwrap();
let lines = io::BufReader::new(file).lines();
// Add bootstrap relays to relay pool
for line in lines.map_while(Result::ok) {
if let Some((relay, option)) = line.split_once(',') {
match RelayMetadata::from_str(option) {
Ok(meta) => {
println!("connecting to bootstrap relay...: {} - {}", relay, meta);
let opts = if meta == RelayMetadata::Read {
RelayOptions::new().read(true).write(false)
} else {
RelayOptions::new().write(true).read(false)
};
let _ = client.add_relay_with_opts(relay, opts).await;
}
Err(_) => {
println!("connecting to bootstrap relay...: {}", relay);
let _ = client.add_relay(relay).await;
// Add bootstrap relays to relay pool
for line in lines.map_while(Result::ok) {
if let Some((relay, option)) = line.split_once(',') {
match RelayMetadata::from_str(option) {
Ok(meta) => {
println!("connecting to bootstrap relay...: {} - {}", relay, meta);
let opts = if meta == RelayMetadata::Read {
RelayOptions::new().read(true).write(false)
} else {
RelayOptions::new().write(true).read(false)
};
let _ = client.add_relay_with_opts(relay, opts).await;
}
Err(_) => {
println!("connecting to bootstrap relay...: {}", relay);
let _ = client.add_relay(relay).await;
}
}
}
}

View File

@ -9,7 +9,7 @@ use tauri::State;
use crate::nostr::utils::{create_event_tags, dedup_event, parse_event, Meta};
use crate::Nostr;
#[derive(Debug, Serialize, Type)]
#[derive(Debug, Clone, Serialize, Type)]
pub struct RichEvent {
pub raw: String,
pub parsed: Option<Meta>,
@ -146,33 +146,69 @@ pub async fn get_event_from(
pub async fn get_replies(id: &str, state: State<'_, Nostr>) -> Result<Vec<RichEvent>, String> {
let client = &state.client;
match EventId::from_hex(id) {
Ok(event_id) => {
let filter = Filter::new().kinds(vec![Kind::TextNote]).event(event_id);
let event_id = match EventId::from_hex(id) {
Ok(id) => id,
Err(err) => return Err(err.to_string()),
};
match client.get_events_of(vec![filter], None).await {
Ok(events) => {
let futures = events.into_iter().map(|ev| async move {
let raw = ev.as_json();
let parsed = if ev.kind == Kind::TextNote {
Some(parse_event(&ev.content).await)
} else {
None
};
let filter = Filter::new().kinds(vec![Kind::TextNote]).event(event_id);
RichEvent { raw, parsed }
});
let rich_events = join_all(futures).await;
match client.get_events_of(vec![filter], None).await {
Ok(events) => {
let futures = events.into_iter().map(|ev| async move {
let raw = ev.as_json();
let parsed = if ev.kind == Kind::TextNote {
Some(parse_event(&ev.content).await)
} else {
None
};
Ok(rich_events)
}
Err(err) => Err(err.to_string()),
}
RichEvent { raw, parsed }
});
let rich_events = join_all(futures).await;
Ok(rich_events)
}
Err(_) => Err("Event ID is not valid".into()),
Err(err) => Err(err.to_string()),
}
}
#[tauri::command]
#[specta::specta]
pub async fn listen_event_reply(id: &str, state: State<'_, Nostr>) -> Result<(), String> {
let client = &state.client;
let mut label = "event-".to_owned();
label.push_str(id);
let sub_id = SubscriptionId::new(label);
let event_id = match EventId::from_hex(id) {
Ok(id) => id,
Err(err) => return Err(err.to_string()),
};
let filter = Filter::new()
.kinds(vec![Kind::TextNote])
.event(event_id)
.since(Timestamp::now());
// Subscribe
client.subscribe_with_id(sub_id, vec![filter], None).await;
Ok(())
}
#[tauri::command]
#[specta::specta]
pub async fn unlisten_event_reply(id: &str, state: State<'_, Nostr>) -> Result<(), ()> {
let client = &state.client;
let sub_id = SubscriptionId::new(id);
// Remove subscription
client.unsubscribe(sub_id).await;
Ok(())
}
#[tauri::command]
#[specta::specta]
pub async fn get_events_by(

View File

@ -8,6 +8,8 @@ use specta::Type;
use tauri::{EventTarget, Manager, State};
use tauri_plugin_notification::NotificationExt;
use crate::nostr::event::RichEvent;
use crate::nostr::utils::parse_event;
use crate::{Nostr, Settings};
#[derive(Serialize, Type)]
@ -44,7 +46,7 @@ pub fn get_accounts() -> Result<Vec<String>, String> {
#[tauri::command]
#[specta::specta]
pub fn create_account() -> Result<Account, ()> {
pub fn create_account() -> Result<Account, String> {
let keys = Keys::generate();
let public_key = keys.public_key();
let secret_key = keys.secret_key().unwrap();
@ -57,6 +59,19 @@ pub fn create_account() -> Result<Account, ()> {
Ok(result)
}
#[tauri::command]
#[specta::specta]
pub fn get_private_key(npub: &str) -> Result<String, String> {
let keyring = Entry::new(npub, "nostr_secret").unwrap();
if let Ok(nsec) = keyring.get_password() {
let secret_key = SecretKey::from_bech32(nsec).unwrap();
Ok(secret_key.to_bech32().unwrap())
} else {
Err("Key not found".into())
}
}
#[tauri::command]
#[specta::specta]
pub async fn save_account(
@ -94,6 +109,56 @@ pub async fn save_account(
}
}
#[tauri::command]
#[specta::specta]
pub async fn connect_remote_account(uri: &str, state: State<'_, Nostr>) -> Result<String, String> {
let client = &state.client;
match NostrConnectURI::parse(uri) {
Ok(bunker_uri) => {
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 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(remote_npub)
}
Err(err) => Err(err.to_string()),
}
}
Err(err) => Err(err.to_string()),
}
}
#[tauri::command]
#[specta::specta]
pub async fn get_encrypted_key(npub: &str, password: &str) -> Result<String, String> {
let keyring = Entry::new(npub, "nostr_secret").unwrap();
if let Ok(nsec) = keyring.get_password() {
let secret_key = SecretKey::from_bech32(nsec).unwrap();
let new_key = EncryptedSecretKey::new(&secret_key, password, 16, KeySecurity::Medium);
if let Ok(key) = new_key {
Ok(key.to_bech32().unwrap())
} else {
Err("Encrypt key failed".into())
}
} else {
Err("Key not found".into())
}
}
#[tauri::command]
#[specta::specta]
pub async fn load_account(
@ -105,175 +170,171 @@ pub async fn load_account(
let client = &state.client;
let keyring = Entry::new(npub, "nostr_secret").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.");
let password = match keyring.get_password() {
Ok(pw) => pw,
Err(_) => return Err("Cancelled".into()),
};
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()),
}
match bunker {
Some(uri) => {
let app_keys = Keys::parse(password).expect("Secret Key is modified, please check again.");
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(err) => return Err(err.to_string()),
}
}
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;
Err(err) => return Err(err.to_string()),
}
}
None => {
let keys = Keys::parse(password).expect("Secret Key is modified, please check again.");
let signer = NostrSigner::Keys(keys);
// Verify signer
let signer = client.signer().await.unwrap();
let public_key = signer.public_key().await.unwrap();
// Update signer
client.set_signer(Some(signer)).await;
}
}
// Verify signer
let signer = client.signer().await.unwrap();
let public_key = signer.public_key().await.unwrap();
// Connect to user's relay (NIP-65)
if let Ok(events) = client
.get_events_of(
vec![Filter::new()
.author(public_key)
.kind(Kind::RelayList)
.limit(1)],
None,
)
.await
{
if let Some(event) = events.first() {
let relay_list = nip65::extract_relay_list(event);
for item in relay_list.into_iter() {
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::default(),
};
// 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();
println!("connecting to relay: {} - {:?}", item.0, item.1);
}
}
};
// Get user's contact list
let contacts = client.get_contact_list(None).await.unwrap();
*state.contact_list.lock().unwrap() = contacts;
// Create a subscription for notification
let sub_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
print!("Subscribing for new notification...");
client.subscribe_with_id(sub_id, vec![filter], None).await;
// Get user's settings
let handle = app.clone();
// Spawn a thread to handle it
tauri::async_runtime::spawn(async move {
let window = handle.get_window("main").unwrap();
let state = window.state::<Nostr>();
let client = &state.client;
let ident = "lume:settings";
let filter = Filter::new()
.author(public_key)
.kind(Kind::ApplicationSpecificData)
.identifier(ident)
.limit(1);
// Connect to user's relay (NIP-65)
if let Ok(events) = client
.get_events_of(
vec![Filter::new()
.author(public_key)
.kind(Kind::RelayList)
.limit(1)],
Some(Duration::from_secs(10)),
)
.get_events_of(vec![filter], Some(Duration::from_secs(5)))
.await
{
if let Some(event) = events.first() {
let relay_list = nip65::extract_relay_list(event);
for item in relay_list.into_iter() {
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::default(),
};
let content = event.content();
if let Ok(decrypted) = signer.nip44_decrypt(public_key, content).await {
let parsed: Settings =
serde_json::from_str(&decrypted).expect("Could not parse settings payload");
// 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();
println!("connecting to relay: {} - {:?}", item.0, item.1);
*state.settings.lock().unwrap() = parsed;
}
}
};
}
});
// Get user's contact list
let contacts = client
.get_contact_list(Some(Duration::from_secs(10)))
.await
.unwrap();
// Run sync service
let handle = app.clone();
// Spawn a thread to handle it
tauri::async_runtime::spawn(async move {
let window = handle.get_window("main").unwrap();
let state = window.state::<Nostr>();
let client = &state.client;
// Update state
*state.contact_list.lock().unwrap() = contacts;
let filter = Filter::new()
.pubkey(public_key)
.kinds(vec![
Kind::TextNote,
Kind::Repost,
Kind::Reaction,
Kind::ZapReceipt,
])
.limit(500);
// Get user's settings
let handle = app.clone();
// Spawn a thread to handle it
tauri::async_runtime::spawn(async move {
let window = handle.get_window("main").unwrap();
let state = window.state::<Nostr>();
let client = &state.client;
match client.reconcile(filter, NegentropyOptions::default()).await {
Ok(_) => println!("Sync notification done."),
Err(_) => println!("Sync notification failed."),
}
});
let ident = "lume:settings";
let filter = Filter::new()
.author(public_key)
.kind(Kind::ApplicationSpecificData)
.identifier(ident)
.limit(1);
// Run notification service
// Spawn a thread to handle it
tauri::async_runtime::spawn(async move {
let window = app.get_window("main").unwrap();
let state = window.state::<Nostr>();
let client = &state.client;
if let Ok(events) = client
.get_events_of(vec![filter], Some(Duration::from_secs(5)))
.await
{
if let Some(event) = events.first() {
let content = event.content();
if let Ok(decrypted) = signer.nip44_decrypt(public_key, content).await {
let parsed: Settings =
serde_json::from_str(&decrypted).expect("Could not parse settings payload");
*state.settings.lock().unwrap() = parsed;
}
}
}
});
// Run sync service
let handle = app.clone();
// Spawn a thread to handle it
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
// Spawn a thread to handle it
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 {
// Handle notifications
if client
.handle_notifications(|notification| async {
if let RelayPoolNotification::Message { message, .. } = notification {
if let RelayMessage::Event {
subscription_id,
event,
..
} = notification
} = message
{
if subscription_id == notification_id {
println!("new notification: {}", event.as_json());
let id = subscription_id.to_string();
if id.starts_with("notification") {
if app
.emit_to(
EventTarget::window("panel"),
@ -336,78 +397,39 @@ pub async fn load_account(
}
_ => {}
}
} else if id.starts_with("event-") {
let raw = event.as_json();
let parsed = if event.kind == Kind::TextNote {
Some(parse_event(&event.content).await)
} else {
None
};
if app
.emit_to(
EventTarget::window(id),
"new_reply",
RichEvent { raw, parsed },
)
.is_err()
{
println!("Emit new notification failed.")
}
} else {
println!("new event: {}", event.as_json())
}
} else {
println!("new message: {}", message.as_json())
}
Ok(false)
})
.await;
});
Ok(true)
} else {
Err("Cancelled".into())
}
}
#[tauri::command]
#[specta::specta]
pub async fn connect_remote_account(uri: &str, state: State<'_, Nostr>) -> Result<String, String> {
let client = &state.client;
match NostrConnectURI::parse(uri) {
Ok(bunker_uri) => {
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 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(remote_npub)
}
Err(err) => Err(err.to_string()),
}
Ok(false)
})
.await
.is_ok()
{
print!("Listing for new event...");
}
Err(err) => Err(err.to_string()),
}
}
#[tauri::command(async)]
#[specta::specta]
pub fn get_encrypted_key(npub: &str, password: &str) -> Result<String, String> {
let keyring = Entry::new(npub, "nostr_secret").unwrap();
if let Ok(nsec) = keyring.get_password() {
let secret_key = SecretKey::from_bech32(nsec).unwrap();
let new_key = EncryptedSecretKey::new(&secret_key, password, 16, KeySecurity::Medium);
if let Ok(key) = new_key {
Ok(key.to_bech32().unwrap())
} else {
Err("Encrypt key failed".into())
}
} else {
Err("Key not found".into())
}
}
#[tauri::command(async)]
#[specta::specta]
pub fn get_private_key(npub: &str) -> Result<String, String> {
let keyring = Entry::new(npub, "nostr_secret").unwrap();
if let Ok(nsec) = keyring.get_password() {
let secret_key = SecretKey::from_bech32(nsec).unwrap();
Ok(secret_key.to_bech32().unwrap())
} else {
Err("Key not found".into())
}
});
Ok(true)
}

View File

@ -2,14 +2,14 @@ use std::collections::HashSet;
use std::str::FromStr;
use linkify::LinkFinder;
use nostr_sdk::prelude::Nip19Event;
use nostr_sdk::{Alphabet, Event, EventId, FromBech32, PublicKey, SingleLetterTag, Tag, TagKind};
use nostr_sdk::prelude::Nip19Event;
use reqwest::Client;
use serde::Serialize;
use specta::Type;
use url::Url;
#[derive(Debug, Serialize, Type)]
#[derive(Debug, Clone, Serialize, Type)]
pub struct Meta {
pub content: String,
pub images: Vec<String>,

View File

@ -11,10 +11,9 @@
"minWidth": 500,
"minHeight": 800,
"hiddenTitle": true,
"decorations": true,
"transparent": true,
"windowEffects": {
"effects": ["windowBackground"]
"state": "followsWindowActiveState",
"effects": ["underWindowBackground"]
}
},
{
@ -25,9 +24,8 @@
"height": 500,
"fullscreen": false,
"resizable": false,
"decorations": false,
"transparent": true,
"visible": false,
"decorations": false,
"windowEffects": {
"effects": ["popover"]
}