feat: support nip-36

This commit is contained in:
reya 2024-04-16 07:49:44 +07:00
parent 09b143cb08
commit 94d400cab2
16 changed files with 253 additions and 150 deletions

View File

@ -19,6 +19,7 @@
"@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-popover": "^1.0.7", "@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-switch": "^1.0.3", "@radix-ui/react-switch": "^1.0.3",
"@radix-ui/react-tooltip": "^1.0.7",
"@tanstack/query-sync-storage-persister": "^5.29.0", "@tanstack/query-sync-storage-persister": "^5.29.0",
"@tanstack/react-query": "^5.29.0", "@tanstack/react-query": "^5.29.0",
"@tanstack/react-query-persist-client": "^5.29.0", "@tanstack/react-query-persist-client": "^5.29.0",

View File

@ -10,7 +10,7 @@ import { getCurrent } from "@tauri-apps/api/webviewWindow";
import { readTextFile } from "@tauri-apps/plugin-fs"; import { readTextFile } from "@tauri-apps/plugin-fs";
import { nanoid } from "nanoid"; import { nanoid } from "nanoid";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { useDebounce, useDebouncedCallback } from "use-debounce"; import { useDebouncedCallback } from "use-debounce";
import { VList, VListHandle } from "virtua"; import { VList, VListHandle } from "virtua";
export const Route = createFileRoute("/$account/home")({ export const Route = createFileRoute("/$account/home")({

View File

@ -2,8 +2,15 @@ import { Outlet, createRootRouteWithContext } from "@tanstack/react-router";
import { type Ark } from "@lume/ark"; import { type Ark } from "@lume/ark";
import { type QueryClient } from "@tanstack/react-query"; import { type QueryClient } from "@tanstack/react-query";
import { type Platform } from "@tauri-apps/plugin-os"; import { type Platform } from "@tauri-apps/plugin-os";
import { Account, Interests, Settings } from "@lume/types"; import type { Account, Interests, Settings } from "@lume/types";
import { Spinner } from "@lume/ui"; import { Spinner } from "@lume/ui";
import { type Descendant } from "slate";
type EditorElement = {
type: string;
children: Descendant[];
eventId?: string;
};
interface RouterContext { interface RouterContext {
ark: Ark; ark: Ark;
@ -13,6 +20,7 @@ interface RouterContext {
settings?: Settings; settings?: Settings;
interests?: Interests; interests?: Interests;
accounts?: Account[]; accounts?: Account[];
initialValue?: EditorElement[];
} }
export const Route = createRootRouteWithContext<RouterContext>()({ export const Route = createRootRouteWithContext<RouterContext>()({

View File

@ -69,6 +69,13 @@ function Screen() {
})); }));
}; };
const toggleNsfw = () => {
setNewSettings((prev) => ({
...prev,
nsfw: !newSettings.nsfw,
}));
};
const submit = async () => { const submit = async () => {
try { try {
// start loading // start loading
@ -167,18 +174,28 @@ function Screen() {
</p> </p>
</div> </div>
</div> </div>
<div className="flex w-full items-start justify-between gap-4 rounded-lg bg-neutral-50 px-5 py-4 dark:bg-neutral-950"> <div className="flex w-full items-start justify-between gap-4 rounded-lg bg-neutral-100 px-5 py-4 dark:bg-neutral-900">
<p className="text-sm text-neutral-700 dark:text-neutral-300"> <Switch.Root
There are many more settings you can configure from the 'Settings' checked={newSettings.nsfw}
Screen. Be sure to visit it later. onClick={() => toggleNsfw()}
</p> className="relative mt-1 h-7 w-12 shrink-0 cursor-default rounded-full bg-neutral-200 outline-none data-[state=checked]:bg-blue-500 dark:bg-neutral-800"
>
<Switch.Thumb className="block size-6 translate-x-0.5 rounded-full bg-white transition-transform duration-100 will-change-transform data-[state=checked]:translate-x-[19px]" />
</Switch.Root>
<div className="flex-1">
<h3 className="font-semibold">Filter sensitive content</h3>
<p className="text-sm text-neutral-700 dark:text-neutral-300">
By default, Lume will display all content which have Content
Warning tag, it's may include NSFW content.
</p>
</div>
</div> </div>
</div> </div>
<button <button
type="button" type="button"
onClick={submit} onClick={submit}
disabled={loading} disabled={loading}
className="inline-flex h-11 w-full shrink-0 items-center justify-center rounded-lg bg-blue-500 font-semibold text-white hover:bg-blue-600 disabled:opacity-50" className="mb-1 inline-flex h-11 w-full shrink-0 items-center justify-center rounded-lg bg-blue-500 font-semibold text-white hover:bg-blue-600 disabled:opacity-50"
> >
{t("global.continue")} {t("global.continue")}
</button> </button>

View File

@ -7,6 +7,7 @@ import { getCurrent } from "@tauri-apps/api/window";
import { UnlistenFn } from "@tauri-apps/api/event"; import { UnlistenFn } from "@tauri-apps/api/event";
import { useRouteContext } from "@tanstack/react-router"; import { useRouteContext } from "@tanstack/react-router";
import { Spinner } from "@lume/ui"; import { Spinner } from "@lume/ui";
import * as Tooltip from "@radix-ui/react-tooltip";
export function MediaButton({ className }: { className?: string }) { export function MediaButton({ className }: { className?: string }) {
const { ark } = useRouteContext({ strict: false }); const { ark } = useRouteContext({ strict: false });
@ -16,14 +17,13 @@ export function MediaButton({ className }: { className?: string }) {
const uploadToNostrBuild = async () => { const uploadToNostrBuild = async () => {
try { try {
// start loading
setLoading(true); setLoading(true);
const image = await ark.upload(); const image = await ark.upload();
insertImage(editor, image);
if (image) { // reset loading
insertImage(editor, image);
}
setLoading(false); setLoading(false);
} catch (e) { } catch (e) {
setLoading(false); setLoading(false);
@ -63,17 +63,29 @@ export function MediaButton({ className }: { className?: string }) {
}, []); }, []);
return ( return (
<button <Tooltip.Provider>
type="button" <Tooltip.Root delayDuration={150}>
onClick={() => uploadToNostrBuild()} <Tooltip.Trigger asChild>
disabled={loading} <button
className={cn("inline-flex items-center justify-center", className)} type="button"
> onClick={() => uploadToNostrBuild()}
{loading ? ( disabled={loading}
<Spinner className="size-5" /> className={cn("inline-flex items-center justify-center", className)}
) : ( >
<AddMediaIcon className="size-5" /> {loading ? (
)} <Spinner className="size-4" />
</button> ) : (
<AddMediaIcon className="size-4" />
)}
</button>
</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Content className="inline-flex h-7 select-none items-center justify-center rounded-md bg-neutral-950 px-3.5 text-sm text-neutral-50 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 dark:bg-neutral-50 dark:text-neutral-950">
Upload media
<Tooltip.Arrow className="fill-neutral-950 dark:fill-neutral-50" />
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
</Tooltip.Provider>
); );
} }

View File

@ -0,0 +1,40 @@
import { NsfwIcon } from "@lume/icons";
import { cn } from "@lume/utils";
import * as Tooltip from "@radix-ui/react-tooltip";
import { Dispatch, SetStateAction } from "react";
export function NsfwToggle({
nsfw,
setNsfw,
className,
}: {
nsfw: boolean;
setNsfw: Dispatch<SetStateAction<boolean>>;
className?: string;
}) {
return (
<Tooltip.Provider>
<Tooltip.Root delayDuration={150}>
<Tooltip.Trigger asChild>
<button
type="button"
onClick={() => setNsfw((prev) => !prev)}
className={cn(
"inline-flex items-center justify-center",
className,
nsfw ? "bg-blue-500 text-white" : "",
)}
>
<NsfwIcon className="size-4" />
</button>
</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Content className="inline-flex h-7 select-none items-center justify-center rounded-md bg-neutral-950 px-3.5 text-sm text-neutral-50 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 dark:bg-neutral-50 dark:text-neutral-950">
Mark as sensitive content
<Tooltip.Arrow className="fill-neutral-950 dark:fill-neutral-50" />
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
</Tooltip.Provider>
);
}

View File

@ -1,4 +1,4 @@
import { LoaderIcon, TrashIcon } from "@lume/icons"; import { ComposeFilledIcon, NsfwIcon, TrashIcon } from "@lume/icons";
import { import {
Portal, Portal,
cn, cn,
@ -35,11 +35,11 @@ import { Spinner, User } from "@lume/ui";
import { nip19 } from "nostr-tools"; import { nip19 } from "nostr-tools";
import { queryOptions, useSuspenseQuery } from "@tanstack/react-query"; import { queryOptions, useSuspenseQuery } from "@tanstack/react-query";
import { invoke } from "@tauri-apps/api/core"; import { invoke } from "@tauri-apps/api/core";
import { NsfwToggle } from "./-components/nsfw";
type EditorElement = { type EditorSearch = {
type: string; reply_to: string;
children: Descendant[]; quote: boolean;
eventId?: string;
}; };
const contactQueryOptions = queryOptions({ const contactQueryOptions = queryOptions({
@ -51,46 +51,48 @@ const contactQueryOptions = queryOptions({
}); });
export const Route = createFileRoute("/editor/")({ export const Route = createFileRoute("/editor/")({
loader: ({ context }) => validateSearch: (search: Record<string, string>): EditorSearch => {
context.queryClient.ensureQueryData(contactQueryOptions), return {
reply_to: search.reply_to,
quote: search.quote === "true" ?? false,
};
},
beforeLoad: async ({ search }) => {
return {
initialValue: search.quote
? [
{
type: "paragraph",
children: [{ text: "" }],
},
{
type: "event",
eventId: `nostr:${nip19.noteEncode(search.reply_to)}`,
children: [{ text: "" }],
},
{
type: "paragraph",
children: [{ text: "" }],
},
]
: [
{
type: "paragraph",
children: [{ text: "" }],
},
],
};
},
loader: ({ context }) => {
context.queryClient.ensureQueryData(contactQueryOptions);
},
component: Screen, component: Screen,
pendingComponent: Pending, pendingComponent: Pending,
}); });
function Screen() { function Screen() {
// @ts-ignore, useless
const { reply_to, quote } = Route.useSearch(); const { reply_to, quote } = Route.useSearch();
const { ark } = Route.useRouteContext(); const { ark, initialValue } = Route.useRouteContext();
let initialValue: EditorElement[];
if (quote) {
initialValue = [
{
type: "paragraph",
children: [{ text: "" }],
},
{
type: "event",
eventId: `nostr:${nip19.noteEncode(reply_to)}`,
children: [{ text: "" }],
},
{
type: "paragraph",
children: [{ text: "" }],
},
];
} else {
initialValue = [
{
type: "paragraph",
children: [{ text: "" }],
},
];
}
const ref = useRef<HTMLDivElement | null>();
const contacts = useSuspenseQuery(contactQueryOptions).data as Contact[];
const [t] = useTranslation(); const [t] = useTranslation();
const [editorValue, setEditorValue] = useState(initialValue); const [editorValue, setEditorValue] = useState(initialValue);
@ -98,10 +100,14 @@ function Screen() {
const [index, setIndex] = useState(0); const [index, setIndex] = useState(0);
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [nsfw, setNsfw] = useState(false);
const [editor] = useState(() => const [editor] = useState(() =>
withMentions(withNostrEvent(withImages(withReact(createEditor())))), withMentions(withNostrEvent(withImages(withReact(createEditor())))),
); );
const ref = useRef<HTMLDivElement | null>();
const contacts = useSuspenseQuery(contactQueryOptions).data as Contact[];
const filters = contacts const filters = contacts
?.filter((c) => ?.filter((c) =>
c?.profile.name?.toLowerCase().startsWith(search.toLowerCase()), c?.profile.name?.toLowerCase().startsWith(search.toLowerCase()),
@ -204,15 +210,25 @@ function Screen() {
> >
<div <div
data-tauri-drag-region data-tauri-drag-region
className="flex h-16 w-full shrink-0 items-center justify-end gap-3 px-2" className="flex h-14 w-full shrink-0 items-center justify-end gap-2 px-2"
> >
<MediaButton className="size-9 rounded-full bg-neutral-200 hover:bg-neutral-300 dark:bg-neutral-800 dark:hover:bg-neutral-700" /> <NsfwToggle
nsfw={nsfw}
setNsfw={setNsfw}
className="size-8 rounded-full bg-neutral-200 hover:bg-neutral-300 dark:bg-neutral-800 dark:hover:bg-neutral-700"
/>
<MediaButton className="size-8 rounded-full bg-neutral-200 hover:bg-neutral-300 dark:bg-neutral-800 dark:hover:bg-neutral-700" />
<button <button
type="button" type="button"
onClick={publish} onClick={publish}
className="inline-flex h-9 w-24 items-center justify-center rounded-full bg-blue-500 px-3 font-medium text-white hover:bg-blue-600" className="inline-flex h-8 w-max items-center justify-center gap-1 rounded-full bg-blue-500 px-3 text-sm font-medium text-white hover:bg-blue-600"
> >
{loading ? <Spinner className="size-5" /> : t("global.post")} {loading ? (
<Spinner className="size-4" />
) : (
<ComposeFilledIcon className="size-4" />
)}
{t("global.post")}
</button> </button>
</div> </div>
<div className="flex h-full min-h-0 w-full"> <div className="flex h-full min-h-0 w-full">

View File

@ -23,9 +23,11 @@ enum NSTORE_KEYS {
export class Ark { export class Ark {
public windows: WebviewWindow[]; public windows: WebviewWindow[];
public settings: Settings;
constructor() { constructor() {
this.windows = []; this.windows = [];
this.settings = undefined;
} }
public async get_all_accounts() { public async get_all_accounts() {
@ -144,7 +146,6 @@ export class Ark {
if (asOf && asOf > 0) until = asOf.toString(); if (asOf && asOf > 0) until = asOf.toString();
const dedup = true;
const seenIds = new Set<string>(); const seenIds = new Set<string>();
const dedupQueue = new Set<string>(); const dedupQueue = new Set<string>();
@ -155,31 +156,37 @@ export class Ark {
global: isGlobal, global: isGlobal,
}); });
if (dedup) { for (const event of nostrEvents) {
for (const event of nostrEvents) { const tags = event.tags
const tags = event.tags .filter((el) => el[0] === "e")
.filter((el) => el[0] === "e") ?.map((item) => item[1]);
?.map((item) => item[1]);
if (tags.length) { if (tags.length) {
for (const tag of tags) { for (const tag of tags) {
if (seenIds.has(tag)) { if (seenIds.has(tag)) {
dedupQueue.add(event.id); dedupQueue.add(event.id);
break; break;
}
seenIds.add(tag);
} }
seenIds.add(tag);
} }
} }
return nostrEvents
.filter((event) => !dedupQueue.has(event.id))
.sort((a, b) => b.created_at - a.created_at);
} }
return nostrEvents; const events = nostrEvents
.filter((event) => !dedupQueue.has(event.id))
.sort((a, b) => b.created_at - a.created_at);
if (this.settings?.nsfw) {
return events.filter(
(event) =>
event.tags.filter((event) => event[0] === "content-warning")
.length > 0,
);
}
return events;
} catch (e) { } catch (e) {
console.error(String(e)); console.info(String(e));
return []; return [];
} }
} }
@ -229,7 +236,12 @@ export class Ark {
return nostrEvents.sort((a, b) => b.created_at - a.created_at); return nostrEvents.sort((a, b) => b.created_at - a.created_at);
} }
public async publish(content: string, reply_to?: string, quote?: boolean) { public async publish(
content: string,
reply_to?: string,
quote?: boolean,
nsfw?: boolean,
) {
try { try {
const g = await generateContentTags(content); const g = await generateContentTags(content);
@ -238,26 +250,34 @@ export class Ark {
if (reply_to) { if (reply_to) {
const replyEvent = await this.get_event(reply_to); const replyEvent = await this.get_event(reply_to);
const relayHint =
replyEvent.tags.find((ev) => ev[0] === "e")?.[0][2] ?? "";
if (quote) { if (quote) {
eventTags.push([ eventTags.push(["e", replyEvent.id, relayHint, "mention"]);
"e",
replyEvent.id,
replyEvent.relay || "",
"mention",
]);
} else { } else {
const rootEvent = replyEvent.tags.find((ev) => ev[3] === "root"); const rootEvent = replyEvent.tags.find((ev) => ev[3] === "root");
if (rootEvent) { if (rootEvent) {
eventTags.push(["e", rootEvent[1], rootEvent[2] || "", "root"]); eventTags.push([
"e",
rootEvent[1],
rootEvent[2] || relayHint,
"root",
]);
} }
eventTags.push(["e", replyEvent.id, replyEvent.relay || "", "reply"]); eventTags.push(["e", replyEvent.id, relayHint, "reply"]);
eventTags.push(["p", replyEvent.pubkey]); eventTags.push(["p", replyEvent.pubkey]);
} }
} }
if (nsfw) {
eventTags.push(["L", "content-warning"]);
eventTags.push(["l", "reason", "content-warning"]);
eventTags.push(["content-warning", "nsfw"]);
}
const cmd: string = await invoke("publish", { const cmd: string = await invoke("publish", {
content: eventContent, content: eventContent,
tags: eventTags, tags: eventTags,
@ -605,6 +625,7 @@ export class Ark {
key: NSTORE_KEYS.settings, key: NSTORE_KEYS.settings,
}); });
const settings: Settings = cmd ? JSON.parse(cmd) : null; const settings: Settings = cmd ? JSON.parse(cmd) : null;
this.settings = settings;
return settings; return settings;
} catch { } catch {
const defaultSettings: Settings = { const defaultSettings: Settings = {
@ -612,7 +633,9 @@ export class Ark {
enhancedPrivacy: false, enhancedPrivacy: false,
notification: false, notification: false,
zap: false, zap: false,
nsfw: false,
}; };
this.settings = defaultSettings;
return defaultSettings; return defaultSettings;
} }
} }

View File

@ -123,3 +123,4 @@ export * from "./src/laurel";
export * from "./src/quote"; export * from "./src/quote";
export * from "./src/key"; export * from "./src/key";
export * from "./src/remote"; export * from "./src/remote";
export * from "./src/nsfw";

View File

@ -1,20 +1,13 @@
export function AddMediaIcon(props: JSX.IntrinsicElements["svg"]) { export function AddMediaIcon(props: JSX.IntrinsicElements["svg"]) {
return ( return (
<svg <svg width="24" height="24" fill="none" viewBox="0 0 24 24" {...props}>
xmlns="http://www.w3.org/2000/svg" <path
width="24" stroke="currentColor"
height="24" strokeLinecap="round"
fill="none" strokeLinejoin="round"
viewBox="0 0 24 24" strokeWidth="1.5"
{...props} d="M15.25 8.75v-4a2 2 0 0 0-2-2h-8.5a2 2 0 0 0-2 2v8.5a2 2 0 0 0 2 2h4M3.1 11.9l1.794-1.176a2 2 0 0 1 2.206.01l1.279.852M6 6.25h.5m8 8.75h.5M6.75 6.25a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0Zm7 6.95v3.6l2.8-1.8-2.8-1.8Zm5.5 8.05h-8.5a2 2 0 0 1-2-2v-8.5a2 2 0 0 1 2-2h8.5a2 2 0 0 1 2 2v8.5a2 2 0 0 1-2 2Z"
> />
<path </svg>
stroke="currentColor" );
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M19 22v-3m0 0v-3m0 3h-3m3 0h3m0-5.648V11l-.001-1m-9.464 11H10c-.756 0-1.41 0-1.983-.01M22 10H21c-1.393 0-2.09 0-2.676.06A11.5 11.5 0 008.06 20.324c-.02.2-.034.415-.043.665M22 10c-.008-2.15-.068-3.336-.544-4.27a5 5 0 00-2.185-2.185C18.2 3 16.8 3 14 3h-4c-2.8 0-4.2 0-5.27.545A5 5 0 002.545 5.73C2 6.8 2 8.2 2 11v2c0 2.8 0 4.2.545 5.27a5 5 0 002.185 2.185c.78.398 1.738.505 3.287.534M7.5 9.5a1 1 0 110-2 1 1 0 010 2z"
/>
</svg>
);
} }

View File

@ -0,0 +1,13 @@
export function NsfwIcon(props: JSX.IntrinsicElements["svg"]) {
return (
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" {...props}>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
d="M4.75 18.75v1.5a1 1 0 0 0 1 1h12.5a1 1 0 0 0 1-1v-1.5a2 2 0 0 0-2-2H6.75a2 2 0 0 0-2 2Zm2-2V12a5.25 5.25 0 0 1 10.5 0v4.75M12 1.75v1.025M21.225 12h1.025M2.775 12H1.75m16.773-6.523.725-.725m-13.771.725-.725-.725M12 16.75v-3"
/>
</svg>
);
}

View File

@ -3,6 +3,8 @@ export interface Settings {
enhancedPrivacy: boolean; enhancedPrivacy: boolean;
autoUpdate: boolean; autoUpdate: boolean;
zap: boolean; zap: boolean;
nsfw: boolean;
[key: string]: string | number | boolean;
} }
export interface Keys { export interface Keys {

View File

@ -20,7 +20,6 @@ export const Note = {
Pin: NotePin, Pin: NotePin,
Content: NoteContent, Content: NoteContent,
Zap: NoteZap, Zap: NoteZap,
Pin: NotePin,
Child: NoteChild, Child: NoteChild,
Thread: NoteThread, Thread: NoteThread,
}; };

15
pnpm-lock.yaml generated
View File

@ -87,6 +87,9 @@ importers:
'@radix-ui/react-switch': '@radix-ui/react-switch':
specifier: ^1.0.3 specifier: ^1.0.3
version: 1.0.3(@types/react-dom@18.2.24)(@types/react@18.2.75)(react-dom@18.2.0)(react@18.2.0) version: 1.0.3(@types/react-dom@18.2.24)(@types/react@18.2.75)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-tooltip':
specifier: ^1.0.7
version: 1.0.7(@types/react-dom@18.2.24)(@types/react@18.2.75)(react-dom@18.2.0)(react@18.2.0)
'@tanstack/query-sync-storage-persister': '@tanstack/query-sync-storage-persister':
specifier: ^5.29.0 specifier: ^5.29.0
version: 5.29.0 version: 5.29.0
@ -259,7 +262,7 @@ importers:
version: 1.0.7(@types/react-dom@18.2.24)(@types/react@18.2.75)(react-dom@18.2.0)(react@18.2.0) version: 1.0.7(@types/react-dom@18.2.24)(@types/react@18.2.75)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-tooltip': '@radix-ui/react-tooltip':
specifier: ^1.0.7 specifier: ^1.0.7
version: 1.0.7(@types/react@18.2.75)(react-dom@18.2.0)(react@18.2.0) version: 1.0.7(@types/react-dom@18.2.24)(@types/react@18.2.75)(react-dom@18.2.0)(react@18.2.0)
'@tanstack/react-query': '@tanstack/react-query':
specifier: ^5.29.0 specifier: ^5.29.0
version: 5.29.0(react@18.2.0) version: 5.29.0(react@18.2.0)
@ -410,7 +413,7 @@ importers:
version: 1.0.7(@types/react-dom@18.2.24)(@types/react@18.2.75)(react-dom@18.2.0)(react@18.2.0) version: 1.0.7(@types/react-dom@18.2.24)(@types/react@18.2.75)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-tooltip': '@radix-ui/react-tooltip':
specifier: ^1.0.7 specifier: ^1.0.7
version: 1.0.7(@types/react@18.2.75)(react-dom@18.2.0)(react@18.2.0) version: 1.0.7(@types/react-dom@18.2.24)(@types/react@18.2.75)(react-dom@18.2.0)(react@18.2.0)
'@tanstack/react-query': '@tanstack/react-query':
specifier: ^5.29.0 specifier: ^5.29.0
version: 5.29.0(react@18.2.0) version: 5.29.0(react@18.2.0)
@ -2283,7 +2286,7 @@ packages:
react-dom: 18.2.0(react@18.2.0) react-dom: 18.2.0(react@18.2.0)
dev: false dev: false
/@radix-ui/react-tooltip@1.0.7(@types/react@18.2.75)(react-dom@18.2.0)(react@18.2.0): /@radix-ui/react-tooltip@1.0.7(@types/react-dom@18.2.24)(@types/react@18.2.75)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-lPh5iKNFVQ/jav/j6ZrWq3blfDJ0OH9R6FlNUHPMqdLuQ9vwDgFsRxvl8b7Asuy5c8xmoojHUxKHQSOAvMHxyw==} resolution: {integrity: sha512-lPh5iKNFVQ/jav/j6ZrWq3blfDJ0OH9R6FlNUHPMqdLuQ9vwDgFsRxvl8b7Asuy5c8xmoojHUxKHQSOAvMHxyw==}
peerDependencies: peerDependencies:
'@types/react': '*' '@types/react': '*'
@ -2308,8 +2311,9 @@ packages:
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.24)(@types/react@18.2.75)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.24)(@types/react@18.2.75)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-slot': 1.0.2(@types/react@18.2.75)(react@18.2.0) '@radix-ui/react-slot': 1.0.2(@types/react@18.2.75)(react@18.2.0)
'@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.75)(react@18.2.0) '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.75)(react@18.2.0)
'@radix-ui/react-visually-hidden': 1.0.3(@types/react@18.2.75)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-visually-hidden': 1.0.3(@types/react-dom@18.2.24)(@types/react@18.2.75)(react-dom@18.2.0)(react@18.2.0)
'@types/react': 18.2.75 '@types/react': 18.2.75
'@types/react-dom': 18.2.24
react: 18.2.0 react: 18.2.0
react-dom: 18.2.0(react@18.2.0) react-dom: 18.2.0(react@18.2.0)
dev: false dev: false
@ -2416,7 +2420,7 @@ packages:
react: 18.2.0 react: 18.2.0
dev: false dev: false
/@radix-ui/react-visually-hidden@1.0.3(@types/react@18.2.75)(react-dom@18.2.0)(react@18.2.0): /@radix-ui/react-visually-hidden@1.0.3(@types/react-dom@18.2.24)(@types/react@18.2.75)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-D4w41yN5YRKtu464TLnByKzMDG/JlMPHtfZgQAu9v6mNakUqGUI9vUrfQKz8NK41VMm/xbZbh76NUTVtIYqOMA==} resolution: {integrity: sha512-D4w41yN5YRKtu464TLnByKzMDG/JlMPHtfZgQAu9v6mNakUqGUI9vUrfQKz8NK41VMm/xbZbh76NUTVtIYqOMA==}
peerDependencies: peerDependencies:
'@types/react': '*' '@types/react': '*'
@ -2432,6 +2436,7 @@ packages:
'@babel/runtime': 7.24.4 '@babel/runtime': 7.24.4
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.24)(@types/react@18.2.75)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.24)(@types/react@18.2.75)(react-dom@18.2.0)(react@18.2.0)
'@types/react': 18.2.75 '@types/react': 18.2.75
'@types/react-dom': 18.2.24
react: 18.2.0 react: 18.2.0
react-dom: 18.2.0(react@18.2.0) react-dom: 18.2.0(react@18.2.0)
dev: false dev: false

View File

@ -126,8 +126,6 @@ fn main() {
nostr::event::get_event_thread, nostr::event::get_event_thread,
nostr::event::publish, nostr::event::publish,
nostr::event::repost, nostr::event::repost,
nostr::event::upvote,
nostr::event::downvote,
commands::folder::show_in_folder, commands::folder::show_in_folder,
commands::folder::get_accounts, commands::folder::get_accounts,
commands::opg::fetch_opg, commands::opg::fetch_opg,

View File

@ -222,16 +222,15 @@ pub async fn get_event_thread(id: &str, state: State<'_, Nostr>) -> Result<Vec<E
#[tauri::command] #[tauri::command]
pub async fn publish( pub async fn publish(
content: &str, content: &str,
tags: Vec<Vec<String>>, tags: Vec<Vec<&str>>,
state: State<'_, Nostr>, state: State<'_, Nostr>,
) -> Result<String, String> { ) -> Result<String, String> {
let client = &state.client; let client = &state.client;
let final_tags = tags.into_iter().map(|val| Tag::parse(&val).unwrap()); let final_tags = tags.into_iter().map(|val| Tag::parse(&val).unwrap());
if let Ok(event_id) = client.publish_text_note(content, final_tags).await { match client.publish_text_note(content, final_tags).await {
Ok(event_id.to_bech32().unwrap()) Ok(event_id) => Ok(event_id.to_bech32().unwrap()),
} else { Err(err) => Err(err.to_string()),
Err("Publish text note failed".into())
} }
} }
@ -246,27 +245,3 @@ pub async fn repost(raw: &str, state: State<'_, Nostr>) -> Result<EventId, Strin
Err("Repost failed".into()) Err("Repost failed".into())
} }
} }
#[tauri::command]
pub async fn upvote(raw: &str, state: State<'_, Nostr>) -> Result<EventId, String> {
let client = &state.client;
let event = Event::from_json(raw).unwrap();
if let Ok(event_id) = client.like(&event).await {
Ok(event_id)
} else {
Err("Upvote failed".into())
}
}
#[tauri::command]
pub async fn downvote(raw: &str, state: State<'_, Nostr>) -> Result<EventId, String> {
let client = &state.client;
let event = Event::from_json(raw).unwrap();
if let Ok(event_id) = client.dislike(&event).await {
Ok(event_id)
} else {
Err("Downvote failed".into())
}
}