wip: update design

This commit is contained in:
reya 2024-02-16 14:11:49 +07:00
parent 296b11b7b8
commit f28a7ae82f
20 changed files with 218 additions and 172 deletions

View File

@ -63,13 +63,13 @@ function Home() {
{data.map((item) => renderItem(item))}
</Virtualizer>
)}
<div className="flex h-16 items-center justify-center">
<div className="flex h-20 items-center justify-center">
{hasNextPage ? (
<button
type="button"
onClick={() => fetchNextPage()}
disabled={!hasNextPage || isFetchingNextPage}
className="inline-flex h-12 w-full items-center justify-center gap-2 rounded-xl bg-neutral-100 font-medium hover:bg-neutral-200 focus:outline-none dark:bg-neutral-900 dark:hover:bg-neutral-800"
className="inline-flex h-11 w-max items-center justify-center gap-2 rounded-full bg-neutral-100 px-3 font-medium hover:bg-neutral-200 focus:outline-none dark:bg-neutral-900 dark:hover:bg-neutral-800"
>
{isFetchingNextPage ? (
<LoaderIcon className="size-5 animate-spin" />

View File

@ -1,4 +1,6 @@
import { ReplyList, ThreadNote } from "@lume/ui";
import { createLazyFileRoute } from "@tanstack/react-router";
import { WindowVirtualizer } from "virtua";
export const Route = createLazyFileRoute("/events/$eventId")({
component: Event,
@ -7,5 +9,15 @@ export const Route = createLazyFileRoute("/events/$eventId")({
function Event() {
const { eventId } = Route.useParams();
return <div>{eventId}</div>;
return (
<div className="relative h-screen w-screen overflow-y-auto overflow-x-hidden">
<div data-tauri-drag-region className="h-11 w-full" />
<WindowVirtualizer>
<div className="px-6">
<ThreadNote eventId={eventId} />
<ReplyList eventId={eventId} />
</div>
</WindowVirtualizer>
</div>
);
}

View File

@ -1,4 +1,10 @@
import type { CurrentAccount, Event, Keys, Metadata } from "@lume/types";
import type {
CurrentAccount,
Event,
EventWithReplies,
Keys,
Metadata,
} from "@lume/types";
import { invoke } from "@tauri-apps/api/core";
import { WebviewWindow } from "@tauri-apps/api/webview";
@ -133,27 +139,27 @@ export class Ark {
}
}
public async repost(id: string, pubkey: string) {
public async repost(id: string, author: string) {
try {
const cmd: string = await invoke("repost", { id, pubkey });
const cmd: string = await invoke("repost", { id, pubkey: author });
return cmd;
} catch (e) {
console.error(String(e));
}
}
public async upvote(id: string, pubkey: string) {
public async upvote(id: string, author: string) {
try {
const cmd: string = await invoke("upvote", { id, pubkey });
const cmd: string = await invoke("upvote", { id, pubkey: author });
return cmd;
} catch (e) {
console.error(String(e));
}
}
public async downvote(id: string, pubkey: string) {
public async downvote(id: string, author: string) {
try {
const cmd: string = await invoke("downvote", { id, pubkey });
const cmd: string = await invoke("downvote", { id, pubkey: author });
return cmd;
} catch (e) {
console.error(String(e));
@ -162,8 +168,36 @@ export class Ark {
public async get_event_thread(id: string) {
try {
const cmd: Event[] = await invoke("get_event_thread", { id });
return cmd;
const events: EventWithReplies[] = await invoke("get_event_thread", {
id,
});
if (events.length > 0) {
const replies = new Set();
for (const event of events) {
const tags = event.tags.filter(
(el) => el[0] === "e" && el[1] !== id && el[3] !== "mention",
);
if (tags.length > 0) {
for (const tag of tags) {
const rootIndex = events.findIndex((el) => el.id === tag[1]);
if (rootIndex !== -1) {
const rootEvent = events[rootIndex];
if (rootEvent?.replies) {
rootEvent.replies.push(event);
} else {
rootEvent.replies = [event];
}
replies.add(event.id);
}
}
}
}
const cleanEvents = events.filter((ev) => !replies.has(ev.id));
return cleanEvents;
}
return events;
} catch (e) {
return [];
}
@ -250,7 +284,8 @@ export class Ark {
return new WebviewWindow(`event-${id}`, {
title: "Thread",
url: `/events/${id}`,
width: 600,
minWidth: 500,
width: 500,
height: 800,
hiddenTitle: true,
titleBarStyle: "overlay",
@ -261,7 +296,8 @@ export class Ark {
return new WebviewWindow(`user-${pubkey}`, {
title: "Profile",
url: `/users/${pubkey}`,
width: 600,
minWidth: 500,
width: 500,
height: 800,
hiddenTitle: true,
titleBarStyle: "overlay",

View File

@ -298,9 +298,9 @@ export function ReplyForm({
return (
<div className={cn("flex gap-3", className)}>
<User.Provider pubkey={ark.account.pubkey}>
<User.Provider pubkey={ark.account.npub}>
<User.Root>
<User.Avatar className="size-9 shrink-0 rounded-lg object-cover" />
<User.Avatar className="size-10 shrink-0 rounded-full object-cover" />
</User.Root>
</User.Provider>
<div className="flex-1">

View File

@ -6,6 +6,7 @@ export * from "./column";
// Note Primities
export * from "./note/primitives/text";
export * from "./note/primitives/repost";
export * from "./note/primitives/thread";
// Deprecated
export * from "./routes/event";

View File

@ -1,22 +1,50 @@
import { ArrowDownIcon, ArrowUpIcon } from "@lume/icons";
import { useState } from "react";
import { useNoteContext } from "../provider";
import { useArk } from "@lume/ark";
import { cn } from "@lume/utils";
export function NoteReaction() {
const ark = useArk();
const event = useNoteContext();
const [reaction, setReaction] = useState<"+" | "-">(null);
const up = async () => {
const res = await ark.upvote(event.id, event.pubkey);
if (res) setReaction("+");
};
const down = async () => {
const res = await ark.downvote(event.id, event.pubkey);
if (res) setReaction("-");
};
return (
<div className="inline-flex items-center gap-2">
<button
type="button"
className="inline-flex size-7 items-center justify-center rounded-full bg-neutral-100 text-neutral-700 hover:bg-blue-500 hover:text-white dark:bg-neutral-900 dark:text-neutral-300"
onClick={up}
disabled={!!reaction}
className={cn(
"inline-flex size-7 items-center justify-center rounded-full",
reaction === "+"
? "bg-blue-500 text-white"
: "bg-neutral-100 text-neutral-700 hover:bg-blue-500 hover:text-white dark:bg-neutral-900 dark:text-neutral-300",
)}
>
<ArrowUpIcon className="size-4" />
</button>
<button
type="button"
className="inline-flex size-7 items-center justify-center rounded-full bg-neutral-100 text-neutral-700 hover:bg-blue-500 hover:text-white dark:bg-neutral-900 dark:text-neutral-300"
onClick={down}
disabled={!!reaction}
className={cn(
"inline-flex size-7 items-center justify-center rounded-full",
reaction === "-"
? "bg-blue-500 text-white"
: "bg-neutral-100 text-neutral-700 hover:bg-blue-500 hover:text-white dark:bg-neutral-900 dark:text-neutral-300",
)}
>
<ArrowDownIcon className="size-4" />
</button>

View File

@ -7,8 +7,10 @@ import { useState } from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import { useNoteContext } from "../provider";
import { useArk } from "@lume/ark";
export function NoteRepost() {
const ark = useArk();
const event = useNoteContext();
const setEditorValue = useSetAtom(editorValueAtom);
const setIsEditorOpen = useSetAtom(editorAtom);
@ -23,7 +25,7 @@ export function NoteRepost() {
setLoading(true);
// repost
await event.repost(true);
await ark.repost(event.id, event.pubkey);
// update state
setLoading(false);

View File

@ -39,9 +39,7 @@ export function NoteContent({ className }: { className?: string }) {
const richContent = useMemo(() => {
if (event.kind !== Kind.Text) return content;
let parsedContent: string | ReactNode[] = stripHtml(
content.replace(/\n{2,}\s*/g, "\n"),
).result;
let parsedContent: string | ReactNode[] = stripHtml(content).result;
let linkPreview: string = undefined;
let images: string[] = [];
let videos: string[] = [];
@ -176,7 +174,7 @@ export function NoteContent({ className }: { className?: string }) {
);
parsedContent = reactStringReplace(parsedContent, "\n", () => {
return <div key={nanoid()} className="h-3" />;
return <div key={nanoid()} />;
});
if (typeof parsedContent[0] === "string") {

View File

@ -21,10 +21,7 @@ export function MentionNote({
const richContent = useMemo(() => {
if (!data) return "";
let parsedContent: string | ReactNode[] = data.content.replace(
/\n+/g,
"\n",
);
let parsedContent: string | ReactNode[] = data.content;
const text = parsedContent as string;
const words = text.split(/( |\n)/);
@ -106,11 +103,11 @@ export function MentionNote({
}
return (
<div className="my-1 flex w-full cursor-default flex-col rounded-lg border border-black/5 bg-neutral-100 dark:border-white/5 dark:bg-neutral-900">
<div className="my-1.5 flex w-full cursor-default flex-col rounded-xl bg-neutral-100 pt-1 ring-1 ring-black/5 dark:bg-neutral-900 dark:ring-white/5">
<User.Provider pubkey={data.pubkey}>
<User.Root className="flex h-10 items-center gap-2 px-3">
<User.Avatar className="size-6 shrink-0 rounded-md object-cover" />
<div className="inline-flex flex-1 gap-2">
<User.Avatar className="size-6 shrink-0 rounded-full object-cover" />
<div className="inline-flex flex-1 items-center gap-2">
<User.Name className="font-semibold text-neutral-900 dark:text-neutral-100" />
<span className="text-neutral-600 dark:text-neutral-400">·</span>
<User.Time
@ -127,16 +124,10 @@ export function MentionNote({
<div className="flex h-10 items-center justify-between px-3">
<a
href={`/events/${data.id}`}
className="text-sm text-blue-500 hover:text-blue-600"
className="text-blue-500 hover:text-blue-600"
>
{t("note.showMore")}
</a>
<button
type="button"
className="inline-flex size-6 items-center justify-center rounded-md bg-neutral-200 text-neutral-600 hover:bg-neutral-300 dark:bg-neutral-800 dark:text-neutral-400 dark:hover:bg-neutral-700"
>
<PinIcon className="size-4" />
</button>
</div>
) : (
<div className="h-3" />

View File

@ -32,8 +32,11 @@ export function NoteMenu() {
return (
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild>
<button type="button">
<HorizontalDotsIcon className="size-4 hover:text-blue-500 dark:text-neutral-200" />
<button
type="button"
className="text-neutral-500 hover:text-blue-500 dark:text-neutral-400"
>
<HorizontalDotsIcon className="size-5" />
</button>
</DropdownMenu.Trigger>
<DropdownMenu.Portal>

View File

@ -5,57 +5,60 @@ import { download } from "@tauri-apps/plugin-upload";
import { SyntheticEvent, useState } from "react";
export function ImagePreview({ url }: { url: string }) {
const [downloaded, setDownloaded] = useState(false);
const [downloaded, setDownloaded] = useState(false);
const downloadImage = async (e: { stopPropagation: () => void }) => {
try {
e.stopPropagation();
const downloadImage = async (e: { stopPropagation: () => void }) => {
try {
e.stopPropagation();
const downloadDirPath = await downloadDir();
const filename = url.substring(url.lastIndexOf("/") + 1);
await download(url, `${downloadDirPath}/${filename}`);
const downloadDirPath = await downloadDir();
const filename = url.substring(url.lastIndexOf("/") + 1);
await download(url, `${downloadDirPath}/${filename}`);
setDownloaded(true);
} catch (e) {
console.error(e);
}
};
setDownloaded(true);
} catch (e) {
console.error(e);
}
};
const open = async () => {
const name = new URL(url).pathname.split("/").pop();
return new WebviewWindow("image-viewer", {
url,
title: name,
});
};
const open = async () => {
const name = new URL(url).pathname.split("/").pop();
return new WebviewWindow("image-viewer", {
url,
title: name,
});
};
const fallback = (event: SyntheticEvent<HTMLImageElement, Event>) => {
event.currentTarget.src = "/fallback-image.jpg";
};
const fallback = (event: SyntheticEvent<HTMLImageElement, Event>) => {
event.currentTarget.src = "/fallback-image.jpg";
};
return (
// biome-ignore lint/a11y/useKeyWithClickEvents: <explanation>
<div onClick={open} className="relative mt-1 mb-2.5 group">
<img
src={url}
alt={url}
loading="lazy"
decoding="async"
style={{ contentVisibility: "auto" }}
onError={fallback}
className="object-cover w-full h-auto border rounded-xl border-neutral-200/50 dark:border-neutral-800/50"
/>
<button
type="button"
onClick={(e) => downloadImage(e)}
className="absolute z-10 items-center justify-center hidden size-10 bg-white/10 text-black/70 backdrop-blur-xl rounded-lg right-2 top-2 group-hover:inline-flex hover:bg-blue-500 hover:text-white"
>
{downloaded ? (
<CheckCircleIcon className="size-5" />
) : (
<DownloadIcon className="size-5" />
)}
</button>
</div>
);
return (
// biome-ignore lint/a11y/useKeyWithClickEvents: <explanation>
<div
onClick={open}
className="group relative my-1.5 rounded-xl ring-1 ring-black/5 dark:ring-white/5"
>
<img
src={url}
alt={url}
loading="lazy"
decoding="async"
style={{ contentVisibility: "auto" }}
onError={fallback}
className="h-auto w-full rounded-xl object-cover"
/>
<button
type="button"
onClick={(e) => downloadImage(e)}
className="absolute right-2 top-2 z-10 hidden size-10 items-center justify-center rounded-lg bg-white/20 text-black/70 backdrop-blur-2xl hover:bg-blue-500 hover:text-white group-hover:inline-flex"
>
{downloaded ? (
<CheckCircleIcon className="size-5" />
) : (
<DownloadIcon className="size-5" />
)}
</button>
</div>
);
}

View File

@ -10,7 +10,7 @@ export function LinkPreview({ url }: { url: string }) {
if (isLoading) {
return (
<div className="mb-2.5 mt-1 flex w-full flex-col overflow-hidden rounded-xl border border-black/5 bg-neutral-100 dark:border-white/5 dark:bg-neutral-900">
<div className="my-1.5 flex w-full flex-col overflow-hidden rounded-xl bg-neutral-100 ring-1 ring-black/5 dark:bg-neutral-900 dark:ring-white/5">
<div className="h-48 w-full shrink-0 animate-pulse bg-neutral-300 dark:bg-neutral-700" />
<div className="flex flex-col gap-2 px-3 py-3">
<div className="h-3 w-2/3 animate-pulse rounded bg-neutral-300 dark:bg-neutral-700" />
@ -29,7 +29,7 @@ export function LinkPreview({ url }: { url: string }) {
href={url}
target="_blank"
rel="noreferrer"
className="text-blue-500 hover:text-blue-600"
className="inline-block text-blue-500 hover:text-blue-600"
>
{url}
</a>
@ -42,7 +42,7 @@ export function LinkPreview({ url }: { url: string }) {
href={url}
target="_blank"
rel="noreferrer"
className="text-blue-500 hover:text-blue-600"
className="inline-block text-blue-500 hover:text-blue-600"
>
{url}
</a>
@ -54,7 +54,7 @@ export function LinkPreview({ url }: { url: string }) {
href={url}
target="_blank"
rel="noreferrer"
className="mb-2.5 mt-1 flex w-full flex-col overflow-hidden rounded-xl border border-black/5 bg-neutral-100 dark:border-white/5 dark:bg-neutral-900"
className="my-1.5 flex w-full flex-col overflow-hidden rounded-xl bg-neutral-100 ring-1 ring-black/5 dark:bg-neutral-900 dark:ring-white/5"
>
{isImage(data.image) ? (
<img

View File

@ -1,30 +1,30 @@
import {
MediaControlBar,
MediaController,
MediaMuteButton,
MediaPlayButton,
MediaTimeDisplay,
MediaTimeRange,
MediaControlBar,
MediaController,
MediaMuteButton,
MediaPlayButton,
MediaTimeDisplay,
MediaTimeRange,
} from "media-chrome/dist/react";
export function VideoPreview({ url }: { url: string }) {
return (
<div className="mt-1 mb-2.5 w-full rounded-xl overflow-hidden">
<MediaController>
<video
slot="media"
src={url}
preload="auto"
muted
className="w-full h-auto"
/>
<MediaControlBar>
<MediaPlayButton />
<MediaTimeRange />
<MediaTimeDisplay showDuration />
<MediaMuteButton />
</MediaControlBar>
</MediaController>
</div>
);
return (
<div className="my-1.5 w-full overflow-hidden rounded-xl ring-1 ring-black/5 dark:ring-white/5">
<MediaController>
<video
slot="media"
src={url}
preload="auto"
muted
className="h-auto w-full rounded-xl"
/>
<MediaControlBar>
<MediaPlayButton />
<MediaTimeRange />
<MediaTimeDisplay showDuration />
<MediaMuteButton />
</MediaControlBar>
</MediaController>
</div>
);
}

View File

@ -11,11 +11,11 @@ export function ThreadNote({ eventId }: { eventId: string }) {
return (
<Note.Provider event={data}>
<Note.Root className="flex flex-col rounded-xl bg-neutral-50 dark:bg-neutral-950">
<div className="flex h-16 items-center justify-between px-3">
<Note.Root className="flex flex-col">
<div className="flex h-16 items-center justify-between">
<User.Provider pubkey={data.pubkey}>
<User.Root className="flex h-16 flex-1 items-center gap-3">
<User.Avatar className="size-10 shrink-0 rounded-lg object-cover ring-1 ring-neutral-200/50 dark:ring-neutral-800/50" />
<User.Avatar className="size-11 shrink-0 rounded-full object-cover ring-1 ring-neutral-200/50 dark:ring-neutral-800/50" />
<div className="flex flex-1 flex-col">
<User.Name className="font-semibold text-neutral-900 dark:text-neutral-100" />
<div className="inline-flex items-center gap-2 text-sm text-neutral-600 dark:text-neutral-400">
@ -29,9 +29,9 @@ export function ThreadNote({ eventId }: { eventId: string }) {
<Note.Menu />
</div>
<Note.Thread className="mb-2" />
<Note.Content className="min-w-0 px-3" />
<div className="flex h-14 items-center justify-between px-3">
<Note.Pin />
<Note.Content className="min-w-0" />
<div className="flex h-14 items-center justify-between">
<Note.Reaction />
<div className="inline-flex items-center gap-4">
<Note.Repost />
<Note.Zap />

View File

@ -1,4 +1,3 @@
import { PinIcon } from "@lume/icons";
import { cn } from "@lume/utils";
import { useTranslation } from "react-i18next";
import { Note } from ".";
@ -19,7 +18,7 @@ export function NoteThread({ className }: { className?: string }) {
return (
<div className={cn("w-full", className)}>
<div className="flex h-min w-full flex-col gap-3 rounded-lg bg-neutral-100 p-3 dark:bg-neutral-900">
<div className="flex h-min w-full flex-col gap-3 rounded-xl bg-neutral-100 p-3 ring-1 ring-black/5 dark:bg-neutral-900 dark:ring-white/5">
{thread.rootEventId ? (
<Note.Child eventId={thread.rootEventId} isRoot />
) : null}
@ -27,17 +26,14 @@ export function NoteThread({ className }: { className?: string }) {
<Note.Child eventId={thread.replyEventId} />
) : null}
<div className="inline-flex items-center justify-between">
<a
href={`/events/${thread?.rootEventId || thread?.replyEventId}`}
<button
type="button"
onClick={() =>
ark.open_thread(thread.rootEventId || thread.rootEventId)
}
className="self-start text-blue-500 hover:text-blue-600"
>
{t("note.showThread")}
</a>
<button
type="button"
className="inline-flex size-6 items-center justify-center rounded-md bg-neutral-200 text-neutral-600 hover:bg-neutral-300 dark:bg-neutral-800 dark:text-neutral-400 dark:hover:bg-neutral-700"
>
<PinIcon className="size-4" />
</button>
</div>
</div>

View File

@ -2,8 +2,10 @@ import { cn } from "@lume/utils";
import * as HoverCard from "@radix-ui/react-hover-card";
import { User } from "../user";
import { useNoteContext } from "./provider";
import { useArk } from "@lume/ark";
export function NoteUser({ className }: { className?: string }) {
const ark = useArk();
const event = useNoteContext();
return (
@ -23,12 +25,12 @@ export function NoteUser({ className }: { className?: string }) {
</div>
<User.Time
time={event.created_at}
className="text-neutral-500 dark:text-neutral-400"
className="text-neutral-600 dark:text-neutral-400"
/>
</User.Root>
<HoverCard.Portal>
<HoverCard.Content
className="data-[side=bottom]:animate-slideUpAndFade w-[300px] rounded-xl bg-white p-5 shadow-lg shadow-neutral-500/20 data-[state=open]:transition-all dark:border dark:border-neutral-800 dark:bg-neutral-900 dark:shadow-none"
className="data-[side=bottom]:animate-slideUpAndFade w-[300px] rounded-xl bg-white p-3 shadow-lg shadow-neutral-500/20 data-[state=open]:transition-all dark:border dark:border-neutral-800 dark:bg-neutral-900 dark:shadow-none"
sideOffset={5}
>
<div className="flex flex-col gap-2">
@ -39,12 +41,12 @@ export function NoteUser({ className }: { className?: string }) {
<User.NIP05 className="text-neutral-600 dark:text-neutral-400" />
</div>
<User.About className="line-clamp-3" />
<a
href={`/users/${event.pubkey}`}
className="mt-3 inline-flex h-8 w-full items-center justify-center rounded-lg bg-neutral-100 text-sm font-medium hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800"
<button
onClick={() => ark.open_profile(event.pubkey)}
className="mt-2 inline-flex h-9 w-full items-center justify-center rounded-lg bg-neutral-100 text-sm font-medium hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800"
>
View profile
</a>
</button>
</div>
</div>
<HoverCard.Arrow className="fill-white dark:fill-neutral-800" />

View File

@ -1,11 +1,11 @@
import { useArk } from "@lume/ark";
import { LoaderIcon } from "@lume/icons";
import { cn } from "@lume/utils";
import { NDKKind, type NDKSubscription } from "@nostr-dev-kit/ndk";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { ReplyForm } from "./editor/replyForm";
import { Reply } from "./note/primitives/reply";
import { EventWithReplies } from "@lume/types";
export function ReplyList({
eventId,
@ -17,41 +17,14 @@ export function ReplyList({
const ark = useArk();
const [t] = useTranslation();
const [data, setData] = useState<null | NDKEventWithReplies[]>(null);
const [data, setData] = useState<null | EventWithReplies[]>(null);
useEffect(() => {
let sub: NDKSubscription = undefined;
let isCancelled = false;
async function fetchRepliesAndSub() {
const id = ark.getCleanEventId(eventId);
const events = await ark.getThreads(id);
if (!isCancelled) {
setData(events);
}
if (!sub) {
sub = ark.subscribe({
filter: {
"#e": [id],
kinds: [NDKKind.Text],
since: Math.floor(Date.now() / 1000),
},
closeOnEose: false,
cb: (event: NDKEventWithReplies) =>
setData((prev) => [event, ...prev]),
});
}
async function getReplies() {
const events = await ark.get_event_thread(eventId);
setData(events);
}
// subscribe for new replies
fetchRepliesAndSub();
return () => {
isCancelled = true;
if (sub) sub.stop();
};
getReplies();
}, [eventId]);
return (

View File

@ -26,6 +26,7 @@
"clipboard-manager:allow-write",
"clipboard-manager:allow-read",
"webview:allow-create-webview-window",
"webview:allow-create-webview",
{
"identifier": "http:default",
"allow": [

View File

@ -1 +1 @@
{"desktop-capability":{"identifier":"desktop-capability","description":"Capability for the desktop","context":"local","windows":["main","settings","event-*","user-*","column-*"],"permissions":["path:default","event:default","window:default","app:default","resources:default","menu:default","tray:default","theme:allow-set-theme","theme:allow-get-theme","notification:allow-is-permission-granted","notification:allow-request-permission","notification:default","os:allow-locale","os:allow-platform","updater:allow-check","updater:default","window:allow-start-dragging","store:allow-get","clipboard-manager:allow-write","clipboard-manager:allow-read","webview:allow-create-webview-window",{"identifier":"http:default","allow":[{"url":"http://**/"},{"url":"https://**/"}]},{"identifier":"fs:allow-read-text-file","allow":[{"path":"$RESOURCE/locales/*"}]}],"platforms":["linux","macOS","windows"]}}
{"desktop-capability":{"identifier":"desktop-capability","description":"Capability for the desktop","context":"local","windows":["main","settings","event-*","user-*","column-*"],"permissions":["path:default","event:default","window:default","app:default","resources:default","menu:default","tray:default","theme:allow-set-theme","theme:allow-get-theme","notification:allow-is-permission-granted","notification:allow-request-permission","notification:default","os:allow-locale","os:allow-platform","updater:allow-check","updater:default","window:allow-start-dragging","store:allow-get","clipboard-manager:allow-write","clipboard-manager:allow-read","webview:allow-create-webview-window","webview:allow-create-webview",{"identifier":"http:default","allow":[{"url":"http://**/"},{"url":"https://**/"}]},{"identifier":"fs:allow-read-text-file","allow":[{"path":"$RESOURCE/locales/*"}]}],"platforms":["linux","macOS","windows"]}}

View File

@ -87,7 +87,7 @@ fn main() {
.await
.expect("Failed to add bootstrap relay.");
client
.add_relay("wss://bostr.nokotaro.com")
.add_relay("wss://bostr.yonle.lecturify.net")
.await
.expect("Failed to add bootstrap relay.");