Some improments and Negentropy (#219)
* feat: adjust default window size * feat: save window state * feat: add window state plugin * feat: add search * feat: use negentropy for newsfeed * feat: live feeds * feat: add search user
This commit is contained in:
@@ -1,12 +1,11 @@
|
|||||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
import { RouterProvider, createRouter } from "@tanstack/react-router";
|
import { RouterProvider, createRouter } from "@tanstack/react-router";
|
||||||
import { StrictMode } from "react";
|
|
||||||
import { type } from "@tauri-apps/plugin-os";
|
import { type } from "@tauri-apps/plugin-os";
|
||||||
|
import { StrictMode } from "react";
|
||||||
import ReactDOM from "react-dom/client";
|
import ReactDOM from "react-dom/client";
|
||||||
import { routeTree } from "./router.gen"; // auto generated file
|
import { routeTree } from "./router.gen"; // auto generated file
|
||||||
import "./app.css";
|
import "./app.css";
|
||||||
|
|
||||||
// Set up a Router instance
|
|
||||||
const queryClient = new QueryClient();
|
const queryClient = new QueryClient();
|
||||||
const platform = type();
|
const platform = type();
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { LumeWindow, useEvent } from "@lume/system";
|
|
||||||
import { LinkIcon } from "@lume/icons";
|
|
||||||
import { cn } from "@lume/utils";
|
|
||||||
import { User } from "@/components/user";
|
import { User } from "@/components/user";
|
||||||
|
import { LinkIcon } from "@lume/icons";
|
||||||
|
import { LumeWindow, useEvent } from "@lume/system";
|
||||||
import { Spinner } from "@lume/ui";
|
import { Spinner } from "@lume/ui";
|
||||||
|
import { cn } from "@lume/utils";
|
||||||
|
|
||||||
export function MentionNote({
|
export function MentionNote({
|
||||||
eventId,
|
eventId,
|
||||||
@@ -40,7 +40,7 @@ export function MentionNote({
|
|||||||
<div className="pl-4 py-3 flex flex-col w-full border-l-2 border-black/5 dark:border-white/5">
|
<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.Provider pubkey={data.pubkey}>
|
||||||
<User.Root className="flex items-center gap-2 h-8">
|
<User.Root className="flex items-center gap-2 h-8">
|
||||||
<User.Avatar className="object-cover rounded-full size-6 shrink-0" />
|
<User.Avatar className="rounded-full size-6" />
|
||||||
<div className="inline-flex items-center flex-1 gap-2">
|
<div className="inline-flex items-center flex-1 gap-2">
|
||||||
<User.Name className="font-semibold text-neutral-900 dark:text-neutral-100" />
|
<User.Name className="font-semibold text-neutral-900 dark:text-neutral-100" />
|
||||||
<span className="text-neutral-600 dark:text-neutral-400">·</span>
|
<span className="text-neutral-600 dark:text-neutral-400">·</span>
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ export function Images({ urls }: { urls: string[] }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative pl-2 overflow-hidden group">
|
<div className="relative px-3 overflow-hidden group">
|
||||||
<div ref={emblaRef} className="w-full h-[320px]">
|
<div ref={emblaRef} className="w-full h-[320px]">
|
||||||
<div className="flex w-full gap-2 scrollbar-none">
|
<div className="flex w-full gap-2 scrollbar-none">
|
||||||
{imageUrls.map((url, index) => (
|
{imageUrls.map((url, index) => (
|
||||||
|
|||||||
@@ -20,10 +20,11 @@ export function VideoPreview({ url }: { url: string }) {
|
|||||||
<div className="my-1">
|
<div className="my-1">
|
||||||
<video
|
<video
|
||||||
className="max-h-[600px] w-auto object-cover rounded-lg outline outline-1 -outline-offset-1 outline-black/15"
|
className="max-h-[600px] w-auto object-cover rounded-lg outline outline-1 -outline-offset-1 outline-black/15"
|
||||||
|
preload="metadata"
|
||||||
controls
|
controls
|
||||||
muted
|
muted
|
||||||
>
|
>
|
||||||
<source src={url} type="video/mp4" />
|
<source src={`${url}#t=0.1`} type="video/mp4" />
|
||||||
Your browser does not support the video tag.
|
Your browser does not support the video tag.
|
||||||
</video>
|
</video>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ export function Videos({ urls }: { urls: string[] }) {
|
|||||||
return (
|
return (
|
||||||
<div className="group px-3">
|
<div className="group px-3">
|
||||||
<video
|
<video
|
||||||
className="w-full h-auto object-cover rounded-lg outline outline-1 -outline-offset-1 outline-black/15"
|
className="max-h-[400px] w-auto object-cover rounded-lg outline outline-1 -outline-offset-1 outline-black/15"
|
||||||
preload="metadata"
|
preload="metadata"
|
||||||
controls
|
controls
|
||||||
muted
|
muted
|
||||||
>
|
>
|
||||||
<source src={urls[0]} type="video/mp4" />
|
<source src={`${urls[0]}#t=0.1`} type="video/mp4" />
|
||||||
Your browser does not support the video tag.
|
Your browser does not support the video tag.
|
||||||
</video>
|
</video>
|
||||||
</div>
|
</div>
|
||||||
@@ -28,7 +28,7 @@ export function Videos({ urls }: { urls: string[] }) {
|
|||||||
controls={false}
|
controls={false}
|
||||||
muted
|
muted
|
||||||
>
|
>
|
||||||
<source src={item} type="video/mp4" />
|
<source src={`${item}#t=0.1`} type="video/mp4" />
|
||||||
Your browser does not support the video tag.
|
Your browser does not support the video tag.
|
||||||
</video>
|
</video>
|
||||||
</CarouselItem>
|
</CarouselItem>
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export function NoteUser({ className }: { className?: string }) {
|
|||||||
onClick={(e) => showContextMenu(e)}
|
onClick={(e) => showContextMenu(e)}
|
||||||
className="shrink-0"
|
className="shrink-0"
|
||||||
>
|
>
|
||||||
<User.Avatar className="object-cover rounded-full size-8 outline outline-1 -outline-offset-1 outline-black/15" />
|
<User.Avatar className="rounded-full size-8" />
|
||||||
</button>
|
</button>
|
||||||
<div className="flex items-center w-full gap-3">
|
<div className="flex items-center w-full gap-3">
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ export const RepostNote = memo(function RepostNote({
|
|||||||
<div className="text-sm font-medium text-neutral-800 dark:text-neutral-200">
|
<div className="text-sm font-medium text-neutral-800 dark:text-neutral-200">
|
||||||
Reposted by
|
Reposted by
|
||||||
</div>
|
</div>
|
||||||
<User.Avatar className="object-cover rounded-full size-6 shrink-0 ring-1 ring-neutral-200/50 dark:ring-neutral-800/50" />
|
<User.Avatar className="rounded-full size-6" />
|
||||||
</User.Root>
|
</User.Root>
|
||||||
</User.Provider>
|
</User.Provider>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,8 +10,6 @@ export const TextNote = memo(function TextNote({
|
|||||||
event: LumeEvent;
|
event: LumeEvent;
|
||||||
className?: string;
|
className?: string;
|
||||||
}) {
|
}) {
|
||||||
console.log("Rendered at: ", event.id, new Date().toLocaleTimeString());
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Note.Provider event={event}>
|
<Note.Provider event={event}>
|
||||||
<Note.Root
|
<Note.Root
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { cn } from "@lume/utils";
|
|||||||
import * as Avatar from "@radix-ui/react-avatar";
|
import * as Avatar from "@radix-ui/react-avatar";
|
||||||
import { useRouteContext } from "@tanstack/react-router";
|
import { useRouteContext } from "@tanstack/react-router";
|
||||||
import { minidenticon } from "minidenticons";
|
import { minidenticon } from "minidenticons";
|
||||||
import { nanoid } from "nanoid";
|
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { useUserContext } from "./provider";
|
import { useUserContext } from "./provider";
|
||||||
|
|
||||||
@@ -22,22 +21,29 @@ export function UserAvatar({ className }: { className?: string }) {
|
|||||||
}
|
}
|
||||||
}, [user.profile?.picture]);
|
}, [user.profile?.picture]);
|
||||||
|
|
||||||
const fallbackAvatar = useMemo(
|
const fallback = useMemo(
|
||||||
() =>
|
() =>
|
||||||
`data:image/svg+xml;utf8,${encodeURIComponent(
|
`data:image/svg+xml;utf8,${encodeURIComponent(
|
||||||
minidenticon(user.pubkey || nanoid(), 90, 50),
|
minidenticon(user.pubkey, 60, 50),
|
||||||
)}`,
|
)}`,
|
||||||
[user.pubkey],
|
[user.pubkey],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (settings && !settings.display_avatar) {
|
if (settings && !settings.display_avatar) {
|
||||||
return (
|
return (
|
||||||
<Avatar.Root className="shrink-0">
|
<Avatar.Root
|
||||||
|
className={cn(
|
||||||
|
"shrink-0 block overflow-hidden bg-neutral-200 dark:bg-neutral-800",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
<Avatar.Fallback delayMs={120}>
|
<Avatar.Fallback delayMs={120}>
|
||||||
<img
|
<img
|
||||||
src={fallbackAvatar}
|
src={fallback}
|
||||||
alt={user.pubkey}
|
alt={user.pubkey}
|
||||||
className={cn("bg-black dark:bg-white", className)}
|
loading="lazy"
|
||||||
|
decoding="async"
|
||||||
|
className="size-full bg-black dark:bg-white outline-[.5px] outline-black/5 content-visibility-auto contain-intrinsic-size-[auto]"
|
||||||
/>
|
/>
|
||||||
</Avatar.Fallback>
|
</Avatar.Fallback>
|
||||||
</Avatar.Root>
|
</Avatar.Root>
|
||||||
@@ -45,19 +51,24 @@ export function UserAvatar({ className }: { className?: string }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Avatar.Root className="shrink-0">
|
<Avatar.Root
|
||||||
|
className={cn(
|
||||||
|
"shrink-0 block overflow-hidden bg-neutral-200 dark:bg-neutral-800",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
<Avatar.Image
|
<Avatar.Image
|
||||||
src={picture}
|
src={picture}
|
||||||
alt={user.pubkey}
|
alt={user.pubkey}
|
||||||
loading="eager"
|
loading="lazy"
|
||||||
decoding="async"
|
decoding="async"
|
||||||
className={cn("outline-[.5px] outline-black/5 object-cover", className)}
|
className="w-full aspect-square object-cover outline-[.5px] outline-black/5 content-visibility-auto contain-intrinsic-size-[auto]"
|
||||||
/>
|
/>
|
||||||
<Avatar.Fallback delayMs={120}>
|
<Avatar.Fallback>
|
||||||
<img
|
<img
|
||||||
src={fallbackAvatar}
|
src={fallback}
|
||||||
alt={user.pubkey}
|
alt={user.pubkey}
|
||||||
className={cn("bg-black dark:bg-white", className)}
|
className="size-full bg-black dark:bg-white outline-[.5px] outline-black/5 content-visibility-auto contain-intrinsic-size-[auto]"
|
||||||
/>
|
/>
|
||||||
</Avatar.Fallback>
|
</Avatar.Fallback>
|
||||||
</Avatar.Root>
|
</Avatar.Root>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Column } from "@/components/column";
|
import { Column } from "@/components/column";
|
||||||
import { Toolbar } from "@/components/toolbar";
|
import { Toolbar } from "@/components/toolbar";
|
||||||
import { ArrowLeftIcon, ArrowRightIcon, PlusSquareIcon } from "@lume/icons";
|
import { ArrowLeftIcon, ArrowRightIcon } from "@lume/icons";
|
||||||
import { NostrQuery } from "@lume/system";
|
import { NostrQuery } from "@lume/system";
|
||||||
import type { ColumnEvent, LumeColumn } from "@lume/types";
|
import type { ColumnEvent, LumeColumn } from "@lume/types";
|
||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
@@ -45,17 +45,6 @@ function Screen() {
|
|||||||
getCurrent().emit("child-webview", { resize: true, direction: "x" });
|
getCurrent().emit("child-webview", { resize: true, direction: "x" });
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const openLumeStore = useDebouncedCallback(async () => {
|
|
||||||
await getCurrent().emit("columns", {
|
|
||||||
type: "add",
|
|
||||||
column: {
|
|
||||||
label: "store",
|
|
||||||
name: "Store",
|
|
||||||
content: "/store/official",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}, 150);
|
|
||||||
|
|
||||||
const add = useDebouncedCallback((column: LumeColumn) => {
|
const add = useDebouncedCallback((column: LumeColumn) => {
|
||||||
column.label = `${column.label}-${nanoid()}`; // update col label
|
column.label = `${column.label}-${nanoid()}`; // update col label
|
||||||
setColumns((prev) => [column, ...prev]);
|
setColumns((prev) => [column, ...prev]);
|
||||||
@@ -158,29 +147,20 @@ function Screen() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Toolbar>
|
<Toolbar>
|
||||||
<div className="flex items-center h-8 gap-1 p-[2px] rounded-full bg-black/5 dark:bg-white/5">
|
<button
|
||||||
<button
|
type="button"
|
||||||
type="button"
|
onClick={() => scrollPrev()}
|
||||||
onClick={() => scrollPrev()}
|
className="inline-flex items-center justify-center rounded-full size-8 hover:bg-black/5 dark:hover:bg-white/5"
|
||||||
className="inline-flex items-center justify-center rounded-full size-7 text-neutral-800 hover:bg-black/10 dark:text-neutral-200 dark:hover:bg-white/10"
|
>
|
||||||
>
|
<ArrowLeftIcon className="size-4" />
|
||||||
<ArrowLeftIcon className="size-4" />
|
</button>
|
||||||
</button>
|
<button
|
||||||
<button
|
type="button"
|
||||||
type="button"
|
onClick={() => scrollNext()}
|
||||||
onClick={() => openLumeStore()}
|
className="inline-flex items-center justify-center rounded-full size-8 hover:bg-black/5 dark:hover:bg-white/5"
|
||||||
className="inline-flex items-center justify-center rounded-full size-7 text-neutral-800 hover:bg-black/10 dark:text-neutral-200 dark:hover:bg-white/10"
|
>
|
||||||
>
|
<ArrowRightIcon className="size-4" />
|
||||||
<PlusSquareIcon className="size-4" />
|
</button>
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => scrollNext()}
|
|
||||||
className="inline-flex items-center justify-center rounded-full size-7 text-neutral-800 hover:bg-black/10 dark:text-neutral-200 dark:hover:bg-white/10"
|
|
||||||
>
|
|
||||||
<ArrowRightIcon className="size-4" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,49 +1,83 @@
|
|||||||
import { User } from "@/components/user";
|
import { User } from "@/components/user";
|
||||||
import { ComposeFilledIcon, HorizontalDotsIcon, PlusIcon } from "@lume/icons";
|
import {
|
||||||
|
ChevronDownIcon,
|
||||||
|
ComposeFilledIcon,
|
||||||
|
PlusIcon,
|
||||||
|
SearchIcon,
|
||||||
|
} from "@lume/icons";
|
||||||
import { LumeWindow, NostrAccount } from "@lume/system";
|
import { LumeWindow, NostrAccount } from "@lume/system";
|
||||||
import { cn } from "@lume/utils";
|
import { cn } from "@lume/utils";
|
||||||
import * as Popover from "@radix-ui/react-popover";
|
|
||||||
import { Outlet, createFileRoute } from "@tanstack/react-router";
|
import { Outlet, createFileRoute } from "@tanstack/react-router";
|
||||||
import { Link } from "@tanstack/react-router";
|
import { Menu, MenuItem, PredefinedMenuItem } from "@tauri-apps/api/menu";
|
||||||
import { Menu, MenuItem } from "@tauri-apps/api/menu";
|
|
||||||
import { getCurrent } from "@tauri-apps/api/window";
|
import { getCurrent } from "@tauri-apps/api/window";
|
||||||
import { message } from "@tauri-apps/plugin-dialog";
|
import { message } from "@tauri-apps/plugin-dialog";
|
||||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
import { memo, useCallback, useState } from "react";
|
||||||
|
|
||||||
export const Route = createFileRoute("/$account")({
|
export const Route = createFileRoute("/$account")({
|
||||||
beforeLoad: async () => {
|
beforeLoad: async ({ params }) => {
|
||||||
const accounts = await NostrAccount.getAccounts();
|
const accounts = await NostrAccount.getAccounts();
|
||||||
return { accounts };
|
const otherAccounts = accounts.filter(
|
||||||
|
(account) => account !== params.account,
|
||||||
|
);
|
||||||
|
|
||||||
|
return { otherAccounts };
|
||||||
},
|
},
|
||||||
component: Screen,
|
component: Screen,
|
||||||
});
|
});
|
||||||
|
|
||||||
function Screen() {
|
function Screen() {
|
||||||
|
const { platform } = Route.useRouteContext();
|
||||||
|
|
||||||
|
const openLumeStore = async () => {
|
||||||
|
await getCurrent().emit("columns", {
|
||||||
|
type: "add",
|
||||||
|
column: {
|
||||||
|
label: "store",
|
||||||
|
name: "Store",
|
||||||
|
content: "/store/official",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col w-screen h-screen">
|
<div className="flex flex-col w-screen h-screen">
|
||||||
<div
|
<div
|
||||||
data-tauri-drag-region
|
data-tauri-drag-region
|
||||||
className="flex h-11 shrink-0 items-center justify-between pr-2 ml-2 pl-20"
|
className="flex h-11 shrink-0 items-center justify-between px-3"
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-3">
|
<div
|
||||||
<Accounts />
|
data-tauri-drag-region
|
||||||
<Link
|
className={cn(
|
||||||
to="/landing"
|
"flex-1 flex items-center gap-2",
|
||||||
className="inline-flex items-center justify-center rounded-full size-8 shrink-0 bg-black/10 text-neutral-800 hover:bg-black/20 dark:bg-white/10 dark:text-neutral-200 dark:hover:bg-white/20"
|
platform === "macos" ? "pl-[64px]" : "",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => openLumeStore()}
|
||||||
|
className="inline-flex items-center justify-center gap-0.5 rounded-full text-sm font-medium h-8 w-max pl-1.5 pr-3 bg-black/5 dark:bg-white/5 hover:bg-black/10 dark:hover:bg-white/10"
|
||||||
>
|
>
|
||||||
<PlusIcon className="size-5" />
|
<PlusIcon className="size-5" />
|
||||||
</Link>
|
Column
|
||||||
|
</button>
|
||||||
|
<div id="toolbar" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div data-tauri-drag-region className="hidden md:flex md:flex-1">
|
||||||
|
<Search />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
data-tauri-drag-region
|
||||||
|
className="flex-1 flex items-center justify-end gap-3"
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => LumeWindow.openEditor()}
|
onClick={() => LumeWindow.openEditor()}
|
||||||
className="inline-flex items-center justify-center h-8 gap-1 px-3 text-sm font-medium text-white bg-blue-500 rounded-full w-max hover:bg-blue-600"
|
className="inline-flex items-center justify-center h-8 gap-1 px-3 text-sm font-medium bg-black/5 dark:bg-white/5 rounded-full w-max hover:bg-blue-500 hover:text-white"
|
||||||
>
|
>
|
||||||
<ComposeFilledIcon className="size-4" />
|
<ComposeFilledIcon className="size-4" />
|
||||||
New Post
|
New Post
|
||||||
</button>
|
</button>
|
||||||
<div id="toolbar" />
|
<Accounts />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
@@ -53,34 +87,25 @@ function Screen() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function Accounts() {
|
const Accounts = memo(function Accounts() {
|
||||||
const navigate = Route.useNavigate();
|
const { otherAccounts } = Route.useRouteContext();
|
||||||
const { accounts } = Route.useRouteContext();
|
|
||||||
const { account } = Route.useParams();
|
const { account } = Route.useParams();
|
||||||
|
|
||||||
const [windowWidth, setWindowWidth] = useState<number>(null);
|
const navigate = Route.useNavigate();
|
||||||
|
|
||||||
const sortedList = useMemo(() => {
|
|
||||||
const list = accounts;
|
|
||||||
|
|
||||||
for (const [i, item] of list.entries()) {
|
|
||||||
if (item === account) {
|
|
||||||
list.splice(i, 1);
|
|
||||||
list.unshift(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return list;
|
|
||||||
}, [accounts]);
|
|
||||||
|
|
||||||
const showContextMenu = useCallback(
|
const showContextMenu = useCallback(
|
||||||
async (e: React.MouseEvent, npub: string) => {
|
async (e: React.MouseEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const menuItems = await Promise.all([
|
const menuItems = await Promise.all([
|
||||||
|
MenuItem.new({
|
||||||
|
text: "New Post",
|
||||||
|
action: () => LumeWindow.openEditor(),
|
||||||
|
}),
|
||||||
|
PredefinedMenuItem.new({ item: "Separator" }),
|
||||||
MenuItem.new({
|
MenuItem.new({
|
||||||
text: "View Profile",
|
text: "View Profile",
|
||||||
action: () => LumeWindow.openProfile(npub),
|
action: () => LumeWindow.openProfile(account),
|
||||||
}),
|
}),
|
||||||
MenuItem.new({
|
MenuItem.new({
|
||||||
text: "Open Settings",
|
text: "Open Settings",
|
||||||
@@ -94,112 +119,107 @@ function Accounts() {
|
|||||||
|
|
||||||
await menu.popup().catch((e) => console.error(e));
|
await menu.popup().catch((e) => console.error(e));
|
||||||
},
|
},
|
||||||
[],
|
[account],
|
||||||
);
|
);
|
||||||
|
|
||||||
const changeAccount = async (e: React.MouseEvent, npub: string) => {
|
const changeAccount = useCallback(
|
||||||
if (npub === account) {
|
async (npub: string) => {
|
||||||
return showContextMenu(e, npub);
|
// Change current account and update signer
|
||||||
}
|
const select = await NostrAccount.loadAccount(npub);
|
||||||
|
|
||||||
// Change current account and update signer
|
if (select) {
|
||||||
const select = await NostrAccount.loadAccount(npub);
|
// Reset current columns
|
||||||
|
await getCurrent().emit("columns", { type: "reset" });
|
||||||
|
|
||||||
if (select) {
|
// Redirect to new account
|
||||||
// Reset current columns
|
return navigate({
|
||||||
await getCurrent().emit("columns", { type: "reset" });
|
to: "/$account/home",
|
||||||
|
params: { account: npub },
|
||||||
|
resetScroll: true,
|
||||||
|
replace: true,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await message("Something wrong.", { title: "Accounts", kind: "error" });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[otherAccounts],
|
||||||
|
);
|
||||||
|
|
||||||
// Redirect to new account
|
return (
|
||||||
return navigate({
|
<div data-tauri-drag-region className="hidden md:flex items-center gap-3">
|
||||||
to: "/$account/home",
|
{otherAccounts.map((npub) => (
|
||||||
params: { account: npub },
|
<button key={npub} type="button" onClick={(e) => changeAccount(npub)}>
|
||||||
resetScroll: true,
|
<User.Provider pubkey={npub}>
|
||||||
replace: true,
|
<User.Root className="shrink-0 rounded-full transition-all ease-in-out duration-150 will-change-auto hover:ring-1 hover:ring-blue-500">
|
||||||
});
|
<User.Avatar className="rounded-full size-8" />
|
||||||
} else {
|
</User.Root>
|
||||||
await message("Something wrong.", { title: "Accounts", kind: "error" });
|
</User.Provider>
|
||||||
}
|
</button>
|
||||||
};
|
))}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={(e) => showContextMenu(e)}
|
||||||
|
className="inline-flex items-center gap-1.5"
|
||||||
|
>
|
||||||
|
<User.Provider pubkey={account}>
|
||||||
|
<User.Root className="shrink-0 rounded-full">
|
||||||
|
<User.Avatar className="rounded-full size-8" />
|
||||||
|
</User.Root>
|
||||||
|
</User.Provider>
|
||||||
|
<ChevronDownIcon className="size-3" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
const getWindowDimensions = () => {
|
const Search = memo(function Search() {
|
||||||
const { innerWidth: width, innerHeight: height } = window;
|
const [searchType, setSearchType] = useState<"notes" | "users">("notes");
|
||||||
return {
|
const [query, setQuery] = useState("");
|
||||||
width,
|
|
||||||
height,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
const showContextMenu = useCallback(async (e: React.MouseEvent) => {
|
||||||
function handleResize() {
|
e.preventDefault();
|
||||||
setWindowWidth(getWindowDimensions().width);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!windowWidth) {
|
const menuItems = await Promise.all([
|
||||||
setWindowWidth(getWindowDimensions().width);
|
MenuItem.new({
|
||||||
}
|
text: "Notes",
|
||||||
|
action: () => setSearchType("notes"),
|
||||||
|
}),
|
||||||
|
MenuItem.new({
|
||||||
|
text: "Users",
|
||||||
|
action: () => setSearchType("users"),
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
window.addEventListener("resize", handleResize);
|
const menu = await Menu.new({
|
||||||
|
items: menuItems,
|
||||||
|
});
|
||||||
|
|
||||||
return () => {
|
await menu.popup().catch((e) => console.error(e));
|
||||||
window.removeEventListener("resize", handleResize);
|
|
||||||
};
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-tauri-drag-region className="flex items-center gap-3">
|
<div className="h-8 w-full px-3 text-sm rounded-full inline-flex items-center bg-black/5 dark:bg-white/5">
|
||||||
{sortedList
|
<button
|
||||||
.slice(0, windowWidth > 500 ? account.length : 2)
|
type="button"
|
||||||
.map((user) => (
|
onClick={(e) => showContextMenu(e)}
|
||||||
<button
|
className="inline-flex items-center gap-1 capitalize text-sm font-medium pr-2 border-r border-black/10 dark:border-white/10"
|
||||||
key={user}
|
>
|
||||||
type="button"
|
{searchType}
|
||||||
onClick={(e) => changeAccount(e, user)}
|
<ChevronDownIcon className="size-3 text-black/50 dark:text-white/50" />
|
||||||
>
|
</button>
|
||||||
<User.Provider pubkey={user}>
|
<input
|
||||||
<User.Root
|
type="text"
|
||||||
className={cn(
|
name="search"
|
||||||
"shrink-0 rounded-full transition-all ease-in-out duration-150 will-change-auto",
|
placeholder="Search..."
|
||||||
user === account
|
onChange={(e) => setQuery(e.target.value)}
|
||||||
? "ring-1 ring-teal-500 ring-offset-2 ring-offset-neutral-200 dark:ring-offset-neutral-950"
|
onKeyDown={(event) => {
|
||||||
: "",
|
if (event.key === "Enter") {
|
||||||
)}
|
LumeWindow.openSearch(searchType, query);
|
||||||
>
|
}
|
||||||
<User.Avatar
|
}}
|
||||||
className={cn(
|
className="h-full w-full px-3 text-sm rounded-full border-none ring-0 focus:ring-0 focus:outline-none bg-transparent placeholder:text-black/50 dark:placeholder:text-black/50"
|
||||||
"aspect-square h-auto rounded-full object-cover transition-all ease-in-out duration-150 will-change-auto",
|
/>
|
||||||
user === account ? "w-7" : "w-8",
|
<SearchIcon className="size-5" />
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</User.Root>
|
|
||||||
</User.Provider>
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
{accounts.length >= 3 && windowWidth <= 700 ? (
|
|
||||||
<Popover.Root>
|
|
||||||
<Popover.Trigger className="inline-flex items-center justify-center rounded-full size-8 shrink-0 bg-black/10 text-neutral-800 hover:bg-black/20 dark:bg-white/10 dark:text-neutral-200 dark:hover:bg-white/20">
|
|
||||||
<HorizontalDotsIcon className="size-5" />
|
|
||||||
</Popover.Trigger>
|
|
||||||
<Popover.Portal>
|
|
||||||
<Popover.Content className="flex h-11 select-none items-center justify-center rounded-md bg-black/20 p-1 will-change-[transform,opacity] data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade">
|
|
||||||
{sortedList.slice(2).map((user) => (
|
|
||||||
<button
|
|
||||||
key={user}
|
|
||||||
type="button"
|
|
||||||
onClick={(e) => changeAccount(e, user)}
|
|
||||||
className="inline-flex items-center justify-center rounded-md size-9 hover:bg-white/10"
|
|
||||||
>
|
|
||||||
<User.Provider pubkey={user}>
|
|
||||||
<User.Root className="rounded-full ring-1 ring-white/10">
|
|
||||||
<User.Avatar className="object-cover h-auto rounded-full size-7 aspect-square" />
|
|
||||||
</User.Root>
|
|
||||||
</User.Provider>
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
<Popover.Arrow className="fill-neutral-950 dark:fill-neutral-50" />
|
|
||||||
</Popover.Content>
|
|
||||||
</Popover.Portal>
|
|
||||||
</Popover.Root>
|
|
||||||
) : null}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ function Screen() {
|
|||||||
>
|
>
|
||||||
<User.Provider pubkey={item}>
|
<User.Provider pubkey={item}>
|
||||||
<User.Root className="flex items-center gap-2.5">
|
<User.Root className="flex items-center gap-2.5">
|
||||||
<User.Avatar className="object-cover rounded-full size-8" />
|
<User.Avatar className="rounded-full size-8" />
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<User.Name className="text-sm font-medium" />
|
<User.Name className="text-sm font-medium" />
|
||||||
</div>
|
</div>
|
||||||
@@ -157,7 +157,7 @@ function Screen() {
|
|||||||
>
|
>
|
||||||
<User.Provider pubkey={item}>
|
<User.Provider pubkey={item}>
|
||||||
<User.Root className="flex items-center gap-2.5">
|
<User.Root className="flex items-center gap-2.5">
|
||||||
<User.Avatar className="object-cover rounded-full size-8" />
|
<User.Avatar className="rounded-full size-8" />
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<User.Name className="text-sm font-medium" />
|
<User.Name className="text-sm font-medium" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ function Screen() {
|
|||||||
<div className="flex flex-col w-full h-full gap-2">
|
<div className="flex flex-col w-full h-full gap-2">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<User.Avatar className="object-cover rounded-full size-7 shrink-0" />
|
<User.Avatar className="rounded-full size-7" />
|
||||||
<User.Name className="text-sm leadning-tight max-w-[15rem] truncate font-semibold" />
|
<User.Name className="text-sm leadning-tight max-w-[15rem] truncate font-semibold" />
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
|
import { Note } from "@/components/note";
|
||||||
|
import { MentionNote } from "@/components/note/mentions/note";
|
||||||
|
import { User } from "@/components/user";
|
||||||
import { ComposeFilledIcon } from "@lume/icons";
|
import { ComposeFilledIcon } from "@lume/icons";
|
||||||
|
import { LumeEvent, useEvent } from "@lume/system";
|
||||||
import { Spinner } from "@lume/ui";
|
import { Spinner } from "@lume/ui";
|
||||||
import { cn, insertImage, insertNostrEvent, isImageUrl } from "@lume/utils";
|
import { cn, insertImage, insertNostrEvent, isImageUrl } from "@lume/utils";
|
||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
import { nip19 } from "nostr-tools";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { type Descendant, Node, Transforms, createEditor } from "slate";
|
import { type Descendant, Node, Transforms, createEditor } from "slate";
|
||||||
import {
|
import {
|
||||||
@@ -14,13 +19,8 @@ import {
|
|||||||
withReact,
|
withReact,
|
||||||
} from "slate-react";
|
} from "slate-react";
|
||||||
import { MediaButton } from "./-components/media";
|
import { MediaButton } from "./-components/media";
|
||||||
import { LumeEvent, useEvent } from "@lume/system";
|
|
||||||
import { WarningButton } from "./-components/warning";
|
|
||||||
import { MentionNote } from "@/components/note/mentions/note";
|
|
||||||
import { PowButton } from "./-components/pow";
|
import { PowButton } from "./-components/pow";
|
||||||
import { User } from "@/components/user";
|
import { WarningButton } from "./-components/warning";
|
||||||
import { Note } from "@/components/note";
|
|
||||||
import { nip19 } from "nostr-tools";
|
|
||||||
|
|
||||||
type EditorSearch = {
|
type EditorSearch = {
|
||||||
reply_to: string;
|
reply_to: string;
|
||||||
@@ -250,7 +250,7 @@ function ChildNote({ id }: { id: string }) {
|
|||||||
<Note.Root className="flex items-center gap-2">
|
<Note.Root className="flex items-center gap-2">
|
||||||
<User.Provider pubkey={data.pubkey}>
|
<User.Provider pubkey={data.pubkey}>
|
||||||
<User.Root className="shrink-0">
|
<User.Root className="shrink-0">
|
||||||
<User.Avatar className="rounded-full size-8 shrink-0" />
|
<User.Avatar className="rounded-full size-8" />
|
||||||
</User.Root>
|
</User.Root>
|
||||||
</User.Provider>
|
</User.Provider>
|
||||||
<div className="content-break line-clamp-1">{data.content}</div>
|
<div className="content-break line-clamp-1">{data.content}</div>
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ function Screen() {
|
|||||||
>
|
>
|
||||||
<User.Provider pubkey={account}>
|
<User.Provider pubkey={account}>
|
||||||
<User.Root className="flex items-center gap-2.5 p-3">
|
<User.Root className="flex items-center gap-2.5 p-3">
|
||||||
<User.Avatar className="object-cover rounded-full size-10 shrink-0" />
|
<User.Avatar className="rounded-full size-10" />
|
||||||
<div className="inline-flex flex-col items-start">
|
<div className="inline-flex flex-col items-start">
|
||||||
<User.Name className="max-w-[6rem] truncate font-medium leading-tight" />
|
<User.Name className="max-w-[6rem] truncate font-medium leading-tight" />
|
||||||
<span className="text-sm text-neutral-700 dark:text-neutral-300">
|
<span className="text-sm text-neutral-700 dark:text-neutral-300">
|
||||||
|
|||||||
@@ -2,16 +2,23 @@ import { Conversation } from "@/components/conversation";
|
|||||||
import { Quote } from "@/components/quote";
|
import { Quote } from "@/components/quote";
|
||||||
import { RepostNote } from "@/components/repost";
|
import { RepostNote } from "@/components/repost";
|
||||||
import { TextNote } from "@/components/text";
|
import { TextNote } from "@/components/text";
|
||||||
import { ArrowRightCircleIcon } from "@lume/icons";
|
import { ArrowRightCircleIcon, ArrowUpIcon } from "@lume/icons";
|
||||||
import { type LumeEvent, NostrAccount, NostrQuery } from "@lume/system";
|
import { LumeEvent, NostrAccount, NostrQuery } from "@lume/system";
|
||||||
import { type ColumnRouteSearch, Kind } from "@lume/types";
|
import { type ColumnRouteSearch, Kind, type Meta } from "@lume/types";
|
||||||
import { Spinner } from "@lume/ui";
|
import { Spinner } from "@lume/ui";
|
||||||
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
||||||
import { useInfiniteQuery } from "@tanstack/react-query";
|
import { type InfiniteData, useInfiniteQuery } from "@tanstack/react-query";
|
||||||
import { createFileRoute, redirect } from "@tanstack/react-router";
|
import { createFileRoute, redirect } from "@tanstack/react-router";
|
||||||
import { useCallback, useRef } from "react";
|
import { listen } from "@tauri-apps/api/event";
|
||||||
|
import { getCurrent } from "@tauri-apps/api/window";
|
||||||
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import { Virtualizer } from "virtua";
|
import { Virtualizer } from "virtua";
|
||||||
|
|
||||||
|
type Payload = {
|
||||||
|
raw: string;
|
||||||
|
parsed: Meta;
|
||||||
|
};
|
||||||
|
|
||||||
export const Route = createFileRoute("/newsfeed")({
|
export const Route = createFileRoute("/newsfeed")({
|
||||||
validateSearch: (search: Record<string, string>): ColumnRouteSearch => {
|
validateSearch: (search: Record<string, string>): ColumnRouteSearch => {
|
||||||
return {
|
return {
|
||||||
@@ -40,6 +47,7 @@ export const Route = createFileRoute("/newsfeed")({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export function Screen() {
|
export function Screen() {
|
||||||
|
const { queryClient } = Route.useRouteContext();
|
||||||
const { label, account } = Route.useSearch();
|
const { label, account } = Route.useSearch();
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
@@ -84,20 +92,31 @@ export function Screen() {
|
|||||||
[data],
|
[data],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const unlisten = listen("synced", async () => {
|
||||||
|
await queryClient.invalidateQueries({ queryKey: [label, account] });
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
unlisten.then((f) => f());
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollArea.Root
|
<ScrollArea.Root
|
||||||
type={"scroll"}
|
type={"scroll"}
|
||||||
scrollHideDelay={300}
|
scrollHideDelay={300}
|
||||||
className="overflow-hidden size-full"
|
className="overflow-hidden size-full"
|
||||||
>
|
>
|
||||||
<ScrollArea.Viewport ref={ref} className="h-full px-3 pb-3">
|
<ScrollArea.Viewport ref={ref} className="relative h-full px-3 pb-3">
|
||||||
|
<Listerner />
|
||||||
<Virtualizer scrollRef={ref}>
|
<Virtualizer scrollRef={ref}>
|
||||||
{isFetching && !isLoading && !isFetchingNextPage ? (
|
{isFetching && !isLoading && !isFetchingNextPage ? (
|
||||||
<div className="flex items-center justify-center w-full mb-3 h-11 bg-black/10 dark:bg-white/10 backdrop-blur-lg rounded-xl shadow-primary dark:ring-1 ring-neutral-800/50">
|
<div className="flex items-center justify-center w-full mb-3 h-12 bg-black/10 dark:bg-white/10 backdrop-blur-lg rounded-xl shadow-primary dark:ring-1 ring-neutral-800/50">
|
||||||
<div className="flex items-center justify-center gap-2">
|
<div className="flex items-center justify-center gap-2">
|
||||||
<Spinner className="size-5" />
|
<Spinner className="size-5" />
|
||||||
<span className="text-sm font-medium">
|
<span className="text-sm font-medium">
|
||||||
Fetching new notes...
|
Getting new notes...
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -145,3 +164,69 @@ export function Screen() {
|
|||||||
</ScrollArea.Root>
|
</ScrollArea.Root>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Listerner() {
|
||||||
|
const { queryClient } = Route.useRouteContext();
|
||||||
|
const { label, account } = Route.useSearch();
|
||||||
|
|
||||||
|
const [events, setEvents] = useState<LumeEvent[]>([]);
|
||||||
|
|
||||||
|
const pushNewEvents = async () => {
|
||||||
|
await queryClient.setQueryData(
|
||||||
|
[label, account],
|
||||||
|
(oldData: InfiniteData<LumeEvent[], number> | undefined) => {
|
||||||
|
const firstPage = oldData?.pages[0];
|
||||||
|
|
||||||
|
if (firstPage) {
|
||||||
|
return {
|
||||||
|
...oldData,
|
||||||
|
pages: [
|
||||||
|
{
|
||||||
|
...firstPage,
|
||||||
|
posts: [...events, ...firstPage],
|
||||||
|
},
|
||||||
|
...oldData.pages.slice(1),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryClient.invalidateQueries({ queryKey: [label, account] });
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const unlistenEvent = getCurrent().listen<Payload>("new_event", (data) => {
|
||||||
|
const event = LumeEvent.from(data.payload.raw, data.payload.parsed);
|
||||||
|
setEvents((prev) => [event, ...prev]);
|
||||||
|
});
|
||||||
|
|
||||||
|
const unlistenWindow = getCurrent().onCloseRequested(async () => {
|
||||||
|
await NostrQuery.unlisten();
|
||||||
|
await getCurrent().destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Listen for new event
|
||||||
|
NostrQuery.listenLocalEvent().then(() => console.log("listen"));
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
unlistenEvent.then((f) => f());
|
||||||
|
unlistenWindow.then((f) => f());
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!events?.length) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="z-50 fixed top-0 left-0 w-full h-14 flex items-center justify-center px-3">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => pushNewEvents()}
|
||||||
|
className="w-max h-8 pl-2 pr-3 inline-flex items-center justify-center gap-1.5 rounded-full shadow-lg text-sm font-medium text-white bg-black dark:text-black dark:bg-white"
|
||||||
|
>
|
||||||
|
<ArrowUpIcon className="size-4" />
|
||||||
|
{events.length} new notes
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,52 +0,0 @@
|
|||||||
import { ZapIcon } from "@lume/icons";
|
|
||||||
import { NostrAccount } from "@lume/system";
|
|
||||||
import { Container } from "@lume/ui";
|
|
||||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
|
||||||
import { useState } from "react";
|
|
||||||
|
|
||||||
export const Route = createLazyFileRoute("/nwc")({
|
|
||||||
component: Screen,
|
|
||||||
});
|
|
||||||
|
|
||||||
function Screen() {
|
|
||||||
const [uri, setUri] = useState("");
|
|
||||||
const [isDone, setIsDone] = useState(false);
|
|
||||||
|
|
||||||
const save = async () => {
|
|
||||||
const nwc = await NostrAccount.setWallet(uri);
|
|
||||||
setIsDone(nwc);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Container withDrag>
|
|
||||||
<div className="flex-1 w-full h-full px-5">
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<div>
|
|
||||||
<h3 className="text-2xl font-light">
|
|
||||||
Connect <span className="font-semibold">bitcoin wallet</span> to
|
|
||||||
start zapping to your favorite content and creator.
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col gap-2 mt-10">
|
|
||||||
<div className="flex flex-col gap-1.5">
|
|
||||||
<label>Paste a Nostr Wallet Connect connection string</label>
|
|
||||||
<textarea
|
|
||||||
value={uri}
|
|
||||||
onChange={(e) => setUri(e.target.value)}
|
|
||||||
placeholder="nostrconnect://"
|
|
||||||
className="w-full h-24 px-3 bg-transparent rounded-lg border-neutral-300 placeholder:text-neutral-500 focus:border-blue-500 focus:ring focus:ring-blue-200 dark:border-neutral-700 dark:placeholder:text-neutral-400 dark:focus:ring-blue-800"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={save}
|
|
||||||
className="inline-flex h-11 w-full items-center justify-center gap-1.5 rounded-lg bg-blue-500 px-5 font-medium text-white hover:bg-blue-600"
|
|
||||||
>
|
|
||||||
Save & Connect
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Container>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -42,50 +42,41 @@ function Screen() {
|
|||||||
const [account, setAccount] = useState<string>(null);
|
const [account, setAccount] = useState<string>(null);
|
||||||
const [events, setEvents] = useState<LumeEvent[]>([]);
|
const [events, setEvents] = useState<LumeEvent[]>([]);
|
||||||
|
|
||||||
const texts = useMemo(
|
const { texts, zaps, reactions } = useMemo(() => {
|
||||||
() => events.filter((ev) => ev.kind === Kind.Text),
|
const zaps = new Map<string, LumeEvent[]>();
|
||||||
[events],
|
const reactions = new Map<string, LumeEvent[]>();
|
||||||
);
|
const texts = events.filter((ev) => ev.kind === Kind.Text);
|
||||||
|
const zapEvents = events.filter((ev) => ev.kind === Kind.ZapReceipt);
|
||||||
const zaps = useMemo(() => {
|
const reactEvents = events.filter(
|
||||||
const groups = new Map<string, LumeEvent[]>();
|
|
||||||
const list = events.filter((ev) => ev.kind === Kind.ZapReceipt);
|
|
||||||
|
|
||||||
for (const event of list) {
|
|
||||||
const rootId = event.tags.filter((tag) => tag[0] === "e")[0]?.[1];
|
|
||||||
|
|
||||||
if (rootId) {
|
|
||||||
if (groups.has(rootId)) {
|
|
||||||
groups.get(rootId).push(event);
|
|
||||||
} else {
|
|
||||||
groups.set(rootId, [event]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return groups;
|
|
||||||
}, [events]);
|
|
||||||
|
|
||||||
const reactions = useMemo(() => {
|
|
||||||
const groups = new Map<string, LumeEvent[]>();
|
|
||||||
const list = events.filter(
|
|
||||||
(ev) => ev.kind === Kind.Repost || ev.kind === Kind.Reaction,
|
(ev) => ev.kind === Kind.Repost || ev.kind === Kind.Reaction,
|
||||||
);
|
);
|
||||||
|
|
||||||
for (const event of list) {
|
for (const event of reactEvents) {
|
||||||
const rootId = event.tags.filter((tag) => tag[0] === "e")[0]?.[1];
|
const rootId = event.tags.filter((tag) => tag[0] === "e")[0]?.[1];
|
||||||
|
|
||||||
if (rootId) {
|
if (rootId) {
|
||||||
if (groups.has(rootId)) {
|
if (reactions.has(rootId)) {
|
||||||
groups.get(rootId).push(event);
|
reactions.get(rootId).push(event);
|
||||||
} else {
|
} else {
|
||||||
groups.set(rootId, [event]);
|
reactions.set(rootId, [event]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return groups;
|
for (const event of zapEvents) {
|
||||||
}, [events]);
|
const rootId = event.tags.filter((tag) => tag[0] === "e")[0]?.[1];
|
||||||
|
|
||||||
|
if (rootId) {
|
||||||
|
if (zaps.has(rootId)) {
|
||||||
|
zaps.get(rootId).push(event);
|
||||||
|
} else {
|
||||||
|
zaps.set(rootId, [event]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { texts, zaps, reactions };
|
||||||
|
}, [events?.length]);
|
||||||
|
|
||||||
const showContextMenu = useCallback(async (e: React.MouseEvent) => {
|
const showContextMenu = useCallback(async (e: React.MouseEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -99,10 +90,6 @@ function Screen() {
|
|||||||
text: "New Post",
|
text: "New Post",
|
||||||
action: () => LumeWindow.openEditor(),
|
action: () => LumeWindow.openEditor(),
|
||||||
}),
|
}),
|
||||||
MenuItem.new({
|
|
||||||
text: "Search",
|
|
||||||
action: () => LumeWindow.openSearch(),
|
|
||||||
}),
|
|
||||||
PredefinedMenuItem.new({ item: "Separator" }),
|
PredefinedMenuItem.new({ item: "Separator" }),
|
||||||
MenuItem.new({
|
MenuItem.new({
|
||||||
text: "About Lume",
|
text: "About Lume",
|
||||||
@@ -180,13 +167,6 @@ function Screen() {
|
|||||||
<User.Avatar className="rounded-full size-7" />
|
<User.Avatar className="rounded-full size-7" />
|
||||||
</User.Root>
|
</User.Root>
|
||||||
</User.Provider>
|
</User.Provider>
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => LumeWindow.openSearch()}
|
|
||||||
className="inline-flex items-center justify-center rounded-full size-7 bg-black/5 dark:bg-white/5"
|
|
||||||
>
|
|
||||||
<SearchIcon className="size-4" />
|
|
||||||
</button>
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={(e) => showContextMenu(e)}
|
onClick={(e) => showContextMenu(e)}
|
||||||
@@ -351,7 +331,7 @@ function RootNote({ id }: { id: string }) {
|
|||||||
<Note.Root className="flex items-center gap-2">
|
<Note.Root className="flex items-center gap-2">
|
||||||
<User.Provider pubkey={data.pubkey}>
|
<User.Provider pubkey={data.pubkey}>
|
||||||
<User.Root className="shrink-0">
|
<User.Root className="shrink-0">
|
||||||
<User.Avatar className="rounded-full size-8 shrink-0" />
|
<User.Avatar className="rounded-full size-8" />
|
||||||
</User.Root>
|
</User.Root>
|
||||||
</User.Provider>
|
</User.Provider>
|
||||||
<div className="line-clamp-1">{data.content}</div>
|
<div className="line-clamp-1">{data.content}</div>
|
||||||
@@ -371,7 +351,7 @@ function TextNote({ event }: { event: LumeEvent }) {
|
|||||||
<Note.Root className="flex flex-col p-2 mb-2 rounded-lg shrink-0 backdrop-blur-md bg-black/10 dark:bg-white/10">
|
<Note.Root className="flex flex-col p-2 mb-2 rounded-lg shrink-0 backdrop-blur-md bg-black/10 dark:bg-white/10">
|
||||||
<User.Provider pubkey={event.pubkey}>
|
<User.Provider pubkey={event.pubkey}>
|
||||||
<User.Root className="inline-flex items-center gap-2">
|
<User.Root className="inline-flex items-center gap-2">
|
||||||
<User.Avatar className="rounded-full size-9 shrink-0" />
|
<User.Avatar className="rounded-full size-9" />
|
||||||
<div className="flex flex-col flex-1">
|
<div className="flex flex-col flex-1">
|
||||||
<div className="flex items-baseline justify-between w-full">
|
<div className="flex items-baseline justify-between w-full">
|
||||||
<User.Name className="text-sm font-semibold leading-tight" />
|
<User.Name className="text-sm font-semibold leading-tight" />
|
||||||
|
|||||||
96
apps/desktop2/src/routes/search.notes.tsx
Normal file
96
apps/desktop2/src/routes/search.notes.tsx
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
import { Conversation } from "@/components/conversation";
|
||||||
|
import { Quote } from "@/components/quote";
|
||||||
|
import { RepostNote } from "@/components/repost";
|
||||||
|
import { TextNote } from "@/components/text";
|
||||||
|
import { LumeEvent, NostrQuery } from "@lume/system";
|
||||||
|
import { Kind, type NostrEvent } from "@lume/types";
|
||||||
|
import { Spinner } from "@lume/ui";
|
||||||
|
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
import { fetch } from "@tauri-apps/plugin-http";
|
||||||
|
import { useCallback, useRef } from "react";
|
||||||
|
import { Virtualizer } from "virtua";
|
||||||
|
|
||||||
|
type Search = {
|
||||||
|
query: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/search/notes")({
|
||||||
|
validateSearch: (search: Record<string, string>): Search => {
|
||||||
|
return {
|
||||||
|
query: search.query,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
beforeLoad: async () => {
|
||||||
|
const settings = await NostrQuery.getUserSettings();
|
||||||
|
return { settings };
|
||||||
|
},
|
||||||
|
component: Screen,
|
||||||
|
});
|
||||||
|
|
||||||
|
function Screen() {
|
||||||
|
const { query } = Route.useSearch();
|
||||||
|
const { isLoading, data } = useQuery({
|
||||||
|
queryKey: ["search", query],
|
||||||
|
queryFn: async () => {
|
||||||
|
try {
|
||||||
|
const res = await fetch(
|
||||||
|
`https://api.nostr.wine/search?query=${query}&kind=1&limit=50`,
|
||||||
|
);
|
||||||
|
const content = await res.json();
|
||||||
|
const events = content.data as NostrEvent[];
|
||||||
|
const lumeEvents = await Promise.all(
|
||||||
|
events.map(async (item): Promise<LumeEvent> => {
|
||||||
|
const event = await LumeEvent.build(item);
|
||||||
|
return event;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
return lumeEvents.sort((a, b) => b.created_at - a.created_at);
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const renderItem = useCallback(
|
||||||
|
(event: LumeEvent) => {
|
||||||
|
if (!event) return;
|
||||||
|
switch (event.kind) {
|
||||||
|
case Kind.Repost:
|
||||||
|
return <RepostNote key={event.id} event={event} className="mb-3" />;
|
||||||
|
default: {
|
||||||
|
if (event.isConversation) {
|
||||||
|
return (
|
||||||
|
<Conversation key={event.id} className="mb-3" event={event} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (event.isQuote) {
|
||||||
|
return <Quote key={event.id} event={event} className="mb-3" />;
|
||||||
|
}
|
||||||
|
return <TextNote key={event.id} event={event} className="mb-3" />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[data],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ScrollArea.Viewport ref={ref} className="h-full p-3">
|
||||||
|
<Virtualizer scrollRef={ref}>
|
||||||
|
{isLoading ? (
|
||||||
|
<div className="flex items-center justify-center w-full h-11 gap-2">
|
||||||
|
<Spinner className="size-5" />
|
||||||
|
<span className="text-sm font-medium">Searching...</span>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
data.map((item) => renderItem(item))
|
||||||
|
)}
|
||||||
|
</Virtualizer>
|
||||||
|
</ScrollArea.Viewport>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,148 +1,44 @@
|
|||||||
import { Note } from "@/components/note";
|
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
||||||
import { User } from "@/components/user";
|
import { Outlet, createFileRoute } from "@tanstack/react-router";
|
||||||
import { SearchIcon } from "@lume/icons";
|
|
||||||
import { LumeEvent, LumeWindow } from "@lume/system";
|
type Search = {
|
||||||
import { Kind, type NostrEvent } from "@lume/types";
|
query: string;
|
||||||
import { Spinner } from "@lume/ui";
|
};
|
||||||
import { createFileRoute } from "@tanstack/react-router";
|
|
||||||
import { message } from "@tauri-apps/plugin-dialog";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { useDebounce } from "use-debounce";
|
|
||||||
|
|
||||||
export const Route = createFileRoute("/search")({
|
export const Route = createFileRoute("/search")({
|
||||||
|
validateSearch: (search: Record<string, string>): Search => {
|
||||||
|
return {
|
||||||
|
query: search.query,
|
||||||
|
};
|
||||||
|
},
|
||||||
component: Screen,
|
component: Screen,
|
||||||
});
|
});
|
||||||
|
|
||||||
function Screen() {
|
function Screen() {
|
||||||
const [loading, setLoading] = useState(false);
|
const { query } = Route.useSearch();
|
||||||
const [events, setEvents] = useState<LumeEvent[]>([]);
|
|
||||||
const [search, setSearch] = useState("");
|
|
||||||
const [searchValue] = useDebounce(search, 500);
|
|
||||||
|
|
||||||
const searchEvents = async () => {
|
|
||||||
try {
|
|
||||||
setLoading(true);
|
|
||||||
|
|
||||||
const query = `https://api.nostr.wine/search?query=${searchValue}&kind=0,1`;
|
|
||||||
const res = await fetch(query);
|
|
||||||
const content = await res.json();
|
|
||||||
const events = content.data as NostrEvent[];
|
|
||||||
const lumeEvents = events.map((ev) => new LumeEvent(ev));
|
|
||||||
const sorted = lumeEvents.sort((a, b) => b.created_at - a.created_at);
|
|
||||||
|
|
||||||
setLoading(false);
|
|
||||||
setEvents(sorted);
|
|
||||||
} catch (e) {
|
|
||||||
setLoading(false);
|
|
||||||
await message(String(e), {
|
|
||||||
title: "Search",
|
|
||||||
kind: "error",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (searchValue.length >= 3 && searchValue.length < 500) {
|
|
||||||
searchEvents();
|
|
||||||
}
|
|
||||||
}, [searchValue]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-tauri-drag-region className="flex flex-col w-full h-full">
|
<div className="flex flex-col h-full">
|
||||||
<div className="relative flex flex-col h-24 border-b shrink-0 border-black/5 dark:border-white/5">
|
<div
|
||||||
<div data-tauri-drag-region className="w-full h-4 shrink-0" />
|
data-tauri-drag-region
|
||||||
<input
|
className="shrink-0 flex items-end gap-1 h-20 px-3 pb-3 w-full border-b border-black/10 dark:border-white/10"
|
||||||
value={search}
|
>
|
||||||
onChange={(e) => setSearch(e.target.value)}
|
Search result for: <span className="font-semibold">{query}</span>
|
||||||
onKeyDown={(e) => {
|
|
||||||
if (e.key === "Enter") searchEvents();
|
|
||||||
}}
|
|
||||||
placeholder="Search anything..."
|
|
||||||
className="w-full h-20 px-3 pt-10 text-lg bg-transparent border-none focus:outline-none focus:ring-0 placeholder:text-neutral-500 dark:placeholder:text-neutral-600"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex-1 p-3 overflow-y-auto scrollbar-none">
|
|
||||||
{loading ? (
|
|
||||||
<div className="flex items-center justify-center w-full h-full">
|
|
||||||
<Spinner />
|
|
||||||
</div>
|
|
||||||
) : events.length ? (
|
|
||||||
<div className="flex flex-col gap-5">
|
|
||||||
<div className="flex flex-col gap-1.5">
|
|
||||||
<div className="text-sm font-medium text-neutral-700 dark:text-neutral-300 shrink-0">
|
|
||||||
Users
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col flex-1 gap-1">
|
|
||||||
{events
|
|
||||||
.filter((ev) => ev.kind === Kind.Metadata)
|
|
||||||
.map((event) => (
|
|
||||||
<SearchUser key={event.pubkey} event={event} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col gap-1.5">
|
|
||||||
<div className="text-sm font-medium text-neutral-700 dark:text-neutral-300 shrink-0">
|
|
||||||
Notes
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col flex-1 gap-3">
|
|
||||||
{events
|
|
||||||
.filter((ev) => ev.kind === Kind.Text)
|
|
||||||
.map((event) => (
|
|
||||||
<SearchNote key={event.id} event={event} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
{!loading && !events.length ? (
|
|
||||||
<div className="flex flex-col items-center justify-center h-full gap-3">
|
|
||||||
<div className="inline-flex items-center justify-center rounded-full size-16 bg-black/10 dark:bg-white/10">
|
|
||||||
<SearchIcon className="size-6" />
|
|
||||||
</div>
|
|
||||||
Try searching for people, notes, or keywords
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<ScrollArea.Root
|
||||||
);
|
type={"scroll"}
|
||||||
}
|
scrollHideDelay={300}
|
||||||
|
className="overflow-hidden size-full flex-1"
|
||||||
function SearchUser({ event }: { event: LumeEvent }) {
|
>
|
||||||
return (
|
<Outlet />
|
||||||
<button
|
<ScrollArea.Scrollbar
|
||||||
key={event.id}
|
className="flex select-none touch-none p-0.5 duration-[160ms] ease-out data-[orientation=vertical]:w-2"
|
||||||
type="button"
|
orientation="vertical"
|
||||||
onClick={() => LumeWindow.openProfile(event.pubkey)}
|
>
|
||||||
className="col-span-1 p-2 rounded-lg hover:bg-black/10 dark:hover:bg-white/10"
|
<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>
|
||||||
<User.Provider pubkey={event.pubkey} embedProfile={event.content}>
|
<ScrollArea.Corner className="bg-transparent" />
|
||||||
<User.Root className="flex items-center gap-2">
|
</ScrollArea.Root>
|
||||||
<User.Avatar className="rounded-full size-9 shrink-0" />
|
|
||||||
<div className="inline-flex items-center gap-1.5">
|
|
||||||
<User.Name className="font-semibold" />
|
|
||||||
<User.NIP05 />
|
|
||||||
</div>
|
|
||||||
</User.Root>
|
|
||||||
</User.Provider>
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function SearchNote({ event }: { event: LumeEvent }) {
|
|
||||||
return (
|
|
||||||
<div className="bg-white dark:bg-black/20 backdrop-blur-lg rounded-xl shadow-primary dark:ring-1 ring-neutral-800/50">
|
|
||||||
<Note.Provider event={event}>
|
|
||||||
<Note.Root>
|
|
||||||
<div className="flex items-center justify-between px-3 h-14">
|
|
||||||
<Note.User />
|
|
||||||
<Note.Menu />
|
|
||||||
</div>
|
|
||||||
<Note.Content className="px-3" quote={false} mention={false} />
|
|
||||||
<div className="flex items-center gap-4 px-3 mt-3 h-14">
|
|
||||||
<Note.Open />
|
|
||||||
</div>
|
|
||||||
</Note.Root>
|
|
||||||
</Note.Provider>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
101
apps/desktop2/src/routes/search.users.tsx
Normal file
101
apps/desktop2/src/routes/search.users.tsx
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
import { User } from "@/components/user";
|
||||||
|
import { LumeWindow, NostrQuery } from "@lume/system";
|
||||||
|
import type { NostrEvent } from "@lume/types";
|
||||||
|
import { Spinner } from "@lume/ui";
|
||||||
|
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
import { fetch } from "@tauri-apps/plugin-http";
|
||||||
|
import { useRef } from "react";
|
||||||
|
import { Virtualizer } from "virtua";
|
||||||
|
|
||||||
|
type Search = {
|
||||||
|
query: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type UserItem = {
|
||||||
|
pubkey: string;
|
||||||
|
profile: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/search/users")({
|
||||||
|
validateSearch: (search: Record<string, string>): Search => {
|
||||||
|
return {
|
||||||
|
query: search.query,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
beforeLoad: async () => {
|
||||||
|
const settings = await NostrQuery.getUserSettings();
|
||||||
|
return { settings };
|
||||||
|
},
|
||||||
|
component: Screen,
|
||||||
|
});
|
||||||
|
|
||||||
|
function Screen() {
|
||||||
|
const { query } = Route.useSearch();
|
||||||
|
const { isLoading, data } = useQuery({
|
||||||
|
queryKey: ["search", query],
|
||||||
|
queryFn: async () => {
|
||||||
|
try {
|
||||||
|
const res = await fetch(
|
||||||
|
`https://api.nostr.wine/search?query=${query}&kind=0&limit=100`,
|
||||||
|
);
|
||||||
|
const content = await res.json();
|
||||||
|
const events = content.data as NostrEvent[];
|
||||||
|
const users: UserItem[] = events.map((ev) => ({
|
||||||
|
pubkey: ev.pubkey,
|
||||||
|
profile: ev.content,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return users;
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ScrollArea.Viewport ref={ref} className="h-full px-3 pt-3">
|
||||||
|
<Virtualizer scrollRef={ref}>
|
||||||
|
{isLoading ? (
|
||||||
|
<div className="flex items-center justify-center w-full h-11 gap-2">
|
||||||
|
<Spinner className="size-5" />
|
||||||
|
<span className="text-sm font-medium">Searching...</span>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
data.map((item) => (
|
||||||
|
<div
|
||||||
|
key={item.pubkey}
|
||||||
|
className="w-full p-3 mb-2 overflow-hidden bg-white rounded-lg h-max dark:bg-black/20 backdrop-blur-lg shadow-primary dark:ring-1 ring-neutral-800/50"
|
||||||
|
>
|
||||||
|
<User.Provider pubkey={item.pubkey} embedProfile={item.profile}>
|
||||||
|
<User.Root className="flex flex-col w-full h-full gap-2">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<User.Avatar className="rounded-full size-7" />
|
||||||
|
<div className="inline-flex items-center gap-1">
|
||||||
|
<User.Name className="text-sm leadning-tight max-w-[15rem] truncate font-semibold" />
|
||||||
|
<User.NIP05 />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => LumeWindow.openProfile(item.pubkey)}
|
||||||
|
className="inline-flex items-center justify-center w-16 text-sm font-medium rounded-md h-7 bg-black/10 hover:bg-black/20 dark:bg-white/10 dark:hover:bg-white/20"
|
||||||
|
>
|
||||||
|
View
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<User.About className="select-text line-clamp-3 max-w-none text-neutral-800 dark:text-neutral-400" />
|
||||||
|
</User.Root>
|
||||||
|
</User.Provider>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</Virtualizer>
|
||||||
|
</ScrollArea.Viewport>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -51,7 +51,7 @@ function Account({ account }: { account: string }) {
|
|||||||
<div className="flex items-center justify-between gap-2 py-3">
|
<div className="flex items-center justify-between gap-2 py-3">
|
||||||
<User.Provider pubkey={account}>
|
<User.Provider pubkey={account}>
|
||||||
<User.Root className="flex items-center gap-2">
|
<User.Root className="flex items-center gap-2">
|
||||||
<User.Avatar className="object-cover rounded-full size-8" />
|
<User.Avatar className="rounded-full size-8" />
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<User.Name className="text-sm leading-tight" />
|
<User.Name className="text-sm leading-tight" />
|
||||||
<span className="text-sm leading-tight text-black/50 dark:text-white/50">
|
<span className="text-sm leading-tight text-black/50 dark:text-white/50">
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Spinner } from "@lume/ui";
|
|
||||||
import { User } from "@/components/user";
|
import { User } from "@/components/user";
|
||||||
|
import { Spinner } from "@lume/ui";
|
||||||
import { Await, defer } from "@tanstack/react-router";
|
import { Await, defer } from "@tanstack/react-router";
|
||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
import { Suspense } from "react";
|
import { Suspense } from "react";
|
||||||
@@ -52,7 +52,7 @@ export function Screen() {
|
|||||||
<div className="flex h-full w-full flex-col gap-2">
|
<div className="flex h-full w-full flex-col gap-2">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2.5">
|
<div className="flex items-center gap-2.5">
|
||||||
<User.Avatar className="size-10 shrink-0 rounded-full object-cover" />
|
<User.Avatar className="size-10 rounded-full" />
|
||||||
<User.Name className="leadning-tight max-w-[15rem] truncate font-semibold" />
|
<User.Name className="leadning-tight max-w-[15rem] truncate font-semibold" />
|
||||||
</div>
|
</div>
|
||||||
<User.Button className="inline-flex h-8 w-20 items-center justify-center rounded-lg bg-black/10 text-sm font-medium hover:bg-black/20 dark:bg-white/10 dark:hover:bg-white/20" />
|
<User.Button className="inline-flex h-8 w-20 items-center justify-center rounded-lg bg-black/10 text-sm font-medium hover:bg-black/20 dark:bg-white/10 dark:hover:bg-white/20" />
|
||||||
|
|||||||
@@ -25,11 +25,11 @@
|
|||||||
"@tauri-apps/plugin-dialog": "2.0.0-beta.5",
|
"@tauri-apps/plugin-dialog": "2.0.0-beta.5",
|
||||||
"@tauri-apps/plugin-fs": "2.0.0-beta.5",
|
"@tauri-apps/plugin-fs": "2.0.0-beta.5",
|
||||||
"@tauri-apps/plugin-http": "2.0.0-beta.5",
|
"@tauri-apps/plugin-http": "2.0.0-beta.5",
|
||||||
"@tauri-apps/plugin-notification": "2.0.0-beta.5",
|
|
||||||
"@tauri-apps/plugin-os": "github:tauri-apps/tauri-plugin-os#v2",
|
"@tauri-apps/plugin-os": "github:tauri-apps/tauri-plugin-os#v2",
|
||||||
"@tauri-apps/plugin-process": "2.0.0-beta.5",
|
"@tauri-apps/plugin-process": "2.0.0-beta.5",
|
||||||
"@tauri-apps/plugin-shell": "2.0.0-beta.6",
|
"@tauri-apps/plugin-shell": "2.0.0-beta.6",
|
||||||
"@tauri-apps/plugin-updater": "2.0.0-beta.5",
|
"@tauri-apps/plugin-updater": "2.0.0-beta.5",
|
||||||
"@tauri-apps/plugin-upload": "2.0.0-beta.6"
|
"@tauri-apps/plugin-upload": "2.0.0-beta.6",
|
||||||
|
"@tauri-apps/plugin-window-state": "2.0.0-beta.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ try {
|
|||||||
else return { status: "error", error: e as any };
|
else return { status: "error", error: e as any };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async checkContact(hex: string) : Promise<Result<boolean, null>> {
|
async checkContact(hex: string) : Promise<Result<boolean, string>> {
|
||||||
try {
|
try {
|
||||||
return { status: "ok", data: await TAURI_INVOKE("check_contact", { hex }) };
|
return { status: "ok", data: await TAURI_INVOKE("check_contact", { hex }) };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -300,14 +300,6 @@ try {
|
|||||||
else return { status: "error", error: e as any };
|
else return { status: "error", error: e as any };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async unlistenEventReply(id: string) : Promise<Result<null, null>> {
|
|
||||||
try {
|
|
||||||
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 getEventsBy(publicKey: string, asOf: string | null) : Promise<Result<RichEvent[], string>> {
|
async getEventsBy(publicKey: string, asOf: string | null) : Promise<Result<RichEvent[], string>> {
|
||||||
try {
|
try {
|
||||||
return { status: "ok", data: await TAURI_INVOKE("get_events_by", { publicKey, asOf }) };
|
return { status: "ok", data: await TAURI_INVOKE("get_events_by", { publicKey, asOf }) };
|
||||||
@@ -324,6 +316,14 @@ try {
|
|||||||
else return { status: "error", error: e as any };
|
else return { status: "error", error: e as any };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async listenLocalEvent(label: string) : Promise<Result<null, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("listen_local_event", { label }) };
|
||||||
|
} 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 getGroupEvents(publicKeys: string[], until: string | null) : Promise<Result<RichEvent[], string>> {
|
||||||
try {
|
try {
|
||||||
return { status: "ok", data: await TAURI_INVOKE("get_group_events", { publicKeys, until }) };
|
return { status: "ok", data: await TAURI_INVOKE("get_group_events", { publicKeys, until }) };
|
||||||
@@ -388,6 +388,14 @@ try {
|
|||||||
else return { status: "error", error: e as any };
|
else return { status: "error", error: e as any };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async unlisten(id: string) : Promise<Result<null, null>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("unlisten", { id }) };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
async showInFolder(path: string) : Promise<void> {
|
async showInFolder(path: string) : Promise<void> {
|
||||||
await TAURI_INVOKE("show_in_folder", { path });
|
await TAURI_INVOKE("show_in_folder", { path });
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -200,7 +200,7 @@ export class LumeEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async unlistenEventReply() {
|
public async unlistenEventReply() {
|
||||||
const query = await commands.unlistenEventReply(this.id);
|
const query = await commands.unlisten(this.id);
|
||||||
|
|
||||||
if (query.status === "ok") {
|
if (query.status === "ok") {
|
||||||
return query.data;
|
return query.data;
|
||||||
@@ -271,6 +271,17 @@ export class LumeEvent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async build(event: NostrEvent) {
|
||||||
|
const query = await commands.getEventMeta(event.content);
|
||||||
|
|
||||||
|
if (query.status === "ok") {
|
||||||
|
event.meta = query.data;
|
||||||
|
return new LumeEvent(event);
|
||||||
|
} else {
|
||||||
|
return new LumeEvent(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static from(raw: string, parsed?: Meta) {
|
static from(raw: string, parsed?: Meta) {
|
||||||
const nostrEvent: NostrEvent = JSON.parse(raw);
|
const nostrEvent: NostrEvent = JSON.parse(raw);
|
||||||
|
|
||||||
@@ -280,6 +291,6 @@ export class LumeEvent {
|
|||||||
nostrEvent.meta = null;
|
nostrEvent.meta = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new this(nostrEvent);
|
return new LumeEvent(nostrEvent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { LumeColumn, Metadata, NostrEvent, Relay } from "@lume/types";
|
import type { LumeColumn, Metadata, NostrEvent, Relay } from "@lume/types";
|
||||||
import { resolveResource } from "@tauri-apps/api/path";
|
import { resolveResource } from "@tauri-apps/api/path";
|
||||||
|
import { getCurrent } from "@tauri-apps/api/window";
|
||||||
import { open } from "@tauri-apps/plugin-dialog";
|
import { open } from "@tauri-apps/plugin-dialog";
|
||||||
import { readFile, readTextFile } from "@tauri-apps/plugin-fs";
|
import { readFile, readTextFile } from "@tauri-apps/plugin-fs";
|
||||||
import { relaunch } from "@tauri-apps/plugin-process";
|
import { relaunch } from "@tauri-apps/plugin-process";
|
||||||
@@ -206,6 +207,17 @@ export class NostrQuery {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async listenLocalEvent() {
|
||||||
|
const label = getCurrent().label;
|
||||||
|
const query = await commands.listenLocalEvent(label);
|
||||||
|
|
||||||
|
if (query.status === "ok") {
|
||||||
|
return query.data;
|
||||||
|
} else {
|
||||||
|
throw new Error(query.error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static async getGroupEvents(pubkeys: string[], asOf?: number) {
|
static async getGroupEvents(pubkeys: string[], asOf?: number) {
|
||||||
const until: string = asOf && asOf > 0 ? asOf.toString() : undefined;
|
const until: string = asOf && asOf > 0 ? asOf.toString() : undefined;
|
||||||
const query = await commands.getGroupEvents(pubkeys, until);
|
const query = await commands.getGroupEvents(pubkeys, until);
|
||||||
@@ -403,4 +415,15 @@ export class NostrQuery {
|
|||||||
throw new Error(query.error);
|
throw new Error(query.error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async unlisten(id?: string) {
|
||||||
|
const label = id ? id : getCurrent().label;
|
||||||
|
const query = await commands.unlisten(label);
|
||||||
|
|
||||||
|
if (query.status === "ok") {
|
||||||
|
return query.data;
|
||||||
|
} else {
|
||||||
|
throw new Error(query.error);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -129,11 +129,17 @@ export class LumeWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static async openSearch() {
|
static async openSearch(searchType: "notes" | "users", searchQuery: string) {
|
||||||
const label = "search";
|
const url = `/search/${searchType}?query=${searchQuery}`;
|
||||||
|
const label = `search-${searchQuery
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/[^\w ]+/g, "")
|
||||||
|
.replace(/ +/g, "_")
|
||||||
|
.replace(/_+/g, "_")}`;
|
||||||
|
|
||||||
const query = await commands.openWindow({
|
const query = await commands.openWindow({
|
||||||
label,
|
label,
|
||||||
url: "/search",
|
url,
|
||||||
title: "Search",
|
title: "Search",
|
||||||
width: 400,
|
width: 400,
|
||||||
height: 600,
|
height: 600,
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
"@tailwindcss/typography": "^0.5.13",
|
"@tailwindcss/typography": "^0.5.13",
|
||||||
"tailwind-gradient-mask-image": "^1.2.0",
|
"tailwind-gradient-mask-image": "^1.2.0",
|
||||||
"tailwind-scrollbar": "^3.1.0",
|
"tailwind-scrollbar": "^3.1.0",
|
||||||
"tailwindcss": "^3.4.4"
|
"tailwindcss": "^3.4.4",
|
||||||
|
"tailwindcss-content-visibility": "^0.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ const config = {
|
|||||||
require("@tailwindcss/forms"),
|
require("@tailwindcss/forms"),
|
||||||
require("@tailwindcss/typography"),
|
require("@tailwindcss/typography"),
|
||||||
require("tailwind-gradient-mask-image"),
|
require("tailwind-gradient-mask-image"),
|
||||||
require("tailwind-scrollbar")({ nocompatible: true }),
|
require("tailwindcss-content-visibility"),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
export * from "./src/constants";
|
export * from "./src/constants";
|
||||||
export * from "./src/delay";
|
|
||||||
export * from "./src/formater";
|
export * from "./src/formater";
|
||||||
export * from "./src/editor";
|
export * from "./src/editor";
|
||||||
export * from "./src/cn";
|
export * from "./src/cn";
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
export const delay = (ms: number) => new Promise((res) => setTimeout(res, ms));
|
|
||||||
7465
pnpm-lock.yaml
generated
7465
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
35
src-tauri/Cargo.lock
generated
35
src-tauri/Cargo.lock
generated
@@ -2811,6 +2811,7 @@ dependencies = [
|
|||||||
"tauri-plugin-theme",
|
"tauri-plugin-theme",
|
||||||
"tauri-plugin-updater",
|
"tauri-plugin-updater",
|
||||||
"tauri-plugin-upload",
|
"tauri-plugin-upload",
|
||||||
|
"tauri-plugin-window-state",
|
||||||
"tauri-specta",
|
"tauri-specta",
|
||||||
"tokio",
|
"tokio",
|
||||||
"url",
|
"url",
|
||||||
@@ -5373,7 +5374,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-clipboard-manager"
|
name = "tauri-plugin-clipboard-manager"
|
||||||
version = "2.1.0-beta.4"
|
version = "2.1.0-beta.4"
|
||||||
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#29751ee939fc8d26df07e4da3ad7f5c2aa0926ba"
|
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#03d3cc3677bbebd3a8a4f1ab07f9a3bec671b7f5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arboard",
|
"arboard",
|
||||||
"image 0.24.9",
|
"image 0.24.9",
|
||||||
@@ -5404,7 +5405,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-dialog"
|
name = "tauri-plugin-dialog"
|
||||||
version = "2.0.0-beta.9"
|
version = "2.0.0-beta.9"
|
||||||
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#29751ee939fc8d26df07e4da3ad7f5c2aa0926ba"
|
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#03d3cc3677bbebd3a8a4f1ab07f9a3bec671b7f5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dunce",
|
"dunce",
|
||||||
"log",
|
"log",
|
||||||
@@ -5421,7 +5422,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-fs"
|
name = "tauri-plugin-fs"
|
||||||
version = "2.0.0-beta.9"
|
version = "2.0.0-beta.9"
|
||||||
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#29751ee939fc8d26df07e4da3ad7f5c2aa0926ba"
|
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#03d3cc3677bbebd3a8a4f1ab07f9a3bec671b7f5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"glob",
|
"glob",
|
||||||
@@ -5439,7 +5440,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-http"
|
name = "tauri-plugin-http"
|
||||||
version = "2.0.0-beta.10"
|
version = "2.0.0-beta.10"
|
||||||
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#29751ee939fc8d26df07e4da3ad7f5c2aa0926ba"
|
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#03d3cc3677bbebd3a8a4f1ab07f9a3bec671b7f5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"data-url",
|
"data-url",
|
||||||
"http",
|
"http",
|
||||||
@@ -5459,7 +5460,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-notification"
|
name = "tauri-plugin-notification"
|
||||||
version = "2.0.0-beta.8"
|
version = "2.0.0-beta.8"
|
||||||
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#29751ee939fc8d26df07e4da3ad7f5c2aa0926ba"
|
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#03d3cc3677bbebd3a8a4f1ab07f9a3bec671b7f5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"notify-rust",
|
"notify-rust",
|
||||||
@@ -5477,7 +5478,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-os"
|
name = "tauri-plugin-os"
|
||||||
version = "2.0.0-beta.6"
|
version = "2.0.0-beta.6"
|
||||||
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#29751ee939fc8d26df07e4da3ad7f5c2aa0926ba"
|
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#03d3cc3677bbebd3a8a4f1ab07f9a3bec671b7f5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"gethostname",
|
"gethostname",
|
||||||
"log",
|
"log",
|
||||||
@@ -5494,7 +5495,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-process"
|
name = "tauri-plugin-process"
|
||||||
version = "2.0.0-beta.6"
|
version = "2.0.0-beta.6"
|
||||||
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#29751ee939fc8d26df07e4da3ad7f5c2aa0926ba"
|
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#03d3cc3677bbebd3a8a4f1ab07f9a3bec671b7f5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"tauri",
|
"tauri",
|
||||||
"tauri-plugin",
|
"tauri-plugin",
|
||||||
@@ -5503,7 +5504,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-shell"
|
name = "tauri-plugin-shell"
|
||||||
version = "2.0.0-beta.7"
|
version = "2.0.0-beta.7"
|
||||||
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#29751ee939fc8d26df07e4da3ad7f5c2aa0926ba"
|
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#03d3cc3677bbebd3a8a4f1ab07f9a3bec671b7f5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"encoding_rs",
|
"encoding_rs",
|
||||||
"log",
|
"log",
|
||||||
@@ -5541,7 +5542,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-updater"
|
name = "tauri-plugin-updater"
|
||||||
version = "2.0.0-beta.8"
|
version = "2.0.0-beta.8"
|
||||||
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#29751ee939fc8d26df07e4da3ad7f5c2aa0926ba"
|
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#03d3cc3677bbebd3a8a4f1ab07f9a3bec671b7f5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"dirs-next",
|
"dirs-next",
|
||||||
@@ -5569,7 +5570,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-upload"
|
name = "tauri-plugin-upload"
|
||||||
version = "2.0.0-beta.7"
|
version = "2.0.0-beta.7"
|
||||||
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#29751ee939fc8d26df07e4da3ad7f5c2aa0926ba"
|
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#03d3cc3677bbebd3a8a4f1ab07f9a3bec671b7f5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"log",
|
"log",
|
||||||
@@ -5584,6 +5585,20 @@ dependencies = [
|
|||||||
"tokio-util",
|
"tokio-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tauri-plugin-window-state"
|
||||||
|
version = "2.0.0-beta.9"
|
||||||
|
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#03d3cc3677bbebd3a8a4f1ab07f9a3bec671b7f5"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.6.0",
|
||||||
|
"log",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"tauri",
|
||||||
|
"tauri-plugin",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-runtime"
|
name = "tauri-runtime"
|
||||||
version = "2.0.0-beta.18"
|
version = "2.0.0-beta.18"
|
||||||
|
|||||||
@@ -17,12 +17,13 @@ serde_json = "1.0"
|
|||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
monitor = { git = "https://github.com/ahkohd/tauri-toolkit", branch = "v2" }
|
monitor = { git = "https://github.com/ahkohd/tauri-toolkit", branch = "v2" }
|
||||||
tauri = { version = "2.0.0-beta", features = [
|
tauri = { version = "2.0.0-beta", features = [
|
||||||
"unstable",
|
"unstable",
|
||||||
"tray-icon",
|
"tray-icon",
|
||||||
"macos-private-api",
|
"macos-private-api",
|
||||||
"native-tls-vendored",
|
"native-tls-vendored",
|
||||||
"protocol-asset",
|
"protocol-asset",
|
||||||
] }
|
] }
|
||||||
|
tauri-plugin-window-state = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
|
||||||
tauri-plugin-clipboard-manager = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
|
tauri-plugin-clipboard-manager = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
|
||||||
tauri-plugin-dialog = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
|
tauri-plugin-dialog = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
|
||||||
tauri-plugin-fs = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
|
tauri-plugin-fs = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
|
||||||
|
|||||||
51
src-tauri/capabilities/column.json
Normal file
51
src-tauri/capabilities/column.json
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
{
|
||||||
|
"$schema": "../gen/schemas/desktop-schema.json",
|
||||||
|
"identifier": "desktop-capability",
|
||||||
|
"description": "Capability for the column",
|
||||||
|
"platforms": [
|
||||||
|
"linux",
|
||||||
|
"macOS",
|
||||||
|
"windows"
|
||||||
|
],
|
||||||
|
"windows": [
|
||||||
|
"column-*"
|
||||||
|
],
|
||||||
|
"permissions": [
|
||||||
|
"resources:default",
|
||||||
|
"tray:default",
|
||||||
|
"os:allow-locale",
|
||||||
|
"os:allow-os-type",
|
||||||
|
"clipboard-manager:allow-write-text",
|
||||||
|
"dialog:allow-open",
|
||||||
|
"dialog:allow-ask",
|
||||||
|
"dialog:allow-message",
|
||||||
|
"fs:allow-read-file",
|
||||||
|
"menu:default",
|
||||||
|
"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/*"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,90 +1,91 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../gen/schemas/desktop-schema.json",
|
"$schema": "../gen/schemas/desktop-schema.json",
|
||||||
"identifier": "desktop-capability",
|
"identifier": "desktop-capability",
|
||||||
"description": "Capability for the desktop",
|
"description": "Capability for the desktop",
|
||||||
"platforms": ["linux", "macOS", "windows"],
|
"platforms": [
|
||||||
"windows": [
|
"linux",
|
||||||
"main",
|
"macOS",
|
||||||
"panel",
|
"windows"
|
||||||
"splash",
|
],
|
||||||
"settings",
|
"windows": [
|
||||||
"search",
|
"main",
|
||||||
"nwc",
|
"panel",
|
||||||
"activity",
|
"settings",
|
||||||
"zap-*",
|
"search-*",
|
||||||
"event-*",
|
"zap-*",
|
||||||
"user-*",
|
"event-*",
|
||||||
"editor-*",
|
"user-*",
|
||||||
"column-*"
|
"editor-*"
|
||||||
],
|
],
|
||||||
"permissions": [
|
"permissions": [
|
||||||
"path:default",
|
"path:default",
|
||||||
"event:default",
|
"event:default",
|
||||||
"window:default",
|
"window:default",
|
||||||
"app:default",
|
"app:default",
|
||||||
"resources:default",
|
"resources:default",
|
||||||
"menu:default",
|
"menu:default",
|
||||||
"tray:default",
|
"tray:default",
|
||||||
"notification:allow-is-permission-granted",
|
"notification:allow-is-permission-granted",
|
||||||
"notification:allow-request-permission",
|
"notification:allow-request-permission",
|
||||||
"notification:default",
|
"notification:default",
|
||||||
"os:allow-locale",
|
"os:allow-locale",
|
||||||
"os:allow-platform",
|
"os:allow-platform",
|
||||||
"os:allow-os-type",
|
"os:allow-os-type",
|
||||||
"updater:default",
|
"updater:default",
|
||||||
"updater:allow-check",
|
"updater:allow-check",
|
||||||
"updater:allow-download-and-install",
|
"updater:allow-download-and-install",
|
||||||
"window:allow-start-dragging",
|
"window:allow-start-dragging",
|
||||||
"window:allow-create",
|
"window:allow-create",
|
||||||
"window:allow-close",
|
"window:allow-close",
|
||||||
"window:allow-destroy",
|
"window:allow-destroy",
|
||||||
"window:allow-set-focus",
|
"window:allow-set-focus",
|
||||||
"window:allow-center",
|
"window:allow-center",
|
||||||
"window:allow-minimize",
|
"window:allow-minimize",
|
||||||
"window:allow-maximize",
|
"window:allow-maximize",
|
||||||
"window:allow-set-size",
|
"window:allow-set-size",
|
||||||
"window:allow-set-focus",
|
"window:allow-set-focus",
|
||||||
"window:allow-start-dragging",
|
"window:allow-start-dragging",
|
||||||
"decorum:allow-show-snap-overlay",
|
"decorum:allow-show-snap-overlay",
|
||||||
"clipboard-manager:allow-write-text",
|
"clipboard-manager:allow-write-text",
|
||||||
"clipboard-manager:allow-read-text",
|
"clipboard-manager:allow-read-text",
|
||||||
"webview:allow-create-webview-window",
|
"webview:allow-create-webview-window",
|
||||||
"webview:allow-create-webview",
|
"webview:allow-create-webview",
|
||||||
"webview:allow-set-webview-size",
|
"webview:allow-set-webview-size",
|
||||||
"webview:allow-set-webview-position",
|
"webview:allow-set-webview-position",
|
||||||
"webview:allow-webview-close",
|
"webview:allow-webview-close",
|
||||||
"dialog:allow-open",
|
"dialog:allow-open",
|
||||||
"dialog:allow-ask",
|
"dialog:allow-ask",
|
||||||
"dialog:allow-message",
|
"dialog:allow-message",
|
||||||
"process:allow-restart",
|
"process:allow-restart",
|
||||||
"fs:allow-read-file",
|
"process:allow-exit",
|
||||||
"theme:allow-set-theme",
|
"fs:allow-read-file",
|
||||||
"theme:allow-get-theme",
|
"theme:allow-set-theme",
|
||||||
"menu:allow-new",
|
"theme:allow-get-theme",
|
||||||
"menu:allow-popup",
|
"menu:allow-new",
|
||||||
"http:default",
|
"menu:allow-popup",
|
||||||
"shell:allow-open",
|
"http:default",
|
||||||
{
|
"shell:allow-open",
|
||||||
"identifier": "http:default",
|
{
|
||||||
"allow": [
|
"identifier": "http:default",
|
||||||
{
|
"allow": [
|
||||||
"url": "http://**/"
|
{
|
||||||
},
|
"url": "http://**/"
|
||||||
{
|
},
|
||||||
"url": "https://**/"
|
{
|
||||||
}
|
"url": "https://**/"
|
||||||
]
|
}
|
||||||
},
|
]
|
||||||
{
|
},
|
||||||
"identifier": "fs:allow-read-text-file",
|
{
|
||||||
"allow": [
|
"identifier": "fs:allow-read-text-file",
|
||||||
{
|
"allow": [
|
||||||
"path": "$RESOURCE/locales/*"
|
{
|
||||||
},
|
"path": "$RESOURCE/locales/*"
|
||||||
{
|
},
|
||||||
"path": "$RESOURCE/resources/*"
|
{
|
||||||
}
|
"path": "$RESOURCE/resources/*"
|
||||||
]
|
}
|
||||||
}
|
]
|
||||||
]
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
|||||||
{"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"]}}
|
{"desktop-capability":{"identifier":"desktop-capability","description":"Capability for the desktop","local":true,"windows":["main","panel","settings","search-*","zap-*","event-*","user-*","editor-*"],"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","process:allow-exit","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"]}}
|
||||||
@@ -142,7 +142,7 @@
|
|||||||
"identifier": {
|
"identifier": {
|
||||||
"oneOf": [
|
"oneOf": [
|
||||||
{
|
{
|
||||||
"description": "fs:default -> # Tauri `fs` default permissions\n\nThis configuration file defines the default permissions granted\nto the filesystem.\n\n### Granted Permissions\n\nThis default permission set enables all read-related commands and\nallows access to the `$APP` folder and sub directories created in it.\nThe location of the `$APP` folder depends on the operating system,\nwhere the application is run.\n\nIn general the `$APP` folder needs to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\n### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n",
|
"description": "fs:default -> This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"fs:default"
|
"fs:default"
|
||||||
@@ -1373,6 +1373,13 @@
|
|||||||
"fs:allow-write-text-file"
|
"fs:allow-write-text-file"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "fs:create-app-specific-dirs -> This permissions allows to create the application specific directories.\n",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"fs:create-app-specific-dirs"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "fs:deny-copy-file -> Denies the copy_file command without any pre-configured scope.",
|
"description": "fs:deny-copy-file -> Denies the copy_file command without any pre-configured scope.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -1562,6 +1569,13 @@
|
|||||||
"fs:read-all"
|
"fs:read-all"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "fs:read-app-specific-dirs-recursive -> This permission allows recursive read functionality on the application\nspecific base directories. \n",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"fs:read-app-specific-dirs-recursive"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "fs:read-dirs -> This enables directory read and file metadata related commands without any pre-configured accessible paths.",
|
"description": "fs:read-dirs -> This enables directory read and file metadata related commands without any pre-configured accessible paths.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -2190,7 +2204,7 @@
|
|||||||
"identifier": {
|
"identifier": {
|
||||||
"oneOf": [
|
"oneOf": [
|
||||||
{
|
{
|
||||||
"description": "http:default -> Allows all fetch operations",
|
"description": "http:default -> This permission set configures what kind of\nfetch operations are available from the http plugin.\n\nThis enables all fetch operations but does not\nallow explicitly any origins to be fetched. This needs to\nbe manually configured before usage.\n\n#### Granted Permissions\n\nAll fetch operations are enabled.\n\n",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"http:default"
|
"http:default"
|
||||||
@@ -2313,6 +2327,7 @@
|
|||||||
"identifier": {
|
"identifier": {
|
||||||
"oneOf": [
|
"oneOf": [
|
||||||
{
|
{
|
||||||
|
"description": "shell:default -> This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality without any specific\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"shell:default"
|
"shell:default"
|
||||||
@@ -2546,6 +2561,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"description": "clipboard-manager:default -> No features are enabled by default, as we believe\nthe clipboard can be inherently dangerous and it is \napplication specific if read and/or write access is needed.\n\nClipboard interaction needs to be explicitly enabled.\n",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"clipboard-manager:default"
|
"clipboard-manager:default"
|
||||||
@@ -2656,6 +2672,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"description": "dialog:default -> This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"dialog:default"
|
"dialog:default"
|
||||||
@@ -3852,7 +3869,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "fs:default -> # Tauri `fs` default permissions\n\nThis configuration file defines the default permissions granted\nto the filesystem.\n\n### Granted Permissions\n\nThis default permission set enables all read-related commands and\nallows access to the `$APP` folder and sub directories created in it.\nThe location of the `$APP` folder depends on the operating system,\nwhere the application is run.\n\nIn general the `$APP` folder needs to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\n### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n",
|
"description": "fs:default -> This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"fs:default"
|
"fs:default"
|
||||||
@@ -4026,6 +4043,13 @@
|
|||||||
"fs:allow-write-text-file"
|
"fs:allow-write-text-file"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "fs:create-app-specific-dirs -> This permissions allows to create the application specific directories.\n",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"fs:create-app-specific-dirs"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "fs:deny-copy-file -> Denies the copy_file command without any pre-configured scope.",
|
"description": "fs:deny-copy-file -> Denies the copy_file command without any pre-configured scope.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -4215,6 +4239,13 @@
|
|||||||
"fs:read-all"
|
"fs:read-all"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "fs:read-app-specific-dirs-recursive -> This permission allows recursive read functionality on the application\nspecific base directories. \n",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"fs:read-app-specific-dirs-recursive"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "fs:read-dirs -> This enables directory read and file metadata related commands without any pre-configured accessible paths.",
|
"description": "fs:read-dirs -> This enables directory read and file metadata related commands without any pre-configured accessible paths.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -4783,7 +4814,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "http:default -> Allows all fetch operations",
|
"description": "http:default -> This permission set configures what kind of\nfetch operations are available from the http plugin.\n\nThis enables all fetch operations but does not\nallow explicitly any origins to be fetched. This needs to\nbe manually configured before usage.\n\n#### Granted Permissions\n\nAll fetch operations are enabled.\n\n",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"http:default"
|
"http:default"
|
||||||
@@ -5238,12 +5269,61 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "notification:default -> Allows requesting permission, checking permission state and sending notifications",
|
"description": "notification:default -> This permission set configures which\nnotification features are by default exposed.\n\n#### Granted Permissions\n\nIt allows all notification related features.\n\n",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"notification:default"
|
"notification:default"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "notification:allow-batch -> Enables the batch command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"notification:allow-batch"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "notification:allow-cancel -> Enables the cancel command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"notification:allow-cancel"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "notification:allow-check-permissions -> Enables the check_permissions command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"notification:allow-check-permissions"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "notification:allow-create-channel -> Enables the create_channel command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"notification:allow-create-channel"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "notification:allow-delete-channel -> Enables the delete_channel command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"notification:allow-delete-channel"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "notification:allow-get-active -> Enables the get_active command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"notification:allow-get-active"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "notification:allow-get-pending -> Enables the get_pending command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"notification:allow-get-pending"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "notification:allow-is-permission-granted -> Enables the is_permission_granted command without any pre-configured scope.",
|
"description": "notification:allow-is-permission-granted -> Enables the is_permission_granted command without any pre-configured scope.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -5251,6 +5331,13 @@
|
|||||||
"notification:allow-is-permission-granted"
|
"notification:allow-is-permission-granted"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "notification:allow-list-channels -> Enables the list_channels command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"notification:allow-list-channels"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "notification:allow-notify -> Enables the notify command without any pre-configured scope.",
|
"description": "notification:allow-notify -> Enables the notify command without any pre-configured scope.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -5258,6 +5345,13 @@
|
|||||||
"notification:allow-notify"
|
"notification:allow-notify"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "notification:allow-permission-state -> Enables the permission_state command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"notification:allow-permission-state"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "notification:allow-register-action-types -> Enables the register_action_types command without any pre-configured scope.",
|
"description": "notification:allow-register-action-types -> Enables the register_action_types command without any pre-configured scope.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -5272,6 +5366,13 @@
|
|||||||
"notification:allow-register-listener"
|
"notification:allow-register-listener"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "notification:allow-remove-active -> Enables the remove_active command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"notification:allow-remove-active"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "notification:allow-request-permission -> Enables the request_permission command without any pre-configured scope.",
|
"description": "notification:allow-request-permission -> Enables the request_permission command without any pre-configured scope.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -5279,6 +5380,62 @@
|
|||||||
"notification:allow-request-permission"
|
"notification:allow-request-permission"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "notification:allow-show -> Enables the show command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"notification:allow-show"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "notification:deny-batch -> Denies the batch command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"notification:deny-batch"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "notification:deny-cancel -> Denies the cancel command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"notification:deny-cancel"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "notification:deny-check-permissions -> Denies the check_permissions command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"notification:deny-check-permissions"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "notification:deny-create-channel -> Denies the create_channel command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"notification:deny-create-channel"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "notification:deny-delete-channel -> Denies the delete_channel command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"notification:deny-delete-channel"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "notification:deny-get-active -> Denies the get_active command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"notification:deny-get-active"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "notification:deny-get-pending -> Denies the get_pending command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"notification:deny-get-pending"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "notification:deny-is-permission-granted -> Denies the is_permission_granted command without any pre-configured scope.",
|
"description": "notification:deny-is-permission-granted -> Denies the is_permission_granted command without any pre-configured scope.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -5286,6 +5443,13 @@
|
|||||||
"notification:deny-is-permission-granted"
|
"notification:deny-is-permission-granted"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "notification:deny-list-channels -> Denies the list_channels command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"notification:deny-list-channels"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "notification:deny-notify -> Denies the notify command without any pre-configured scope.",
|
"description": "notification:deny-notify -> Denies the notify command without any pre-configured scope.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -5293,6 +5457,13 @@
|
|||||||
"notification:deny-notify"
|
"notification:deny-notify"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "notification:deny-permission-state -> Denies the permission_state command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"notification:deny-permission-state"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "notification:deny-register-action-types -> Denies the register_action_types command without any pre-configured scope.",
|
"description": "notification:deny-register-action-types -> Denies the register_action_types command without any pre-configured scope.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -5307,6 +5478,13 @@
|
|||||||
"notification:deny-register-listener"
|
"notification:deny-register-listener"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "notification:deny-remove-active -> Denies the remove_active command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"notification:deny-remove-active"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "notification:deny-request-permission -> Denies the request_permission command without any pre-configured scope.",
|
"description": "notification:deny-request-permission -> Denies the request_permission command without any pre-configured scope.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -5315,6 +5493,14 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"description": "notification:deny-show -> Denies the show command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"notification:deny-show"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "os:default -> This permission set configures which\noperating system information are available\nto gather from the frontend.\n\n#### Granted Permissions\n\nAll information except the host name are available.\n\n",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"os:default"
|
"os:default"
|
||||||
@@ -5552,6 +5738,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"description": "process:default -> This permission set configures which\nprocess feeatures are by default exposed.\n\n#### Granted Permissions\n\nThis enables to quit via `allow-exit` and restart via `allow-restart`\nthe application.\n",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"process:default"
|
"process:default"
|
||||||
@@ -5607,6 +5794,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"description": "shell:default -> This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality without any specific\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"shell:default"
|
"shell:default"
|
||||||
@@ -5878,7 +6066,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "updater:default -> Allows checking for new updates and installing them",
|
"description": "updater:default -> This permission set configures which kind of\nupdater functions are exposed to the frontend.\n\n#### Granted Permissions\n\nThe full workflow from checking for updates to installing them\nis enabled.\n\n",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"updater:default"
|
"updater:default"
|
||||||
@@ -5941,6 +6129,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"description": "upload:default -> This permission set configures what kind of\noperations are available from the upload plugin.\n\n#### Granted Permissions\n\nAll operations are enabled by default.\n\n",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"upload:default"
|
"upload:default"
|
||||||
@@ -7037,6 +7226,55 @@
|
|||||||
"enum": [
|
"enum": [
|
||||||
"window:deny-unminimize"
|
"window:deny-unminimize"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "window-state:default -> This permission set configures what kind of\noperations are available from the window state plugin.\n\n#### Granted Permissions\n\nAll operations are enabled by default.\n\n",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"window-state:default"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "window-state:allow-filename -> Enables the filename command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"window-state:allow-filename"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "window-state:allow-restore-state -> Enables the restore_state command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"window-state:allow-restore-state"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "window-state:allow-save-window-state -> Enables the save_window_state command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"window-state:allow-save-window-state"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "window-state:deny-filename -> Denies the filename command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"window-state:deny-filename"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "window-state:deny-restore-state -> Denies the restore_state command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"window-state:deny-restore-state"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "window-state:deny-save-window-state -> Denies the save_window_state command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"window-state:deny-save-window-state"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -142,7 +142,7 @@
|
|||||||
"identifier": {
|
"identifier": {
|
||||||
"oneOf": [
|
"oneOf": [
|
||||||
{
|
{
|
||||||
"description": "fs:default -> # Tauri `fs` default permissions\n\nThis configuration file defines the default permissions granted\nto the filesystem.\n\n### Granted Permissions\n\nThis default permission set enables all read-related commands and\nallows access to the `$APP` folder and sub directories created in it.\nThe location of the `$APP` folder depends on the operating system,\nwhere the application is run.\n\nIn general the `$APP` folder needs to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\n### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n",
|
"description": "fs:default -> This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"fs:default"
|
"fs:default"
|
||||||
@@ -1373,6 +1373,13 @@
|
|||||||
"fs:allow-write-text-file"
|
"fs:allow-write-text-file"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "fs:create-app-specific-dirs -> This permissions allows to create the application specific directories.\n",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"fs:create-app-specific-dirs"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "fs:deny-copy-file -> Denies the copy_file command without any pre-configured scope.",
|
"description": "fs:deny-copy-file -> Denies the copy_file command without any pre-configured scope.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -1562,6 +1569,13 @@
|
|||||||
"fs:read-all"
|
"fs:read-all"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "fs:read-app-specific-dirs-recursive -> This permission allows recursive read functionality on the application\nspecific base directories. \n",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"fs:read-app-specific-dirs-recursive"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "fs:read-dirs -> This enables directory read and file metadata related commands without any pre-configured accessible paths.",
|
"description": "fs:read-dirs -> This enables directory read and file metadata related commands without any pre-configured accessible paths.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -2190,7 +2204,7 @@
|
|||||||
"identifier": {
|
"identifier": {
|
||||||
"oneOf": [
|
"oneOf": [
|
||||||
{
|
{
|
||||||
"description": "http:default -> Allows all fetch operations",
|
"description": "http:default -> This permission set configures what kind of\nfetch operations are available from the http plugin.\n\nThis enables all fetch operations but does not\nallow explicitly any origins to be fetched. This needs to\nbe manually configured before usage.\n\n#### Granted Permissions\n\nAll fetch operations are enabled.\n\n",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"http:default"
|
"http:default"
|
||||||
@@ -2313,6 +2327,7 @@
|
|||||||
"identifier": {
|
"identifier": {
|
||||||
"oneOf": [
|
"oneOf": [
|
||||||
{
|
{
|
||||||
|
"description": "shell:default -> This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality without any specific\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"shell:default"
|
"shell:default"
|
||||||
@@ -2546,6 +2561,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"description": "clipboard-manager:default -> No features are enabled by default, as we believe\nthe clipboard can be inherently dangerous and it is \napplication specific if read and/or write access is needed.\n\nClipboard interaction needs to be explicitly enabled.\n",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"clipboard-manager:default"
|
"clipboard-manager:default"
|
||||||
@@ -2656,6 +2672,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"description": "dialog:default -> This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"dialog:default"
|
"dialog:default"
|
||||||
@@ -3852,7 +3869,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "fs:default -> # Tauri `fs` default permissions\n\nThis configuration file defines the default permissions granted\nto the filesystem.\n\n### Granted Permissions\n\nThis default permission set enables all read-related commands and\nallows access to the `$APP` folder and sub directories created in it.\nThe location of the `$APP` folder depends on the operating system,\nwhere the application is run.\n\nIn general the `$APP` folder needs to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\n### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n",
|
"description": "fs:default -> This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"fs:default"
|
"fs:default"
|
||||||
@@ -4026,6 +4043,13 @@
|
|||||||
"fs:allow-write-text-file"
|
"fs:allow-write-text-file"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "fs:create-app-specific-dirs -> This permissions allows to create the application specific directories.\n",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"fs:create-app-specific-dirs"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "fs:deny-copy-file -> Denies the copy_file command without any pre-configured scope.",
|
"description": "fs:deny-copy-file -> Denies the copy_file command without any pre-configured scope.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -4215,6 +4239,13 @@
|
|||||||
"fs:read-all"
|
"fs:read-all"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "fs:read-app-specific-dirs-recursive -> This permission allows recursive read functionality on the application\nspecific base directories. \n",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"fs:read-app-specific-dirs-recursive"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "fs:read-dirs -> This enables directory read and file metadata related commands without any pre-configured accessible paths.",
|
"description": "fs:read-dirs -> This enables directory read and file metadata related commands without any pre-configured accessible paths.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -4783,7 +4814,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "http:default -> Allows all fetch operations",
|
"description": "http:default -> This permission set configures what kind of\nfetch operations are available from the http plugin.\n\nThis enables all fetch operations but does not\nallow explicitly any origins to be fetched. This needs to\nbe manually configured before usage.\n\n#### Granted Permissions\n\nAll fetch operations are enabled.\n\n",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"http:default"
|
"http:default"
|
||||||
@@ -5238,12 +5269,61 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "notification:default -> Allows requesting permission, checking permission state and sending notifications",
|
"description": "notification:default -> This permission set configures which\nnotification features are by default exposed.\n\n#### Granted Permissions\n\nIt allows all notification related features.\n\n",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"notification:default"
|
"notification:default"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "notification:allow-batch -> Enables the batch command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"notification:allow-batch"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "notification:allow-cancel -> Enables the cancel command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"notification:allow-cancel"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "notification:allow-check-permissions -> Enables the check_permissions command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"notification:allow-check-permissions"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "notification:allow-create-channel -> Enables the create_channel command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"notification:allow-create-channel"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "notification:allow-delete-channel -> Enables the delete_channel command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"notification:allow-delete-channel"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "notification:allow-get-active -> Enables the get_active command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"notification:allow-get-active"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "notification:allow-get-pending -> Enables the get_pending command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"notification:allow-get-pending"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "notification:allow-is-permission-granted -> Enables the is_permission_granted command without any pre-configured scope.",
|
"description": "notification:allow-is-permission-granted -> Enables the is_permission_granted command without any pre-configured scope.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -5251,6 +5331,13 @@
|
|||||||
"notification:allow-is-permission-granted"
|
"notification:allow-is-permission-granted"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "notification:allow-list-channels -> Enables the list_channels command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"notification:allow-list-channels"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "notification:allow-notify -> Enables the notify command without any pre-configured scope.",
|
"description": "notification:allow-notify -> Enables the notify command without any pre-configured scope.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -5258,6 +5345,13 @@
|
|||||||
"notification:allow-notify"
|
"notification:allow-notify"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "notification:allow-permission-state -> Enables the permission_state command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"notification:allow-permission-state"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "notification:allow-register-action-types -> Enables the register_action_types command without any pre-configured scope.",
|
"description": "notification:allow-register-action-types -> Enables the register_action_types command without any pre-configured scope.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -5272,6 +5366,13 @@
|
|||||||
"notification:allow-register-listener"
|
"notification:allow-register-listener"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "notification:allow-remove-active -> Enables the remove_active command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"notification:allow-remove-active"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "notification:allow-request-permission -> Enables the request_permission command without any pre-configured scope.",
|
"description": "notification:allow-request-permission -> Enables the request_permission command without any pre-configured scope.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -5279,6 +5380,62 @@
|
|||||||
"notification:allow-request-permission"
|
"notification:allow-request-permission"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "notification:allow-show -> Enables the show command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"notification:allow-show"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "notification:deny-batch -> Denies the batch command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"notification:deny-batch"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "notification:deny-cancel -> Denies the cancel command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"notification:deny-cancel"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "notification:deny-check-permissions -> Denies the check_permissions command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"notification:deny-check-permissions"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "notification:deny-create-channel -> Denies the create_channel command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"notification:deny-create-channel"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "notification:deny-delete-channel -> Denies the delete_channel command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"notification:deny-delete-channel"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "notification:deny-get-active -> Denies the get_active command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"notification:deny-get-active"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "notification:deny-get-pending -> Denies the get_pending command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"notification:deny-get-pending"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "notification:deny-is-permission-granted -> Denies the is_permission_granted command without any pre-configured scope.",
|
"description": "notification:deny-is-permission-granted -> Denies the is_permission_granted command without any pre-configured scope.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -5286,6 +5443,13 @@
|
|||||||
"notification:deny-is-permission-granted"
|
"notification:deny-is-permission-granted"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "notification:deny-list-channels -> Denies the list_channels command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"notification:deny-list-channels"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "notification:deny-notify -> Denies the notify command without any pre-configured scope.",
|
"description": "notification:deny-notify -> Denies the notify command without any pre-configured scope.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -5293,6 +5457,13 @@
|
|||||||
"notification:deny-notify"
|
"notification:deny-notify"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "notification:deny-permission-state -> Denies the permission_state command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"notification:deny-permission-state"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "notification:deny-register-action-types -> Denies the register_action_types command without any pre-configured scope.",
|
"description": "notification:deny-register-action-types -> Denies the register_action_types command without any pre-configured scope.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -5307,6 +5478,13 @@
|
|||||||
"notification:deny-register-listener"
|
"notification:deny-register-listener"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "notification:deny-remove-active -> Denies the remove_active command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"notification:deny-remove-active"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "notification:deny-request-permission -> Denies the request_permission command without any pre-configured scope.",
|
"description": "notification:deny-request-permission -> Denies the request_permission command without any pre-configured scope.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -5315,6 +5493,14 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"description": "notification:deny-show -> Denies the show command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"notification:deny-show"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "os:default -> This permission set configures which\noperating system information are available\nto gather from the frontend.\n\n#### Granted Permissions\n\nAll information except the host name are available.\n\n",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"os:default"
|
"os:default"
|
||||||
@@ -5552,6 +5738,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"description": "process:default -> This permission set configures which\nprocess feeatures are by default exposed.\n\n#### Granted Permissions\n\nThis enables to quit via `allow-exit` and restart via `allow-restart`\nthe application.\n",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"process:default"
|
"process:default"
|
||||||
@@ -5607,6 +5794,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"description": "shell:default -> This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality without any specific\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"shell:default"
|
"shell:default"
|
||||||
@@ -5878,7 +6066,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "updater:default -> Allows checking for new updates and installing them",
|
"description": "updater:default -> This permission set configures which kind of\nupdater functions are exposed to the frontend.\n\n#### Granted Permissions\n\nThe full workflow from checking for updates to installing them\nis enabled.\n\n",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"updater:default"
|
"updater:default"
|
||||||
@@ -5941,6 +6129,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"description": "upload:default -> This permission set configures what kind of\noperations are available from the upload plugin.\n\n#### Granted Permissions\n\nAll operations are enabled by default.\n\n",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"upload:default"
|
"upload:default"
|
||||||
@@ -7037,6 +7226,55 @@
|
|||||||
"enum": [
|
"enum": [
|
||||||
"window:deny-unminimize"
|
"window:deny-unminimize"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "window-state:default -> This permission set configures what kind of\noperations are available from the window state plugin.\n\n#### Granted Permissions\n\nAll operations are enabled by default.\n\n",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"window-state:default"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "window-state:allow-filename -> Enables the filename command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"window-state:allow-filename"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "window-state:allow-restore-state -> Enables the restore_state command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"window-state:allow-restore-state"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "window-state:allow-save-window-state -> Enables the save_window_state command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"window-state:allow-save-window-state"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "window-state:deny-filename -> Denies the filename command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"window-state:deny-filename"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "window-state:deny-restore-state -> Denies the restore_state command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"window-state:deny-restore-state"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "window-state:deny-save-window-state -> Denies the save_window_state command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"window-state:deny-save-window-state"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -71,6 +71,10 @@ impl Default for Settings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const FETCH_LIMIT: usize = 20;
|
||||||
|
pub const NEWSFEED_NEG_LIMIT: usize = 256;
|
||||||
|
pub const NOTIFICATION_NEG_LIMIT: usize = 64;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut ctx = tauri::generate_context!();
|
let mut ctx = tauri::generate_context!();
|
||||||
|
|
||||||
@@ -113,9 +117,9 @@ fn main() {
|
|||||||
nostr::event::get_event_from,
|
nostr::event::get_event_from,
|
||||||
nostr::event::get_replies,
|
nostr::event::get_replies,
|
||||||
nostr::event::listen_event_reply,
|
nostr::event::listen_event_reply,
|
||||||
nostr::event::unlisten_event_reply,
|
|
||||||
nostr::event::get_events_by,
|
nostr::event::get_events_by,
|
||||||
nostr::event::get_local_events,
|
nostr::event::get_local_events,
|
||||||
|
nostr::event::listen_local_event,
|
||||||
nostr::event::get_group_events,
|
nostr::event::get_group_events,
|
||||||
nostr::event::get_global_events,
|
nostr::event::get_global_events,
|
||||||
nostr::event::get_hashtag_events,
|
nostr::event::get_hashtag_events,
|
||||||
@@ -124,6 +128,7 @@ fn main() {
|
|||||||
nostr::event::repost,
|
nostr::event::repost,
|
||||||
nostr::event::event_to_bech32,
|
nostr::event::event_to_bech32,
|
||||||
nostr::event::user_to_bech32,
|
nostr::event::user_to_bech32,
|
||||||
|
nostr::event::unlisten,
|
||||||
commands::folder::show_in_folder,
|
commands::folder::show_in_folder,
|
||||||
commands::window::create_column,
|
commands::window::create_column,
|
||||||
commands::window::close_column,
|
commands::window::close_column,
|
||||||
@@ -263,6 +268,11 @@ fn main() {
|
|||||||
.plugin(tauri_plugin_shell::init())
|
.plugin(tauri_plugin_shell::init())
|
||||||
.plugin(tauri_plugin_upload::init())
|
.plugin(tauri_plugin_upload::init())
|
||||||
.plugin(tauri_plugin_updater::Builder::new().build())
|
.plugin(tauri_plugin_updater::Builder::new().build())
|
||||||
|
.plugin(
|
||||||
|
tauri_plugin_window_state::Builder::new()
|
||||||
|
.with_denylist(&["panel"])
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
.invoke_handler(invoke_handler)
|
.invoke_handler(invoke_handler)
|
||||||
.build(ctx)
|
.build(ctx)
|
||||||
.expect("error while running tauri application")
|
.expect("error while running tauri application")
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use specta::Type;
|
|||||||
use tauri::State;
|
use tauri::State;
|
||||||
|
|
||||||
use crate::nostr::utils::{create_event_tags, dedup_event, parse_event, Meta};
|
use crate::nostr::utils::{create_event_tags, dedup_event, parse_event, Meta};
|
||||||
use crate::Nostr;
|
use crate::{Nostr, FETCH_LIMIT};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Type)]
|
#[derive(Debug, Clone, Serialize, Type)]
|
||||||
pub struct RichEvent {
|
pub struct RichEvent {
|
||||||
@@ -197,18 +197,6 @@ pub async fn listen_event_reply(id: &str, state: State<'_, Nostr>) -> Result<(),
|
|||||||
Ok(())
|
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]
|
#[tauri::command]
|
||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub async fn get_events_by(
|
pub async fn get_events_by(
|
||||||
@@ -227,7 +215,7 @@ pub async fn get_events_by(
|
|||||||
let filter = Filter::new()
|
let filter = Filter::new()
|
||||||
.kinds(vec![Kind::TextNote, Kind::Repost])
|
.kinds(vec![Kind::TextNote, Kind::Repost])
|
||||||
.author(author)
|
.author(author)
|
||||||
.limit(20)
|
.limit(FETCH_LIMIT)
|
||||||
.until(until);
|
.until(until);
|
||||||
|
|
||||||
match client.get_events_of(vec![filter], None).await {
|
match client.get_events_of(vec![filter], None).await {
|
||||||
@@ -275,17 +263,13 @@ pub async fn get_local_events(
|
|||||||
|
|
||||||
let filter = Filter::new()
|
let filter = Filter::new()
|
||||||
.kinds(vec![Kind::TextNote, Kind::Repost])
|
.kinds(vec![Kind::TextNote, Kind::Repost])
|
||||||
.limit(20)
|
.limit(64)
|
||||||
.until(as_of)
|
.until(as_of)
|
||||||
.authors(authors);
|
.authors(authors);
|
||||||
|
|
||||||
match client
|
match client.database().query(vec![filter], Order::Desc).await {
|
||||||
.get_events_of(vec![filter], Some(Duration::from_secs(10)))
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(events) => {
|
Ok(events) => {
|
||||||
let dedup = dedup_event(&events);
|
let dedup = dedup_event(&events);
|
||||||
|
|
||||||
let futures = dedup.into_iter().map(|ev| async move {
|
let futures = dedup.into_iter().map(|ev| async move {
|
||||||
let raw = ev.as_json();
|
let raw = ev.as_json();
|
||||||
let parsed = if ev.kind == Kind::TextNote {
|
let parsed = if ev.kind == Kind::TextNote {
|
||||||
@@ -296,7 +280,6 @@ pub async fn get_local_events(
|
|||||||
|
|
||||||
RichEvent { raw, parsed }
|
RichEvent { raw, parsed }
|
||||||
});
|
});
|
||||||
|
|
||||||
let rich_events = join_all(futures).await;
|
let rich_events = join_all(futures).await;
|
||||||
|
|
||||||
Ok(rich_events)
|
Ok(rich_events)
|
||||||
@@ -305,6 +288,31 @@ pub async fn get_local_events(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
|
pub async fn listen_local_event(label: &str, state: State<'_, Nostr>) -> Result<(), String> {
|
||||||
|
let client = &state.client;
|
||||||
|
|
||||||
|
let contact_list = state
|
||||||
|
.contact_list
|
||||||
|
.lock()
|
||||||
|
.map_err(|err| err.to_string())?
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
let authors: Vec<PublicKey> = contact_list.into_iter().map(|f| f.public_key).collect();
|
||||||
|
let sub_id = SubscriptionId::new(label);
|
||||||
|
|
||||||
|
let filter = Filter::new()
|
||||||
|
.kinds(vec![Kind::TextNote, Kind::Repost])
|
||||||
|
.authors(authors)
|
||||||
|
.since(Timestamp::now());
|
||||||
|
|
||||||
|
// Subscribe
|
||||||
|
client.subscribe_with_id(sub_id, vec![filter], None).await;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub async fn get_group_events(
|
pub async fn get_group_events(
|
||||||
@@ -332,7 +340,7 @@ pub async fn get_group_events(
|
|||||||
|
|
||||||
let filter = Filter::new()
|
let filter = Filter::new()
|
||||||
.kinds(vec![Kind::TextNote, Kind::Repost])
|
.kinds(vec![Kind::TextNote, Kind::Repost])
|
||||||
.limit(20)
|
.limit(FETCH_LIMIT)
|
||||||
.until(as_of)
|
.until(as_of)
|
||||||
.authors(authors);
|
.authors(authors);
|
||||||
|
|
||||||
@@ -376,7 +384,7 @@ pub async fn get_global_events(
|
|||||||
|
|
||||||
let filter = Filter::new()
|
let filter = Filter::new()
|
||||||
.kinds(vec![Kind::TextNote, Kind::Repost])
|
.kinds(vec![Kind::TextNote, Kind::Repost])
|
||||||
.limit(20)
|
.limit(FETCH_LIMIT)
|
||||||
.until(as_of);
|
.until(as_of);
|
||||||
|
|
||||||
match client
|
match client
|
||||||
@@ -417,7 +425,7 @@ pub async fn get_hashtag_events(
|
|||||||
};
|
};
|
||||||
let filter = Filter::new()
|
let filter = Filter::new()
|
||||||
.kinds(vec![Kind::TextNote, Kind::Repost])
|
.kinds(vec![Kind::TextNote, Kind::Repost])
|
||||||
.limit(20)
|
.limit(FETCH_LIMIT)
|
||||||
.until(as_of)
|
.until(as_of)
|
||||||
.hashtags(hashtags);
|
.hashtags(hashtags);
|
||||||
|
|
||||||
@@ -663,3 +671,15 @@ pub async fn user_to_bech32(user: &str, state: State<'_, Nostr>) -> Result<Strin
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
|
pub async fn unlisten(id: &str, state: State<'_, Nostr>) -> Result<(), ()> {
|
||||||
|
let client = &state.client;
|
||||||
|
let sub_id = SubscriptionId::new(id);
|
||||||
|
|
||||||
|
// Remove subscription
|
||||||
|
client.unsubscribe(sub_id).await;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use tauri_plugin_notification::NotificationExt;
|
|||||||
|
|
||||||
use crate::nostr::event::RichEvent;
|
use crate::nostr::event::RichEvent;
|
||||||
use crate::nostr::utils::parse_event;
|
use crate::nostr::utils::parse_event;
|
||||||
use crate::{Nostr, Settings};
|
use crate::{Nostr, Settings, NEWSFEED_NEG_LIMIT, NOTIFICATION_NEG_LIMIT};
|
||||||
|
|
||||||
#[derive(Serialize, Type)]
|
#[derive(Serialize, Type)]
|
||||||
pub struct Account {
|
pub struct Account {
|
||||||
@@ -242,8 +242,9 @@ pub async fn load_account(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Get user's contact list
|
// Get user's contact list
|
||||||
let contacts = client.get_contact_list(None).await.unwrap();
|
if let Ok(contacts) = client.get_contact_list(None).await {
|
||||||
*state.contact_list.lock().unwrap() = contacts;
|
*state.contact_list.lock().unwrap() = contacts
|
||||||
|
};
|
||||||
|
|
||||||
// Create a subscription for notification
|
// Create a subscription for notification
|
||||||
let sub_id = SubscriptionId::new("notification");
|
let sub_id = SubscriptionId::new("notification");
|
||||||
@@ -299,8 +300,9 @@ pub async fn load_account(
|
|||||||
let window = handle.get_window("main").unwrap();
|
let window = handle.get_window("main").unwrap();
|
||||||
let state = window.state::<Nostr>();
|
let state = window.state::<Nostr>();
|
||||||
let client = &state.client;
|
let client = &state.client;
|
||||||
|
let contact_list = state.contact_list.lock().unwrap().clone();
|
||||||
|
|
||||||
let filter = Filter::new()
|
let notification = Filter::new()
|
||||||
.pubkey(public_key)
|
.pubkey(public_key)
|
||||||
.kinds(vec![
|
.kinds(vec![
|
||||||
Kind::TextNote,
|
Kind::TextNote,
|
||||||
@@ -308,11 +310,39 @@ pub async fn load_account(
|
|||||||
Kind::Reaction,
|
Kind::Reaction,
|
||||||
Kind::ZapReceipt,
|
Kind::ZapReceipt,
|
||||||
])
|
])
|
||||||
.limit(500);
|
.limit(NOTIFICATION_NEG_LIMIT);
|
||||||
|
|
||||||
match client.reconcile(filter, NegentropyOptions::default()).await {
|
match client
|
||||||
Ok(_) => println!("Sync notification done."),
|
.reconcile(notification, NegentropyOptions::default())
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(_) => {
|
||||||
|
if handle.emit_to(EventTarget::Any, "synced", true).is_err() {
|
||||||
|
println!("Emit event failed.")
|
||||||
|
}
|
||||||
|
}
|
||||||
Err(_) => println!("Sync notification failed."),
|
Err(_) => println!("Sync notification failed."),
|
||||||
|
};
|
||||||
|
|
||||||
|
if !contact_list.is_empty() {
|
||||||
|
let authors: Vec<PublicKey> = contact_list.into_iter().map(|f| f.public_key).collect();
|
||||||
|
|
||||||
|
let newsfeed = Filter::new()
|
||||||
|
.authors(authors)
|
||||||
|
.kinds(vec![Kind::TextNote, Kind::Repost])
|
||||||
|
.limit(NEWSFEED_NEG_LIMIT);
|
||||||
|
|
||||||
|
match client
|
||||||
|
.reconcile(newsfeed, NegentropyOptions::default())
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(_) => {
|
||||||
|
if handle.emit_to(EventTarget::Any, "synced", true).is_err() {
|
||||||
|
println!("Emit event failed.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => println!("Sync newsfeed failed."),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -324,7 +354,7 @@ pub async fn load_account(
|
|||||||
let client = &state.client;
|
let client = &state.client;
|
||||||
|
|
||||||
// Handle notifications
|
// Handle notifications
|
||||||
if client
|
client
|
||||||
.handle_notifications(|notification| async {
|
.handle_notifications(|notification| async {
|
||||||
if let RelayPoolNotification::Message { message, .. } = notification {
|
if let RelayPoolNotification::Message { message, .. } = notification {
|
||||||
if let RelayMessage::Event {
|
if let RelayMessage::Event {
|
||||||
@@ -415,6 +445,24 @@ pub async fn load_account(
|
|||||||
{
|
{
|
||||||
println!("Emit new notification failed.")
|
println!("Emit new notification failed.")
|
||||||
}
|
}
|
||||||
|
} else if id.starts_with("column-") {
|
||||||
|
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_event",
|
||||||
|
RichEvent { raw, parsed },
|
||||||
|
)
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
println!("Emit new notification failed.")
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
println!("new event: {}", event.as_json())
|
println!("new event: {}", event.as_json())
|
||||||
}
|
}
|
||||||
@@ -425,10 +473,6 @@ pub async fn load_account(
|
|||||||
Ok(false)
|
Ok(false)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.is_ok()
|
|
||||||
{
|
|
||||||
print!("Listing for new event...");
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(true)
|
Ok(true)
|
||||||
|
|||||||
@@ -187,13 +187,15 @@ pub async fn is_contact_list_empty(state: State<'_, Nostr>) -> Result<bool, ()>
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub async fn check_contact(hex: &str, state: State<'_, Nostr>) -> Result<bool, ()> {
|
pub async fn check_contact(hex: &str, state: State<'_, Nostr>) -> Result<bool, String> {
|
||||||
let contact_list = state.contact_list.lock().unwrap();
|
let contact_list = state.contact_list.lock().unwrap();
|
||||||
let public_key = PublicKey::from_str(hex).unwrap();
|
|
||||||
|
|
||||||
match contact_list.iter().position(|x| x.public_key == public_key) {
|
match PublicKey::from_str(hex) {
|
||||||
Some(_) => Ok(true),
|
Ok(public_key) => match contact_list.iter().position(|x| x.public_key == public_key) {
|
||||||
None => Ok(false),
|
Some(_) => Ok(true),
|
||||||
|
None => Ok(false),
|
||||||
|
},
|
||||||
|
Err(err) => Err(err.to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,35 +1,39 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
|
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
|
||||||
"app": {
|
"app": {
|
||||||
"windows": [
|
"windows": [
|
||||||
{
|
{
|
||||||
"title": "Lume",
|
"title": "Lume",
|
||||||
"label": "main",
|
"label": "main",
|
||||||
"titleBarStyle": "Overlay",
|
"titleBarStyle": "Overlay",
|
||||||
"width": 500,
|
"width": 1045,
|
||||||
"height": 800,
|
"height": 800,
|
||||||
"minWidth": 500,
|
"minWidth": 500,
|
||||||
"minHeight": 800,
|
"minHeight": 800,
|
||||||
"hiddenTitle": true,
|
"hiddenTitle": true,
|
||||||
"windowEffects": {
|
"windowEffects": {
|
||||||
"state": "followsWindowActiveState",
|
"state": "followsWindowActiveState",
|
||||||
"effects": ["underWindowBackground"]
|
"effects": [
|
||||||
}
|
"underWindowBackground"
|
||||||
},
|
]
|
||||||
{
|
}
|
||||||
"title": "Lume Panel",
|
},
|
||||||
"label": "panel",
|
{
|
||||||
"url": "/panel",
|
"title": "Lume Panel",
|
||||||
"width": 350,
|
"label": "panel",
|
||||||
"height": 500,
|
"url": "/panel",
|
||||||
"fullscreen": false,
|
"width": 350,
|
||||||
"resizable": false,
|
"height": 500,
|
||||||
"visible": false,
|
"fullscreen": false,
|
||||||
"decorations": false,
|
"resizable": false,
|
||||||
"windowEffects": {
|
"visible": false,
|
||||||
"effects": ["popover"]
|
"decorations": false,
|
||||||
}
|
"windowEffects": {
|
||||||
}
|
"effects": [
|
||||||
]
|
"popover"
|
||||||
}
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user