mirror of
https://github.com/luminous-devs/lume.git
synced 2024-09-29 16:30:55 +00:00
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:
parent
a4540a0802
commit
717c3e17df
@ -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"
|
||||
|
@ -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>
|
||||
|
@ -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"
|
||||
: "",
|
||||
)}
|
||||
>
|
||||
|
@ -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",
|
||||
)}
|
||||
>
|
||||
|
@ -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",
|
||||
)}
|
||||
>
|
||||
|
@ -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",
|
||||
)}
|
||||
>
|
||||
|
@ -15,24 +15,31 @@ 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">
|
||||
<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">
|
||||
<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">
|
||||
<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 px-3 h-11">
|
||||
<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" />
|
||||
@ -44,31 +51,27 @@ export function MentionNote({
|
||||
</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" : "",
|
||||
)}
|
||||
>
|
||||
<div className="select-text text-pretty line-clamp-3 content-break leading-normal">
|
||||
{data.content}
|
||||
</div>
|
||||
{openable ? (
|
||||
<div className="flex items-center justify-end px-2 h-11">
|
||||
<div className="flex items-center justify-start mt-3">
|
||||
<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"
|
||||
className="inline-flex items-center gap-1 text-blue-500 text-sm"
|
||||
>
|
||||
View post
|
||||
<LinkIcon className="size-4" />
|
||||
<LinkIcon className="size-3" />
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="h-3" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -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 }) => {
|
||||
|
@ -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}
|
||||
|
@ -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
|
||||
>
|
||||
|
@ -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,
|
||||
)}
|
||||
>
|
||||
|
@ -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 />
|
||||
|
@ -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>()({
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
143
apps/desktop2/src/routes/events/$id.tsx
Normal file
143
apps/desktop2/src/routes/events/$id.tsx
Normal 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>
|
||||
);
|
||||
}
|
41
apps/desktop2/src/routes/events/-components/child.tsx
Normal file
41
apps/desktop2/src/routes/events/-components/child.tsx
Normal 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;
|
41
apps/desktop2/src/routes/events/-components/parent.tsx
Normal file
41
apps/desktop2/src/routes/events/-components/parent.tsx
Normal 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;
|
@ -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>
|
||||
);
|
||||
}
|
@ -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>
|
||||
);
|
||||
}
|
@ -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">
|
||||
|
@ -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"
|
||||
|
@ -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",
|
||||
|
@ -9,6 +9,6 @@
|
||||
"devDependencies": {
|
||||
"@lume/tsconfig": "workspace:*",
|
||||
"@types/react": "^18.3.3",
|
||||
"typescript": "^5.4.5"
|
||||
"typescript": "^5.5.2"
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
// This file was generated by [tauri-specta](https://github.com/oscartbeaumont/tauri-specta). Do not edit this file manually.
|
||||
|
||||
/** user-defined commands **/
|
||||
@ -14,7 +13,10 @@ try {
|
||||
},
|
||||
async connectRelay(relay: string): Promise<Result<boolean, string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("connect_relay", { relay }) };
|
||||
return {
|
||||
status: "ok",
|
||||
data: await TAURI_INVOKE("connect_relay", { relay }),
|
||||
};
|
||||
} catch (e) {
|
||||
if (e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
@ -22,7 +24,10 @@ try {
|
||||
},
|
||||
async removeRelay(relay: string): Promise<Result<boolean, string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("remove_relay", { relay }) };
|
||||
return {
|
||||
status: "ok",
|
||||
data: await TAURI_INVOKE("remove_relay", { relay }),
|
||||
};
|
||||
} catch (e) {
|
||||
if (e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
@ -38,7 +43,10 @@ try {
|
||||
},
|
||||
async saveBootstrapRelays(relays: string): Promise<Result<null, string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("save_bootstrap_relays", { relays }) };
|
||||
return {
|
||||
status: "ok",
|
||||
data: await TAURI_INVOKE("save_bootstrap_relays", { relays }),
|
||||
};
|
||||
} catch (e) {
|
||||
if (e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
@ -52,7 +60,7 @@ try {
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async createAccount() : Promise<Result<Account, null>> {
|
||||
async createAccount(): Promise<Result<Account, string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("create_account") };
|
||||
} catch (e) {
|
||||
@ -60,17 +68,29 @@ try {
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async saveAccount(nsec: string, password: string) : Promise<Result<string, string>> {
|
||||
async saveAccount(
|
||||
nsec: string,
|
||||
password: string,
|
||||
): Promise<Result<string, string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("save_account", { nsec, password }) };
|
||||
return {
|
||||
status: "ok",
|
||||
data: await TAURI_INVOKE("save_account", { nsec, password }),
|
||||
};
|
||||
} catch (e) {
|
||||
if (e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async getEncryptedKey(npub: string, password: string) : Promise<Result<string, string>> {
|
||||
async getEncryptedKey(
|
||||
npub: string,
|
||||
password: string,
|
||||
): Promise<Result<string, string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("get_encrypted_key", { npub, password }) };
|
||||
return {
|
||||
status: "ok",
|
||||
data: await TAURI_INVOKE("get_encrypted_key", { npub, password }),
|
||||
};
|
||||
} catch (e) {
|
||||
if (e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
@ -78,7 +98,10 @@ try {
|
||||
},
|
||||
async getPrivateKey(npub: string): Promise<Result<string, string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("get_private_key", { npub }) };
|
||||
return {
|
||||
status: "ok",
|
||||
data: await TAURI_INVOKE("get_private_key", { npub }),
|
||||
};
|
||||
} catch (e) {
|
||||
if (e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
@ -86,15 +109,24 @@ try {
|
||||
},
|
||||
async connectRemoteAccount(uri: string): Promise<Result<string, string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("connect_remote_account", { uri }) };
|
||||
return {
|
||||
status: "ok",
|
||||
data: await TAURI_INVOKE("connect_remote_account", { uri }),
|
||||
};
|
||||
} catch (e) {
|
||||
if (e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async loadAccount(npub: string, bunker: string | null) : Promise<Result<boolean, string>> {
|
||||
async loadAccount(
|
||||
npub: string,
|
||||
bunker: string | null,
|
||||
): Promise<Result<boolean, string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("load_account", { npub, bunker }) };
|
||||
return {
|
||||
status: "ok",
|
||||
data: await TAURI_INVOKE("load_account", { npub, bunker }),
|
||||
};
|
||||
} catch (e) {
|
||||
if (e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
@ -126,15 +158,39 @@ try {
|
||||
},
|
||||
async setContactList(publicKeys: string[]): Promise<Result<boolean, string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("set_contact_list", { publicKeys }) };
|
||||
return {
|
||||
status: "ok",
|
||||
data: await TAURI_INVOKE("set_contact_list", { publicKeys }),
|
||||
};
|
||||
} catch (e) {
|
||||
if (e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async createProfile(name: string, displayName: string, about: string, picture: string, banner: string, nip05: string, lud16: string, website: string) : Promise<Result<string, string>> {
|
||||
async createProfile(
|
||||
name: string,
|
||||
displayName: string,
|
||||
about: string,
|
||||
picture: string,
|
||||
banner: string,
|
||||
nip05: string,
|
||||
lud16: string,
|
||||
website: string,
|
||||
): Promise<Result<string, string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("create_profile", { name, displayName, about, picture, banner, nip05, lud16, website }) };
|
||||
return {
|
||||
status: "ok",
|
||||
data: await TAURI_INVOKE("create_profile", {
|
||||
name,
|
||||
displayName,
|
||||
about,
|
||||
picture,
|
||||
banner,
|
||||
nip05,
|
||||
lud16,
|
||||
website,
|
||||
}),
|
||||
};
|
||||
} catch (e) {
|
||||
if (e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
@ -142,7 +198,10 @@ try {
|
||||
},
|
||||
async isContactListEmpty(): Promise<Result<boolean, null>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("is_contact_list_empty") };
|
||||
return {
|
||||
status: "ok",
|
||||
data: await TAURI_INVOKE("is_contact_list_empty"),
|
||||
};
|
||||
} catch (e) {
|
||||
if (e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
@ -150,15 +209,24 @@ try {
|
||||
},
|
||||
async checkContact(hex: string): Promise<Result<boolean, null>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("check_contact", { hex }) };
|
||||
return {
|
||||
status: "ok",
|
||||
data: await TAURI_INVOKE("check_contact", { hex }),
|
||||
};
|
||||
} catch (e) {
|
||||
if (e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async toggleContact(hex: string, alias: string | null) : Promise<Result<string, string>> {
|
||||
async toggleContact(
|
||||
hex: string,
|
||||
alias: string | null,
|
||||
): Promise<Result<string, string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("toggle_contact", { hex, alias }) };
|
||||
return {
|
||||
status: "ok",
|
||||
data: await TAURI_INVOKE("toggle_contact", { hex, alias }),
|
||||
};
|
||||
} catch (e) {
|
||||
if (e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
@ -172,9 +240,15 @@ try {
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async setNstore(key: string, content: string) : Promise<Result<string, string>> {
|
||||
async setNstore(
|
||||
key: string,
|
||||
content: string,
|
||||
): Promise<Result<string, string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("set_nstore", { key, content }) };
|
||||
return {
|
||||
status: "ok",
|
||||
data: await TAURI_INVOKE("set_nstore", { key, content }),
|
||||
};
|
||||
} catch (e) {
|
||||
if (e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
@ -204,17 +278,31 @@ try {
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async zapProfile(id: string, amount: string, message: string) : Promise<Result<boolean, string>> {
|
||||
async zapProfile(
|
||||
id: string,
|
||||
amount: string,
|
||||
message: string,
|
||||
): Promise<Result<boolean, string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("zap_profile", { id, amount, message }) };
|
||||
return {
|
||||
status: "ok",
|
||||
data: await TAURI_INVOKE("zap_profile", { id, amount, message }),
|
||||
};
|
||||
} catch (e) {
|
||||
if (e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async zapEvent(id: string, amount: string, message: string) : Promise<Result<boolean, string>> {
|
||||
async zapEvent(
|
||||
id: string,
|
||||
amount: string,
|
||||
message: string,
|
||||
): Promise<Result<boolean, string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("zap_event", { id, amount, message }) };
|
||||
return {
|
||||
status: "ok",
|
||||
data: await TAURI_INVOKE("zap_event", { id, amount, message }),
|
||||
};
|
||||
} catch (e) {
|
||||
if (e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
@ -222,7 +310,10 @@ try {
|
||||
},
|
||||
async friendToFriend(npub: string): Promise<Result<boolean, string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("friend_to_friend", { npub }) };
|
||||
return {
|
||||
status: "ok",
|
||||
data: await TAURI_INVOKE("friend_to_friend", { npub }),
|
||||
};
|
||||
} catch (e) {
|
||||
if (e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
@ -246,15 +337,24 @@ try {
|
||||
},
|
||||
async setNewSettings(settings: string): Promise<Result<null, null>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("set_new_settings", { settings }) };
|
||||
return {
|
||||
status: "ok",
|
||||
data: await TAURI_INVOKE("set_new_settings", { settings }),
|
||||
};
|
||||
} catch (e) {
|
||||
if (e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async verifyNip05(key: string, nip05: string) : Promise<Result<boolean, string>> {
|
||||
async verifyNip05(
|
||||
key: string,
|
||||
nip05: string,
|
||||
): Promise<Result<boolean, string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("verify_nip05", { key, nip05 }) };
|
||||
return {
|
||||
status: "ok",
|
||||
data: await TAURI_INVOKE("verify_nip05", { key, nip05 }),
|
||||
};
|
||||
} catch (e) {
|
||||
if (e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
@ -262,7 +362,10 @@ try {
|
||||
},
|
||||
async getEventMeta(content: string): Promise<Result<Meta, null>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("get_event_meta", { content }) };
|
||||
return {
|
||||
status: "ok",
|
||||
data: await TAURI_INVOKE("get_event_meta", { content }),
|
||||
};
|
||||
} catch (e) {
|
||||
if (e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
@ -276,9 +379,15 @@ try {
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async getEventFrom(id: string, relayHint: string) : Promise<Result<RichEvent, string>> {
|
||||
async getEventFrom(
|
||||
id: string,
|
||||
relayHint: string,
|
||||
): Promise<Result<RichEvent, string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("get_event_from", { id, relayHint }) };
|
||||
return {
|
||||
status: "ok",
|
||||
data: await TAURI_INVOKE("get_event_from", { id, relayHint }),
|
||||
};
|
||||
} catch (e) {
|
||||
if (e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
@ -292,57 +401,121 @@ try {
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async getEventsBy(publicKey: string, asOf: string | null) : Promise<Result<RichEvent[], string>> {
|
||||
async listenEventReply(id: string): Promise<Result<null, string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("get_events_by", { publicKey, asOf }) };
|
||||
return {
|
||||
status: "ok",
|
||||
data: await TAURI_INVOKE("listen_event_reply", { id }),
|
||||
};
|
||||
} catch (e) {
|
||||
if (e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async getLocalEvents(until: string | null) : Promise<Result<RichEvent[], string>> {
|
||||
async unlistenEventReply(id: string): Promise<Result<null, null>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("get_local_events", { until }) };
|
||||
return {
|
||||
status: "ok",
|
||||
data: await TAURI_INVOKE("unlisten_event_reply", { id }),
|
||||
};
|
||||
} catch (e) {
|
||||
if (e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async getGroupEvents(publicKeys: string[], until: string | null) : Promise<Result<RichEvent[], string>> {
|
||||
async getEventsBy(
|
||||
publicKey: string,
|
||||
asOf: string | null,
|
||||
): Promise<Result<RichEvent[], string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("get_group_events", { publicKeys, until }) };
|
||||
return {
|
||||
status: "ok",
|
||||
data: await TAURI_INVOKE("get_events_by", { publicKey, asOf }),
|
||||
};
|
||||
} catch (e) {
|
||||
if (e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async getGlobalEvents(until: string | null) : Promise<Result<RichEvent[], string>> {
|
||||
async getLocalEvents(
|
||||
until: string | null,
|
||||
): Promise<Result<RichEvent[], string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("get_global_events", { until }) };
|
||||
return {
|
||||
status: "ok",
|
||||
data: await TAURI_INVOKE("get_local_events", { until }),
|
||||
};
|
||||
} catch (e) {
|
||||
if (e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async getHashtagEvents(hashtags: string[], until: string | null) : Promise<Result<RichEvent[], string>> {
|
||||
async getGroupEvents(
|
||||
publicKeys: string[],
|
||||
until: string | null,
|
||||
): Promise<Result<RichEvent[], string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("get_hashtag_events", { hashtags, until }) };
|
||||
return {
|
||||
status: "ok",
|
||||
data: await TAURI_INVOKE("get_group_events", { publicKeys, until }),
|
||||
};
|
||||
} catch (e) {
|
||||
if (e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async publish(content: string, warning: string | null, difficulty: number | null) : Promise<Result<string, string>> {
|
||||
async getGlobalEvents(
|
||||
until: string | null,
|
||||
): Promise<Result<RichEvent[], string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("publish", { content, warning, difficulty }) };
|
||||
return {
|
||||
status: "ok",
|
||||
data: await TAURI_INVOKE("get_global_events", { until }),
|
||||
};
|
||||
} catch (e) {
|
||||
if (e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async reply(content: string, to: string, root: string | null) : Promise<Result<string, string>> {
|
||||
async getHashtagEvents(
|
||||
hashtags: string[],
|
||||
until: string | null,
|
||||
): Promise<Result<RichEvent[], string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("reply", { content, to, root }) };
|
||||
return {
|
||||
status: "ok",
|
||||
data: await TAURI_INVOKE("get_hashtag_events", { hashtags, until }),
|
||||
};
|
||||
} catch (e) {
|
||||
if (e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async publish(
|
||||
content: string,
|
||||
warning: string | null,
|
||||
difficulty: number | null,
|
||||
): Promise<Result<string, string>> {
|
||||
try {
|
||||
return {
|
||||
status: "ok",
|
||||
data: await TAURI_INVOKE("publish", { content, warning, difficulty }),
|
||||
};
|
||||
} catch (e) {
|
||||
if (e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async reply(
|
||||
content: string,
|
||||
to: string,
|
||||
root: string | null,
|
||||
): Promise<Result<string, string>> {
|
||||
try {
|
||||
return {
|
||||
status: "ok",
|
||||
data: await TAURI_INVOKE("reply", { content, to, root }),
|
||||
};
|
||||
} catch (e) {
|
||||
if (e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
@ -358,7 +531,10 @@ try {
|
||||
},
|
||||
async eventToBech32(id: string): Promise<Result<string, string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("event_to_bech32", { id }) };
|
||||
return {
|
||||
status: "ok",
|
||||
data: await TAURI_INVOKE("event_to_bech32", { id }),
|
||||
};
|
||||
} catch (e) {
|
||||
if (e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
@ -366,7 +542,10 @@ try {
|
||||
},
|
||||
async userToBech32(user: string): Promise<Result<string, string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("user_to_bech32", { user }) };
|
||||
return {
|
||||
status: "ok",
|
||||
data: await TAURI_INVOKE("user_to_bech32", { user }),
|
||||
};
|
||||
} catch (e) {
|
||||
if (e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
@ -375,33 +554,53 @@ try {
|
||||
async showInFolder(path: string): Promise<void> {
|
||||
await TAURI_INVOKE("show_in_folder", { path });
|
||||
},
|
||||
async createColumn(label: string, x: number, y: number, width: number, height: number, url: string) : Promise<Result<string, string>> {
|
||||
async createColumn(column: Column): Promise<Result<string, string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("create_column", { label, x, y, width, height, url }) };
|
||||
return {
|
||||
status: "ok",
|
||||
data: await TAURI_INVOKE("create_column", { column }),
|
||||
};
|
||||
} catch (e) {
|
||||
if (e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async closeColumn(label: string) : Promise<Result<boolean, null>> {
|
||||
async closeColumn(label: string): Promise<Result<boolean, string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("close_column", { label }) };
|
||||
return {
|
||||
status: "ok",
|
||||
data: await TAURI_INVOKE("close_column", { label }),
|
||||
};
|
||||
} catch (e) {
|
||||
if (e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async repositionColumn(label: string, x: number, y: number) : Promise<Result<null, string>> {
|
||||
async repositionColumn(
|
||||
label: string,
|
||||
x: number,
|
||||
y: number,
|
||||
): Promise<Result<null, string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("reposition_column", { label, x, y }) };
|
||||
return {
|
||||
status: "ok",
|
||||
data: await TAURI_INVOKE("reposition_column", { label, x, y }),
|
||||
};
|
||||
} catch (e) {
|
||||
if (e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async resizeColumn(label: string, width: number, height: number) : Promise<Result<null, string>> {
|
||||
async resizeColumn(
|
||||
label: string,
|
||||
width: number,
|
||||
height: number,
|
||||
): Promise<Result<null, string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("resize_column", { label, width, height }) };
|
||||
return {
|
||||
status: "ok",
|
||||
data: await TAURI_INVOKE("resize_column", { label, width, height }),
|
||||
};
|
||||
} catch (e) {
|
||||
if (e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
@ -409,7 +608,10 @@ try {
|
||||
},
|
||||
async openWindow(window: Window): Promise<Result<null, string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("open_window", { window }) };
|
||||
return {
|
||||
status: "ok",
|
||||
data: await TAURI_INVOKE("open_window", { window }),
|
||||
};
|
||||
} catch (e) {
|
||||
if (e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
@ -420,25 +622,59 @@ await TAURI_INVOKE("open_main_window");
|
||||
},
|
||||
async setBadge(count: number): Promise<void> {
|
||||
await TAURI_INVOKE("set_badge", { count });
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
/** user-defined events **/
|
||||
|
||||
|
||||
|
||||
/** user-defined statics **/
|
||||
|
||||
|
||||
|
||||
/** user-defined types **/
|
||||
|
||||
export type Account = { npub: string; nsec: string }
|
||||
export type Meta = { content: string; images: string[]; videos: string[]; events: string[]; mentions: string[]; hashtags: string[] }
|
||||
export type Relays = { connected: string[]; read: string[] | null; write: string[] | null; both: string[] | null }
|
||||
export type RichEvent = { raw: string; parsed: Meta | null }
|
||||
export type Settings = { proxy: string | null; image_resize_service: string | null; use_relay_hint: boolean; content_warning: boolean; display_avatar: boolean; display_zap_button: boolean; display_repost_button: boolean; display_media: boolean }
|
||||
export type Window = { label: string; title: string; url: string; width: number; height: number; maximizable: boolean; minimizable: boolean }
|
||||
export type Account = { npub: string; nsec: string };
|
||||
export type Column = {
|
||||
label: string;
|
||||
url: string;
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
export type Meta = {
|
||||
content: string;
|
||||
images: string[];
|
||||
videos: string[];
|
||||
events: string[];
|
||||
mentions: string[];
|
||||
hashtags: string[];
|
||||
};
|
||||
export type Relays = {
|
||||
connected: string[];
|
||||
read: string[] | null;
|
||||
write: string[] | null;
|
||||
both: string[] | null;
|
||||
};
|
||||
export type RichEvent = { raw: string; parsed: Meta | null };
|
||||
export type Settings = {
|
||||
proxy: string | null;
|
||||
image_resize_service: string | null;
|
||||
use_relay_hint: boolean;
|
||||
content_warning: boolean;
|
||||
display_avatar: boolean;
|
||||
display_zap_button: boolean;
|
||||
display_repost_button: boolean;
|
||||
display_media: boolean;
|
||||
};
|
||||
export type Window = {
|
||||
label: string;
|
||||
title: string;
|
||||
url: string;
|
||||
width: number;
|
||||
height: number;
|
||||
maximizable: boolean;
|
||||
minimizable: boolean;
|
||||
hidden_title: boolean;
|
||||
};
|
||||
|
||||
/** tauri-specta globals **/
|
||||
|
||||
@ -448,10 +684,10 @@ import { type WebviewWindow as __WebviewWindow__ } from "@tauri-apps/api/webview
|
||||
|
||||
type __EventObj__<T> = {
|
||||
listen: (
|
||||
cb: TAURI_API_EVENT.EventCallback<T>
|
||||
cb: TAURI_API_EVENT.EventCallback<T>,
|
||||
) => ReturnType<typeof TAURI_API_EVENT.listen<T>>;
|
||||
once: (
|
||||
cb: TAURI_API_EVENT.EventCallback<T>
|
||||
cb: TAURI_API_EVENT.EventCallback<T>,
|
||||
) => ReturnType<typeof TAURI_API_EVENT.once<T>>;
|
||||
emit: T extends null
|
||||
? (payload?: T) => ReturnType<typeof TAURI_API_EVENT.emit>
|
||||
@ -463,7 +699,7 @@ export type Result<T, E> =
|
||||
| { status: "error"; error: E };
|
||||
|
||||
function __makeEvents__<T extends Record<string, any>>(
|
||||
mappings: Record<keyof T, string>
|
||||
mappings: Record<keyof T, string>,
|
||||
) {
|
||||
return new Proxy(
|
||||
{} as unknown as {
|
||||
@ -493,8 +729,6 @@ function __makeEvents__<T extends Record<string, any>>(
|
||||
},
|
||||
});
|
||||
},
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
if (rootEvent.replies?.length) {
|
||||
rootEvent.replies.push(event);
|
||||
} else {
|
||||
rootEvent.replies = [event];
|
||||
}
|
||||
|
||||
replies.add(event.id);
|
||||
}
|
||||
}
|
||||
// Add current event to queue
|
||||
removeQueues.add(event.id);
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return events.filter((ev) => !replies.has(ev.id));
|
||||
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);
|
||||
}
|
||||
} else {
|
||||
rootEvent.replies = [event];
|
||||
// Add current event to queue
|
||||
removeQueues.add(event.id);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -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") {
|
||||
|
@ -9,6 +9,6 @@
|
||||
"access": "public"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.4.5"
|
||||
"typescript": "^5.5.2"
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,6 @@
|
||||
"@lume/types": "workspace:^",
|
||||
"@types/react": "^18.3.3",
|
||||
"tailwindcss": "^3.4.4",
|
||||
"typescript": "^5.4.5"
|
||||
"typescript": "^5.5.2"
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
1263
pnpm-lock.yaml
1263
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
553
src-tauri/Cargo.lock
generated
553
src-tauri/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -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
@ -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"]}}
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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| {
|
||||
|
@ -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,11 +208,11 @@ 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 file = std::fs::File::open(&path).unwrap();
|
||||
let lines = io::BufReader::new(file).lines();
|
||||
|
||||
// Add bootstrap relays to relay pool
|
||||
@ -228,6 +235,7 @@ fn main() {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Connect
|
||||
client.connect().await;
|
||||
|
@ -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,8 +146,11 @@ 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 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);
|
||||
|
||||
match client.get_events_of(vec![filter], None).await {
|
||||
@ -169,8 +172,41 @@ pub async fn get_replies(id: &str, state: State<'_, Nostr>) -> Result<Vec<RichEv
|
||||
Err(err) => Err(err.to_string()),
|
||||
}
|
||||
}
|
||||
Err(_) => Err("Event ID is not valid".into()),
|
||||
|
||||
#[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]
|
||||
|
@ -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,7 +170,11 @@ pub async fn load_account(
|
||||
let client = &state.client;
|
||||
let keyring = Entry::new(npub, "nostr_secret").unwrap();
|
||||
|
||||
if let Ok(password) = keyring.get_password() {
|
||||
let password = match keyring.get_password() {
|
||||
Ok(pw) => pw,
|
||||
Err(_) => return Err("Cancelled".into()),
|
||||
};
|
||||
|
||||
match bunker {
|
||||
Some(uri) => {
|
||||
let app_keys = Keys::parse(password).expect("Secret Key is modified, please check again.");
|
||||
@ -140,7 +209,7 @@ pub async fn load_account(
|
||||
.author(public_key)
|
||||
.kind(Kind::RelayList)
|
||||
.limit(1)],
|
||||
Some(Duration::from_secs(10)),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
{
|
||||
@ -173,14 +242,25 @@ pub async fn load_account(
|
||||
};
|
||||
|
||||
// Get user's contact list
|
||||
let contacts = client
|
||||
.get_contact_list(Some(Duration::from_secs(10)))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Update state
|
||||
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
|
||||
@ -239,41 +319,22 @@ pub async fn load_account(
|
||||
// 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
|
||||
if client
|
||||
.handle_notifications(|notification| async {
|
||||
if let RelayPoolNotification::Event {
|
||||
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;
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
print!("Listing for new event...");
|
||||
}
|
||||
});
|
||||
|
||||
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()),
|
||||
}
|
||||
}
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
@ -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>,
|
||||
|
@ -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"]
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user