feat: add bell

This commit is contained in:
reya 2024-05-07 08:29:58 +07:00
parent c843626bca
commit afb7c87fa3
7 changed files with 1436 additions and 1381 deletions

View File

@ -5,7 +5,7 @@ import type { EventColumns, LumeColumn } from "@lume/types";
import { createFileRoute } from "@tanstack/react-router";
import { listen } from "@tauri-apps/api/event";
import { resolveResource } from "@tauri-apps/api/path";
import { getCurrent } from "@tauri-apps/api/webviewWindow";
import { getCurrent } from "@tauri-apps/api/window";
import { readTextFile } from "@tauri-apps/plugin-fs";
import { nanoid } from "nanoid";
import { useEffect, useRef, useState } from "react";
@ -13,173 +13,179 @@ import { useDebouncedCallback } from "use-debounce";
import { VList, type VListHandle } from "virtua";
export const Route = createFileRoute("/$account/home")({
beforeLoad: async ({ context }) => {
const ark = context.ark;
const resourcePath = await resolveResource("resources/system_columns.json");
const systemColumns: LumeColumn[] = JSON.parse(
await readTextFile(resourcePath),
);
const userColumns = await ark.get_columns();
beforeLoad: async ({ context }) => {
try {
const ark = context.ark;
const resourcePath = await resolveResource(
"resources/system_columns.json",
);
const systemColumns: LumeColumn[] = JSON.parse(
await readTextFile(resourcePath),
);
const userColumns = await ark.get_columns();
return {
storedColumns: !userColumns.length ? systemColumns : userColumns,
};
},
component: Screen,
return {
storedColumns: !userColumns.length ? systemColumns : userColumns,
};
} catch (e) {
console.error(String(e));
}
},
component: Screen,
});
function Screen() {
const vlistRef = useRef<VListHandle>(null);
const vlistRef = useRef<VListHandle>(null);
const { account } = Route.useParams();
const { ark, storedColumns } = Route.useRouteContext();
const { account } = Route.useParams();
const { ark, storedColumns } = Route.useRouteContext();
const [selectedIndex, setSelectedIndex] = useState(-1);
const [columns, setColumns] = useState(storedColumns);
const [isScroll, setIsScroll] = useState(false);
const [isResize, setIsResize] = useState(false);
const [selectedIndex, setSelectedIndex] = useState(-1);
const [columns, setColumns] = useState(storedColumns);
const [isScroll, setIsScroll] = useState(false);
const [isResize, setIsResize] = useState(false);
const goLeft = () => {
const prevIndex = Math.max(selectedIndex - 1, 0);
setSelectedIndex(prevIndex);
vlistRef.current.scrollToIndex(prevIndex, {
align: "center",
});
};
const goLeft = () => {
const prevIndex = Math.max(selectedIndex - 1, 0);
setSelectedIndex(prevIndex);
vlistRef.current.scrollToIndex(prevIndex, {
align: "center",
});
};
const goRight = () => {
const nextIndex = Math.min(selectedIndex + 1, columns.length - 1);
setSelectedIndex(nextIndex);
vlistRef.current.scrollToIndex(nextIndex, {
align: "center",
});
};
const goRight = () => {
const nextIndex = Math.min(selectedIndex + 1, columns.length - 1);
setSelectedIndex(nextIndex);
vlistRef.current.scrollToIndex(nextIndex, {
align: "center",
});
};
const add = useDebouncedCallback((column: LumeColumn) => {
// update col label
column.label = `${column.label}-${nanoid()}`;
const add = useDebouncedCallback((column: LumeColumn) => {
// update col label
column.label = `${column.label}-${nanoid()}`;
// create new cols
const cols = [...columns];
const openColIndex = cols.findIndex((col) => col.label === "open");
const newCols = [
...cols.slice(0, openColIndex),
column,
...cols.slice(openColIndex),
];
// create new cols
const cols = [...columns];
const openColIndex = cols.findIndex((col) => col.label === "open");
const newCols = [
...cols.slice(0, openColIndex),
column,
...cols.slice(openColIndex),
];
setColumns(newCols);
setSelectedIndex(newCols.length);
setIsScroll(true);
setColumns(newCols);
setSelectedIndex(newCols.length);
setIsScroll(true);
// scroll to the newest column
vlistRef.current.scrollToIndex(newCols.length - 1, {
align: "end",
});
}, 150);
// scroll to the newest column
vlistRef.current.scrollToIndex(newCols.length - 1, {
align: "end",
});
}, 150);
const remove = useDebouncedCallback((label: string) => {
const newCols = columns.filter((t) => t.label !== label);
const remove = useDebouncedCallback((label: string) => {
const newCols = columns.filter((t) => t.label !== label);
setColumns(newCols);
setSelectedIndex(newCols.length);
setIsScroll(true);
setColumns(newCols);
setSelectedIndex(newCols.length);
setIsScroll(true);
// scroll to the first column
vlistRef.current.scrollToIndex(newCols.length - 1, {
align: "start",
});
}, 150);
// scroll to the first column
vlistRef.current.scrollToIndex(newCols.length - 1, {
align: "start",
});
}, 150);
const updateName = useDebouncedCallback((label: string, title: string) => {
const currentColIndex = columns.findIndex((col) => col.label === label);
const updateName = useDebouncedCallback((label: string, title: string) => {
const currentColIndex = columns.findIndex((col) => col.label === label);
const updatedCol = Object.assign({}, columns[currentColIndex]);
updatedCol.name = title;
const updatedCol = Object.assign({}, columns[currentColIndex]);
updatedCol.name = title;
const newCols = columns.slice();
newCols[currentColIndex] = updatedCol;
const newCols = columns.slice();
newCols[currentColIndex] = updatedCol;
setColumns(newCols);
}, 150);
setColumns(newCols);
}, 150);
const startResize = useDebouncedCallback(
() => setIsResize((prev) => !prev),
150,
);
const startResize = useDebouncedCallback(
() => setIsResize((prev) => !prev),
150,
);
useEffect(() => {
// save state
ark.set_columns(columns);
}, [columns]);
useEffect(() => {
// save state
ark.set_columns(columns);
}, [columns]);
useEffect(() => {
let unlistenColEvent: Awaited<ReturnType<typeof listen>> | undefined =
undefined;
let unlistenWindowResize: Awaited<ReturnType<typeof listen>> | undefined =
undefined;
useEffect(() => {
let unlistenColEvent: Awaited<ReturnType<typeof listen>> | undefined =
undefined;
let unlistenWindowResize: Awaited<ReturnType<typeof listen>> | undefined =
undefined;
(async () => {
if (unlistenColEvent && unlistenWindowResize) return;
(async () => {
if (unlistenColEvent && unlistenWindowResize) return;
unlistenColEvent = await listen<EventColumns>("columns", (data) => {
if (data.payload.type === "add") add(data.payload.column);
if (data.payload.type === "remove") remove(data.payload.label);
if (data.payload.type === "set_title")
updateName(data.payload.label, data.payload.title);
});
unlistenColEvent = await listen<EventColumns>("columns", (data) => {
if (data.payload.type === "add") add(data.payload.column);
if (data.payload.type === "remove") remove(data.payload.label);
if (data.payload.type === "set_title")
updateName(data.payload.label, data.payload.title);
});
unlistenWindowResize = await getCurrent().listen("tauri://resize", () => {
startResize();
});
})();
unlistenWindowResize = await getCurrent().listen("tauri://resize", () => {
startResize();
});
})();
return () => {
if (unlistenColEvent) unlistenColEvent();
if (unlistenWindowResize) unlistenWindowResize();
};
}, []);
return () => {
if (unlistenColEvent) unlistenColEvent();
if (unlistenWindowResize) unlistenWindowResize();
};
}, []);
return (
<div className="h-full w-full">
<VList
ref={vlistRef}
horizontal
tabIndex={-1}
itemSize={440}
overscan={3}
onScroll={() => setIsScroll(true)}
onScrollEnd={() => setIsScroll(false)}
className="scrollbar-none h-full w-full overflow-x-auto focus:outline-none"
>
{columns.map((column) => (
<Col
key={column.label}
column={column}
account={account}
isScroll={isScroll}
isResize={isResize}
/>
))}
</VList>
<Toolbar>
<div className="flex items-center gap-1">
<button
type="button"
onClick={() => goLeft()}
className="inline-flex size-8 items-center justify-center rounded-full text-neutral-800 hover:bg-black/10 dark:text-neutral-200 dark:hover:bg-white/10"
>
<ArrowLeftIcon className="size-5" />
</button>
<button
type="button"
onClick={() => goRight()}
className="inline-flex size-8 items-center justify-center rounded-full text-neutral-800 hover:bg-black/10 dark:text-neutral-200 dark:hover:bg-white/10"
>
<ArrowRightIcon className="size-5" />
</button>
</div>
</Toolbar>
</div>
);
return (
<div className="h-full w-full">
<VList
ref={vlistRef}
horizontal
tabIndex={-1}
itemSize={440}
overscan={3}
onScroll={() => setIsScroll(true)}
onScrollEnd={() => setIsScroll(false)}
className="scrollbar-none h-full w-full overflow-x-auto focus:outline-none"
>
{columns.map((column) => (
<Col
key={column.label}
column={column}
account={account}
isScroll={isScroll}
isResize={isResize}
/>
))}
</VList>
<Toolbar>
<div className="flex items-center gap-1">
<button
type="button"
onClick={() => goLeft()}
className="inline-flex size-8 items-center justify-center rounded-full text-neutral-800 hover:bg-black/10 dark:text-neutral-200 dark:hover:bg-white/10"
>
<ArrowLeftIcon className="size-5" />
</button>
<button
type="button"
onClick={() => goRight()}
className="inline-flex size-8 items-center justify-center rounded-full text-neutral-800 hover:bg-black/10 dark:text-neutral-200 dark:hover:bg-white/10"
>
<ArrowRightIcon className="size-5" />
</button>
</div>
</Toolbar>
</div>
);
}

View File

@ -1,118 +1,183 @@
import { BellIcon, ComposeFilledIcon, PlusIcon, SearchIcon } from "@lume/icons";
import { Event, Kind } from "@lume/types";
import { User } from "@lume/ui";
import { cn } from "@lume/utils";
import {
cn,
decodeZapInvoice,
displayNpub,
sendNativeNotification,
} from "@lume/utils";
import { Outlet, createFileRoute } from "@tanstack/react-router";
import { UnlistenFn } from "@tauri-apps/api/event";
import { getCurrent } from "@tauri-apps/api/window";
import { useEffect, useState } from "react";
export const Route = createFileRoute("/$account")({
component: Screen,
beforeLoad: async ({ context }) => {
const ark = context.ark;
const accounts = await ark.get_all_accounts();
return { accounts };
},
component: Screen,
});
function Screen() {
const { account } = Route.useParams();
const { ark, platform } = Route.useRouteContext();
const { ark, platform } = Route.useRouteContext();
const navigate = Route.useNavigate();
const navigate = Route.useNavigate();
return (
<div className="flex h-screen w-screen flex-col">
<div
data-tauri-drag-region
className={cn(
"flex h-11 shrink-0 items-center justify-between pr-2",
platform === "macos" ? "ml-2 pl-20" : "pl-4",
)}
>
<div className="flex items-center gap-3">
<Accounts />
<button
type="button"
onClick={() => navigate({ to: "/landing" })}
className="inline-flex size-8 items-center justify-center rounded-full bg-black/10 text-neutral-800 hover:bg-black/20 dark:bg-white/10 dark:text-neutral-200 dark:hover:bg-white/20"
>
<PlusIcon className="size-5" />
</button>
</div>
<div className="flex items-center gap-2">
<button
type="button"
onClick={() => ark.open_editor()}
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"
>
<ComposeFilledIcon className="size-4" />
New Post
</button>
<button
type="button"
onClick={() => ark.open_activity(account)}
className="relative inline-flex size-8 items-center justify-center rounded-full text-neutral-800 hover:bg-black/10 dark:text-neutral-200 dark:hover:bg-white/10"
>
<BellIcon className="size-5" />
{/* <span className="absolute right-0 top-0 block size-2 rounded-full bg-teal-500 ring-1 ring-black/5"></span> */}
</button>
<button
type="button"
onClick={() => ark.open_search()}
className="inline-flex size-8 items-center justify-center rounded-full text-neutral-800 hover:bg-black/10 dark:text-neutral-200 dark:hover:bg-white/10"
>
<SearchIcon className="size-5" />
</button>
<div id="toolbar" />
</div>
</div>
<div className="flex-1">
<Outlet />
</div>
</div>
);
return (
<div className="flex h-screen w-screen flex-col">
<div
data-tauri-drag-region
className={cn(
"flex h-11 shrink-0 items-center justify-between pr-2",
platform === "macos" ? "ml-2 pl-20" : "pl-4",
)}
>
<div className="flex items-center gap-3">
<Accounts />
<button
type="button"
onClick={() => navigate({ to: "/landing" })}
className="inline-flex size-8 items-center justify-center rounded-full bg-black/10 text-neutral-800 hover:bg-black/20 dark:bg-white/10 dark:text-neutral-200 dark:hover:bg-white/20"
>
<PlusIcon className="size-5" />
</button>
</div>
<div className="flex items-center gap-2">
<button
type="button"
onClick={() => ark.open_editor()}
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"
>
<ComposeFilledIcon className="size-4" />
New Post
</button>
<Bell />
<button
type="button"
onClick={() => ark.open_search()}
className="inline-flex size-8 items-center justify-center rounded-full text-neutral-800 hover:bg-black/10 dark:text-neutral-200 dark:hover:bg-white/10"
>
<SearchIcon className="size-5" />
</button>
<div id="toolbar" />
</div>
</div>
<div className="flex-1">
<Outlet />
</div>
</div>
);
}
export function Accounts() {
const navigate = Route.useNavigate();
const { ark } = Route.useRouteContext();
const { account } = Route.useParams();
function Accounts() {
const navigate = Route.useNavigate();
const { ark, accounts } = Route.useRouteContext();
const { account } = Route.useParams();
const [accounts, setAccounts] = useState<string[]>([]);
const changeAccount = async (npub: string) => {
if (npub === account) return;
const changeAccount = async (npub: string) => {
if (npub === account) return;
const select = await ark.load_selected_account(npub);
if (select)
return navigate({ to: "/$account/home", params: { account: npub } });
};
const select = await ark.load_selected_account(npub);
useEffect(() => {
async function getAllAccounts() {
const data = await ark.get_all_accounts();
if (data) setAccounts(data);
}
if (select) {
return navigate({ to: "/$account/home", params: { account: npub } });
}
};
getAllAccounts();
}, []);
return (
<div data-tauri-drag-region className="flex items-center gap-3">
{accounts.map((user) => (
<button key={user} type="button" onClick={() => changeAccount(user)}>
<User.Provider pubkey={user}>
<User.Root
className={cn(
"rounded-full",
user === account
? "ring-1 ring-teal-500 ring-offset-2 ring-offset-neutral-200 dark:ring-offset-neutral-950"
: "",
)}
>
<User.Avatar
className={cn(
"aspect-square h-auto rounded-full object-cover",
user === account ? "w-7" : "w-8",
)}
/>
</User.Root>
</User.Provider>
</button>
))}
</div>
);
return (
<div data-tauri-drag-region className="flex items-center gap-3">
{accounts.map((user) => (
<button key={user} type="button" onClick={() => changeAccount(user)}>
<User.Provider pubkey={user}>
<User.Root
className={cn(
"rounded-full",
user === account
? "ring-1 ring-teal-500 ring-offset-2 ring-offset-neutral-200 dark:ring-offset-neutral-950"
: "",
)}
>
<User.Avatar
className={cn(
"aspect-square h-auto rounded-full object-cover",
user === account ? "w-7" : "w-8",
)}
/>
</User.Root>
</User.Provider>
</button>
))}
</div>
);
}
function Bell() {
const { ark } = Route.useRouteContext();
const { account } = Route.useParams();
const [isRing, setIsRing] = useState(false);
useEffect(() => {
let unlisten: UnlistenFn = undefined;
async function listenNotify() {
unlisten = await getCurrent().listen<string>(
"activity",
async (payload) => {
setIsRing(true);
const event: Event = JSON.parse(payload.payload);
const user = await ark.get_profile(event.pubkey);
const userName =
user.display_name || user.name || displayNpub(event.pubkey, 16);
switch (event.kind) {
case Kind.Text: {
sendNativeNotification("Mentioned you in a note", userName);
break;
}
case Kind.Repost: {
sendNativeNotification("Reposted your note", userName);
break;
}
case Kind.ZapReceipt: {
const amount = decodeZapInvoice(event.tags);
sendNativeNotification(
`Zapped ₿ ${amount.bitcoinFormatted}`,
userName,
);
break;
}
default:
break;
}
},
);
}
if (!unlisten) listenNotify();
return () => {
if (unlisten) unlisten();
};
}, []);
return (
<button
type="button"
onClick={() => {
setIsRing(false);
ark.open_activity(account);
}}
className="relative inline-flex size-8 items-center justify-center rounded-full text-neutral-800 hover:bg-black/10 dark:text-neutral-200 dark:hover:bg-white/10"
>
<BellIcon className="size-5" />
{isRing ? (
<span className="absolute right-0 top-0 block size-2 rounded-full bg-teal-500 ring-1 ring-black/5" />
) : null}
</button>
);
}

View File

@ -7,38 +7,38 @@ import type { Platform } from "@tauri-apps/plugin-os";
import type { Descendant } from "slate";
type EditorElement = {
type: string;
children: Descendant[];
eventId?: string;
type: string;
children: Descendant[];
eventId?: string;
};
interface RouterContext {
// System
ark: Ark;
queryClient: QueryClient;
// App info
platform?: Platform;
locale?: string;
// Settings
settings?: Settings;
interests?: Interests;
// Profile
accounts?: Account[];
profile?: Metadata;
// Editor
initialValue?: EditorElement[];
// System
ark: Ark;
queryClient: QueryClient;
// App info
platform?: Platform;
locale?: string;
// Settings
settings?: Settings;
interests?: Interests;
// Profile
accounts?: string[];
profile?: Metadata;
// Editor
initialValue?: EditorElement[];
}
export const Route = createRootRouteWithContext<RouterContext>()({
component: () => <Outlet />,
pendingComponent: Pending,
wrapInSuspense: true,
component: () => <Outlet />,
pendingComponent: Pending,
wrapInSuspense: true,
});
function Pending() {
return (
<div className="flex h-screen w-screen flex-col items-center justify-center">
<Spinner className="size-5" />
</div>
);
return (
<div className="flex h-screen w-screen flex-col items-center justify-center">
<Spinner className="size-5" />
</div>
);
}

View File

@ -4,131 +4,115 @@ import { Link } from "@tanstack/react-router";
import { createFileRoute, redirect } from "@tanstack/react-router";
import { invoke } from "@tauri-apps/api/core";
import { useState } from "react";
import { toast } from "sonner";
export const Route = createFileRoute("/")({
beforeLoad: async ({ context }) => {
const ark = context.ark;
const accounts = await ark.get_all_accounts();
beforeLoad: async ({ context }) => {
const ark = context.ark;
const accounts = await ark.get_all_accounts();
// Run notification service
if (accounts.length > 0) {
await invoke("run_notification", { accounts });
}
if (!accounts.length) {
throw redirect({
to: "/landing",
replace: true,
});
}
switch (accounts.length) {
// Guest account
case 0:
throw redirect({
to: "/landing",
replace: true,
});
// Only 1 account, skip account selection screen
case 1: {
const account = accounts[0];
const loadedAccount = await ark.load_selected_account(account);
// Run notification service
await invoke("run_notification", { accounts });
if (loadedAccount) {
throw redirect({
to: "/$account/home",
params: { account },
replace: true,
});
}
break;
}
// Account selection
default:
return { accounts };
}
},
component: Screen,
return { accounts };
},
component: Screen,
});
function Screen() {
const navigate = Route.useNavigate();
const context = Route.useRouteContext();
const navigate = Route.useNavigate();
const { ark, accounts } = Route.useRouteContext();
const [loading, setLoading] = useState(false);
const [loading, setLoading] = useState(false);
const select = async (npub: string) => {
setLoading(true);
const select = async (npub: string) => {
try {
setLoading(true);
const ark = context.ark;
const loadAccount = await ark.load_selected_account(npub);
const loadAccount = await ark.load_selected_account(npub);
if (loadAccount) {
return navigate({
to: "/$account/home",
params: { account: npub },
replace: true,
});
}
} catch (e) {
setLoading(false);
toast.error(String(e));
}
};
if (loadAccount) {
return navigate({
to: "/$account/home",
params: { account: npub },
replace: true,
});
}
};
const currentDate = new Date().toLocaleString("default", {
weekday: "long",
month: "long",
day: "numeric",
});
const currentDate = new Date().toLocaleString("default", {
weekday: "long",
month: "long",
day: "numeric",
});
return (
<div className="relative flex h-full w-full items-center justify-center">
<div className="relative z-20 flex flex-col items-center gap-16">
<div className="text-center text-white">
<h2 className="mb-1 text-2xl">{currentDate}</h2>
<h2 className="text-2xl font-semibold">Welcome back!</h2>
</div>
<div className="flex items-center justify-center gap-6">
{loading ? (
<div className="inline-flex size-6 items-center justify-center">
<Spinner className="size-6" />
</div>
) : (
<>
{context.accounts.map((account) => (
<button
type="button"
key={account}
onClick={() => select(account)}
>
<User.Provider pubkey={account}>
<User.Root className="flex h-36 w-32 flex-col items-center justify-center gap-4 rounded-2xl p-2 hover:bg-white/10 dark:hover:bg-black/10">
<User.Avatar className="size-20 rounded-full object-cover" />
<User.Name className="max-w-[5rem] truncate text-lg font-medium leading-tight text-white" />
</User.Root>
</User.Provider>
</button>
))}
<Link to="/landing">
<div className="flex h-36 w-32 flex-col items-center justify-center gap-4 rounded-2xl p-2 text-white hover:bg-white/10 dark:hover:bg-black/10">
<div className="flex size-20 items-center justify-center rounded-full bg-white/20 dark:bg-black/20">
<PlusIcon className="size-5" />
</div>
<p className="text-lg font-medium leading-tight">Add</p>
</div>
</Link>
</>
)}
</div>
</div>
<div className="absolute z-10 h-full w-full bg-white/10 backdrop-blur-lg dark:bg-black/10" />
<div className="absolute inset-0 h-full w-full">
<img
src="/lock-screen.jpg"
srcSet="/lock-screen@2x.jpg 2x"
alt="Lock Screen Background"
className="h-full w-full object-cover"
/>
<a
href="https://njump.me/nprofile1qqs9tuz9jpn57djg7nxunhyvuvk69g5zqaxdpvpqt9hwqv7395u9rpg6zq5uw"
target="_blank"
className="absolute bottom-3 right-3 z-50 rounded-md bg-white/20 px-2 py-1 text-xs font-medium text-white dark:bg-black/20"
rel="noreferrer"
>
Design by NoGood
</a>
</div>
</div>
);
return (
<div className="relative flex h-full w-full items-center justify-center">
<div className="relative z-20 flex flex-col items-center gap-16">
<div className="text-center text-white">
<h2 className="mb-1 text-2xl">{currentDate}</h2>
<h2 className="text-2xl font-semibold">Welcome back!</h2>
</div>
<div className="flex items-center justify-center gap-6">
{loading ? (
<div className="inline-flex size-6 items-center justify-center">
<Spinner className="size-6" />
</div>
) : (
<>
{accounts.map((account) => (
<button
type="button"
key={account}
onClick={() => select(account)}
>
<User.Provider pubkey={account}>
<User.Root className="flex h-36 w-32 flex-col items-center justify-center gap-4 rounded-2xl p-2 hover:bg-white/10 dark:hover:bg-black/10">
<User.Avatar className="size-20 rounded-full object-cover" />
<User.Name className="max-w-[5rem] truncate text-lg font-medium leading-tight text-white" />
</User.Root>
</User.Provider>
</button>
))}
<Link to="/landing">
<div className="flex h-36 w-32 flex-col items-center justify-center gap-4 rounded-2xl p-2 text-white hover:bg-white/10 dark:hover:bg-black/10">
<div className="flex size-20 items-center justify-center rounded-full bg-white/20 dark:bg-black/20">
<PlusIcon className="size-5" />
</div>
<p className="text-lg font-medium leading-tight">Add</p>
</div>
</Link>
</>
)}
</div>
</div>
<div className="absolute z-10 h-full w-full bg-white/10 backdrop-blur-lg dark:bg-black/10" />
<div className="absolute inset-0 h-full w-full">
<img
src="/lock-screen.jpg"
srcSet="/lock-screen@2x.jpg 2x"
alt="Lock Screen Background"
className="h-full w-full object-cover"
/>
<a
href="https://njump.me/nprofile1qqs9tuz9jpn57djg7nxunhyvuvk69g5zqaxdpvpqt9hwqv7395u9rpg6zq5uw"
target="_blank"
className="absolute bottom-3 right-3 z-50 rounded-md bg-white/20 px-2 py-1 text-xs font-medium text-white dark:bg-black/20"
rel="noreferrer"
>
Design by NoGood
</a>
</div>
</div>
);
}

File diff suppressed because it is too large Load Diff

View File

@ -1,166 +1,167 @@
export interface Settings {
notification: boolean;
enhancedPrivacy: boolean;
autoUpdate: boolean;
zap: boolean;
nsfw: boolean;
[key: string]: string | number | boolean;
notification: boolean;
enhancedPrivacy: boolean;
autoUpdate: boolean;
zap: boolean;
nsfw: boolean;
[key: string]: string | number | boolean;
}
export interface Keys {
npub: string;
nsec: string;
npub: string;
nsec: string;
}
export enum Kind {
Metadata = 0,
Text = 1,
RecommendRelay = 2,
Contacts = 3,
Repost = 6,
Reaction = 7,
// NIP-89: App Metadata
AppRecommendation = 31989,
AppHandler = 31990,
// #TODO: Add all nostr kinds
Metadata = 0,
Text = 1,
RecommendRelay = 2,
Contacts = 3,
Repost = 6,
Reaction = 7,
ZapReceipt = 9735,
// NIP-89: App Metadata
AppRecommendation = 31989,
AppHandler = 31990,
// #TODO: Add all nostr kinds
}
export interface Event {
id: string;
pubkey: string;
created_at: number;
kind: Kind;
tags: string[][];
content: string;
sig: string;
relay?: string;
id: string;
pubkey: string;
created_at: number;
kind: Kind;
tags: string[][];
content: string;
sig: string;
relay?: string;
}
export interface EventWithReplies extends Event {
replies: Array<Event>;
replies: Array<Event>;
}
export interface Metadata {
name?: string;
display_name?: string;
about?: string;
website?: string;
picture?: string;
banner?: string;
nip05?: string;
lud06?: string;
lud16?: string;
name?: string;
display_name?: string;
about?: string;
website?: string;
picture?: string;
banner?: string;
nip05?: string;
lud06?: string;
lud16?: string;
}
export interface Contact {
pubkey: string;
profile: Metadata;
pubkey: string;
profile: Metadata;
}
export interface Account {
npub: string;
nsec?: string;
contacts?: string[];
interests?: Interests;
npub: string;
nsec?: string;
contacts?: string[];
interests?: Interests;
}
export interface Interests {
hashtags: string[];
users: string[];
words: string[];
hashtags: string[];
users: string[];
words: string[];
}
export interface RichContent {
parsed: string;
images: string[];
videos: string[];
links: string[];
notes: string[];
parsed: string;
images: string[];
videos: string[];
links: string[];
notes: string[];
}
export interface AppRouteSearch {
account: string;
account: string;
}
export interface ColumnRouteSearch {
account: string;
label: string;
name: string;
redirect?: string;
account: string;
label: string;
name: string;
redirect?: string;
}
export interface LumeColumn {
label: string;
name: string;
content: URL | string;
description?: string;
author?: string;
logo?: string;
cover?: string;
coverRetina?: string;
featured?: boolean;
label: string;
name: string;
content: URL | string;
description?: string;
author?: string;
logo?: string;
cover?: string;
coverRetina?: string;
featured?: boolean;
}
export interface EventColumns {
type: "add" | "remove" | "update" | "left" | "right" | "set_title";
label?: string;
title?: string;
column?: LumeColumn;
type: "add" | "remove" | "update" | "left" | "right" | "set_title";
label?: string;
title?: string;
column?: LumeColumn;
}
export interface Opengraph {
url: string;
title?: string;
description?: string;
image?: string;
url: string;
title?: string;
description?: string;
image?: string;
}
export interface NostrBuildResponse {
ok: boolean;
data?: {
message: string;
status: string;
data: Array<{
blurhash: string;
dimensions: {
width: number;
height: number;
};
mime: string;
name: string;
sha256: string;
size: number;
url: string;
}>;
};
ok: boolean;
data?: {
message: string;
status: string;
data: Array<{
blurhash: string;
dimensions: {
width: number;
height: number;
};
mime: string;
name: string;
sha256: string;
size: number;
url: string;
}>;
};
}
export interface NIP11 {
name: string;
description: string;
pubkey: string;
contact: string;
supported_nips: number[];
software: string;
version: string;
limitation: {
[key: string]: string | number | boolean;
};
relay_countries: string[];
language_tags: string[];
tags: string[];
posting_policy: string;
payments_url: string;
icon: string[];
name: string;
description: string;
pubkey: string;
contact: string;
supported_nips: number[];
software: string;
version: string;
limitation: {
[key: string]: string | number | boolean;
};
relay_countries: string[];
language_tags: string[];
tags: string[];
posting_policy: string;
payments_url: string;
icon: string[];
}
export interface NIP05 {
names: {
[key: string]: string;
};
nip46: {
[key: string]: {
[key: string]: string[];
};
};
names: {
[key: string]: string;
};
nip46: {
[key: string]: {
[key: string]: string[];
};
};
}

View File

@ -23,12 +23,7 @@ pub fn run_notification(accounts: Vec<String>, app: tauri::AppHandle) -> Result<
.collect();
let subscription = Filter::new()
.pubkeys(pubkeys)
.kinds(vec![
Kind::TextNote,
Kind::Repost,
Kind::ZapReceipt,
Kind::EncryptedDirectMessage,
])
.kinds(vec![Kind::TextNote, Kind::Repost, Kind::ZapReceipt])
.since(Timestamp::now());
let activity_id = SubscriptionId::new("activity");
@ -47,7 +42,8 @@ pub fn run_notification(accounts: Vec<String>, app: tauri::AppHandle) -> Result<
} = notification
{
if subscription_id == activity_id {
let _ = app.emit_to("main", "activity", event.as_json());
println!("new notification: {}", event.as_json());
let _ = app.emit("activity", event.as_json());
}
}
Ok(false)