Release v4.1 (#229)
* refactor: remove custom icon packs * fix: command not work on windows * fix: make open_window command async * feat: improve commands * feat: improve * refactor: column * feat: improve thread column * feat: improve * feat: add stories column * feat: improve * feat: add search column * feat: add reset password * feat: add subscription * refactor: settings * chore: improve commands * fix: crash on production * feat: use tauri store plugin for cache * feat: new icon * chore: update icon for windows * chore: improve some columns * chore: polish code
This commit is contained in:
@@ -156,7 +156,7 @@ export const NostrAccount = {
|
||||
}
|
||||
},
|
||||
f2f: async (npub: string) => {
|
||||
const query = await commands.friendToFriend(npub);
|
||||
const query = await commands.copyFriend(npub);
|
||||
|
||||
if (query.status === "ok") {
|
||||
return query.data;
|
||||
|
||||
@@ -104,111 +104,6 @@ export class LumeEvent {
|
||||
}
|
||||
}
|
||||
|
||||
public async getEventReplies() {
|
||||
const query = await commands.getReplies(this.id);
|
||||
|
||||
if (query.status === "ok") {
|
||||
const events = query.data
|
||||
// Create Lume Events
|
||||
.map((item) => LumeEvent.from(item.raw, item.parsed))
|
||||
// Filter quote event
|
||||
.filter(
|
||||
(ev) =>
|
||||
!ev.tags.filter((t) => t[0] === "q" || t[3] === "mention").length,
|
||||
);
|
||||
|
||||
if (events.length > 1) {
|
||||
const removeQueues = new Set();
|
||||
|
||||
for (const event of events) {
|
||||
const tags = event.tags.filter(
|
||||
(t) => t[0] === "e" && t[1] !== this.id,
|
||||
);
|
||||
|
||||
if (tags.length === 1) {
|
||||
const index = events.findIndex((ev) => ev.id === tags[0][1]);
|
||||
|
||||
if (index !== -1) {
|
||||
const rootEvent = events[index];
|
||||
|
||||
if (rootEvent.replies?.length) {
|
||||
rootEvent.replies.push(event);
|
||||
} else {
|
||||
rootEvent.replies = [event];
|
||||
}
|
||||
|
||||
// Add current event to queue
|
||||
removeQueues.add(event.id);
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
for (const tag of tags) {
|
||||
const id = tag[1];
|
||||
const rootIndex = events.findIndex((ev) => ev.id === id);
|
||||
|
||||
if (rootIndex !== -1) {
|
||||
const rootEvent = events[rootIndex];
|
||||
|
||||
if (rootEvent.replies?.length) {
|
||||
const childIndex = rootEvent.replies.findIndex(
|
||||
(ev) => ev.id === id,
|
||||
);
|
||||
|
||||
if (childIndex !== -1) {
|
||||
const childEvent = rootEvent.replies[rootIndex];
|
||||
|
||||
if (childEvent.replies?.length) {
|
||||
childEvent.replies.push(event);
|
||||
} else {
|
||||
childEvent.replies = [event];
|
||||
}
|
||||
|
||||
// Add current event to queue
|
||||
removeQueues.add(event.id);
|
||||
}
|
||||
} else {
|
||||
rootEvent.replies = [event];
|
||||
// Add current event to queue
|
||||
removeQueues.add(event.id);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return events.filter((ev) => !removeQueues.has(ev.id));
|
||||
}
|
||||
|
||||
return events;
|
||||
} else {
|
||||
console.error(query.error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public async listenEventReply() {
|
||||
const query = await commands.listenEventReply(this.id);
|
||||
|
||||
if (query.status === "ok") {
|
||||
return query.data;
|
||||
} else {
|
||||
throw new Error(query.error);
|
||||
}
|
||||
}
|
||||
|
||||
public async unlistenEventReply() {
|
||||
const query = await commands.unlisten(this.id);
|
||||
|
||||
if (query.status === "ok") {
|
||||
return query.data;
|
||||
} else {
|
||||
throw new Error(query.error);
|
||||
}
|
||||
}
|
||||
|
||||
public async zap(amount: number, message: string) {
|
||||
const query = await commands.zapEvent(this.id, amount.toString(), message);
|
||||
|
||||
|
||||
@@ -1,28 +1,66 @@
|
||||
import { type Result, type RichEvent, commands } from "@/commands.gen";
|
||||
import type { NostrEvent } from "@/types";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { NostrQuery } from "../query";
|
||||
import { experimental_createPersister } from "@tanstack/query-persist-client-core";
|
||||
import { nip19 } from "nostr-tools";
|
||||
import { LumeEvent } from "../event";
|
||||
|
||||
export function useEvent(id: string, relayHint?: string) {
|
||||
const { isLoading, isError, data } = useQuery({
|
||||
export function useEvent(id: string) {
|
||||
const { isLoading, isError, error, data } = useQuery({
|
||||
queryKey: ["event", id],
|
||||
queryFn: async () => {
|
||||
try {
|
||||
const event = await NostrQuery.getEvent(id, relayHint);
|
||||
return event;
|
||||
// Validate ID
|
||||
let normalizeId: string = id
|
||||
.replace("nostr:", "")
|
||||
.replace(/[^\w\s]/gi, "");
|
||||
|
||||
// Define query
|
||||
let query: Result<RichEvent, string>;
|
||||
let relayHint: string;
|
||||
|
||||
if (normalizeId.startsWith("nevent1")) {
|
||||
const decoded = nip19.decode(normalizeId);
|
||||
|
||||
if (decoded.type === "nevent") {
|
||||
relayHint = decoded.data.relays[0];
|
||||
normalizeId = decoded.data.id;
|
||||
}
|
||||
}
|
||||
|
||||
// Build query
|
||||
if (relayHint) {
|
||||
try {
|
||||
const url = new URL(relayHint);
|
||||
query = await commands.getEventFrom(normalizeId, url.toString());
|
||||
} catch {
|
||||
query = await commands.getEvent(normalizeId);
|
||||
}
|
||||
} else {
|
||||
query = await commands.getEvent(normalizeId);
|
||||
}
|
||||
|
||||
if (query.status === "ok") {
|
||||
const data = query.data;
|
||||
const raw: NostrEvent = JSON.parse(data.raw);
|
||||
|
||||
if (data.parsed) {
|
||||
raw.meta = data.parsed;
|
||||
}
|
||||
|
||||
return new LumeEvent(raw);
|
||||
} else {
|
||||
throw new Error(query.error);
|
||||
}
|
||||
} catch (e) {
|
||||
throw new Error(e);
|
||||
throw new Error(String(e));
|
||||
}
|
||||
},
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnMount: false,
|
||||
refetchOnReconnect: false,
|
||||
staleTime: Number.POSITIVE_INFINITY,
|
||||
retry: 2,
|
||||
persister: experimental_createPersister({
|
||||
storage: localStorage,
|
||||
maxAge: 1000 * 60 * 60 * 12, // 12 hours
|
||||
}),
|
||||
retry: false,
|
||||
});
|
||||
|
||||
return { isLoading, isError, data };
|
||||
return { isLoading, isError, error, data };
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { commands } from "@/commands.gen";
|
||||
import type { Metadata } from "@/types";
|
||||
import { experimental_createPersister } from "@tanstack/query-persist-client-core";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { nip19 } from "nostr-tools";
|
||||
|
||||
export function useProfile(pubkey: string, embed?: string) {
|
||||
const {
|
||||
@@ -11,30 +11,37 @@ export function useProfile(pubkey: string, embed?: string) {
|
||||
} = useQuery({
|
||||
queryKey: ["profile", pubkey],
|
||||
queryFn: async () => {
|
||||
try {
|
||||
if (embed) return JSON.parse(embed) as Metadata;
|
||||
if (embed) {
|
||||
const metadata: Metadata = JSON.parse(embed);
|
||||
return metadata;
|
||||
}
|
||||
|
||||
const normalize = pubkey.replace("nostr:", "").replace(/[^\w\s]/gi, "");
|
||||
const query = await commands.getProfile(normalize);
|
||||
let normalizeId = pubkey.replace("nostr:", "").replace(/[^\w\s]/gi, "");
|
||||
let relayHint: string;
|
||||
|
||||
if (query.status === "ok") {
|
||||
return JSON.parse(query.data) as Metadata;
|
||||
} else {
|
||||
throw new Error(query.error);
|
||||
if (normalizeId.startsWith("nprofile")) {
|
||||
const decoded = nip19.decode(normalizeId);
|
||||
|
||||
if (decoded.type === "nprofile") {
|
||||
relayHint = decoded.data.relays[0];
|
||||
normalizeId = decoded.data.pubkey;
|
||||
}
|
||||
} catch (e) {
|
||||
throw new Error(e);
|
||||
}
|
||||
|
||||
console.log(relayHint);
|
||||
const query = await commands.getProfile(normalizeId);
|
||||
|
||||
if (query.status === "ok") {
|
||||
return JSON.parse(query.data) as Metadata;
|
||||
} else {
|
||||
throw new Error(query.error);
|
||||
}
|
||||
},
|
||||
refetchOnMount: false,
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnReconnect: false,
|
||||
staleTime: Number.POSITIVE_INFINITY,
|
||||
retry: 2,
|
||||
persister: experimental_createPersister({
|
||||
storage: localStorage,
|
||||
maxAge: 1000 * 60 * 60 * 24, // 24 hours
|
||||
}),
|
||||
retry: false,
|
||||
});
|
||||
|
||||
return { isLoading, isError, profile };
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { type Result, type RichEvent, commands } from "@/commands.gen";
|
||||
import type { LumeColumn, Metadata, NostrEvent, Relay } from "@/types";
|
||||
import { resolveResource } from "@tauri-apps/api/path";
|
||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||
import { open } from "@tauri-apps/plugin-dialog";
|
||||
import { readFile, readTextFile } from "@tauri-apps/plugin-fs";
|
||||
import { readFile } from "@tauri-apps/plugin-fs";
|
||||
import { relaunch } from "@tauri-apps/plugin-process";
|
||||
import { nip19 } from "nostr-tools";
|
||||
import { LumeEvent } from "./event";
|
||||
@@ -178,38 +176,6 @@ export const NostrQuery = {
|
||||
}
|
||||
}
|
||||
},
|
||||
getUserEvents: async (pubkey: string, asOf?: number) => {
|
||||
const until: string = asOf && asOf > 0 ? asOf.toString() : undefined;
|
||||
const query = await commands.getEventsBy(pubkey, until);
|
||||
|
||||
if (query.status === "ok") {
|
||||
const data = toLumeEvents(query.data);
|
||||
return data;
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
getLocalEvents: async (asOf?: number) => {
|
||||
const until: string = asOf && asOf > 0 ? asOf.toString() : undefined;
|
||||
const query = await commands.getLocalEvents(until);
|
||||
|
||||
if (query.status === "ok") {
|
||||
const data = toLumeEvents(query.data);
|
||||
return data;
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
listenLocalEvent: async () => {
|
||||
const label = getCurrentWindow().label;
|
||||
const query = await commands.listenLocalEvent(label);
|
||||
|
||||
if (query.status === "ok") {
|
||||
return query.data;
|
||||
} else {
|
||||
throw new Error(query.error);
|
||||
}
|
||||
},
|
||||
getGroupEvents: async (pubkeys: string[], asOf?: number) => {
|
||||
const until: string = asOf && asOf > 0 ? asOf.toString() : undefined;
|
||||
const query = await commands.getGroupEvents(pubkeys, until);
|
||||
@@ -254,7 +220,7 @@ export const NostrQuery = {
|
||||
}
|
||||
},
|
||||
getNstore: async (key: string) => {
|
||||
const query = await commands.getNstore(key);
|
||||
const query = await commands.getLumeStore(key);
|
||||
|
||||
if (query.status === "ok") {
|
||||
const data = query.data ? JSON.parse(query.data) : null;
|
||||
@@ -264,7 +230,7 @@ export const NostrQuery = {
|
||||
}
|
||||
},
|
||||
setNstore: async (key: string, value: string) => {
|
||||
const query = await commands.setNstore(key, value);
|
||||
const query = await commands.setLumeStore(key, value);
|
||||
|
||||
if (query.status === "ok") {
|
||||
return query.data;
|
||||
@@ -290,36 +256,10 @@ export const NostrQuery = {
|
||||
return query.error;
|
||||
}
|
||||
},
|
||||
getColumns: async () => {
|
||||
const key = "lume:columns";
|
||||
const systemPath = "resources/system_columns.json";
|
||||
const resourcePath = await resolveResource(systemPath);
|
||||
const resourceFile = await readTextFile(resourcePath);
|
||||
const systemColumns: LumeColumn[] = JSON.parse(resourceFile);
|
||||
const query = await commands.getNstore(key);
|
||||
|
||||
try {
|
||||
if (query.status === "ok") {
|
||||
const columns: LumeColumn[] = JSON.parse(query.data);
|
||||
|
||||
if (!columns?.length) {
|
||||
return systemColumns;
|
||||
}
|
||||
|
||||
// Filter "open" column
|
||||
// Reason: deprecated
|
||||
return columns.filter((col) => col.label !== "open");
|
||||
} else {
|
||||
return systemColumns;
|
||||
}
|
||||
} catch {
|
||||
return systemColumns;
|
||||
}
|
||||
},
|
||||
setColumns: async (columns: LumeColumn[]) => {
|
||||
const key = "lume:columns";
|
||||
const key = "lume_v4:columns";
|
||||
const content = JSON.stringify(columns);
|
||||
const query = await commands.setNstore(key, content);
|
||||
const query = await commands.setLumeStore(key, content);
|
||||
|
||||
if (query.status === "ok") {
|
||||
return query.data;
|
||||
@@ -393,14 +333,4 @@ export const NostrQuery = {
|
||||
throw new Error(query.error);
|
||||
}
|
||||
},
|
||||
unlisten: async (id?: string) => {
|
||||
const label = id ? id : getCurrentWindow().label;
|
||||
const query = await commands.unlisten(label);
|
||||
|
||||
if (query.status === "ok") {
|
||||
return query.data;
|
||||
} else {
|
||||
throw new Error(query.error);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,11 +1,54 @@
|
||||
import { commands } from "@/commands.gen";
|
||||
import type { NostrEvent } from "@/types";
|
||||
import type { LumeColumn, NostrEvent } from "@/types";
|
||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||
import type { LumeEvent } from "./event";
|
||||
|
||||
export const LumeWindow = {
|
||||
openMainWindow: async () => {
|
||||
const query = await commands.openMainWindow();
|
||||
return query;
|
||||
openColumn: async (column: LumeColumn) => {
|
||||
await getCurrentWindow().emit("columns", {
|
||||
type: "add",
|
||||
column,
|
||||
});
|
||||
},
|
||||
openColumnsGallery: async () => {
|
||||
await getCurrentWindow().emit("columns", {
|
||||
type: "add",
|
||||
column: {
|
||||
label: "columns_gallery",
|
||||
name: "Columns Gallery",
|
||||
url: "/columns/gallery",
|
||||
},
|
||||
});
|
||||
},
|
||||
openLocalFeeds: async () => {
|
||||
await getCurrentWindow().emit("columns", {
|
||||
type: "add",
|
||||
column: {
|
||||
label: "local_feeds",
|
||||
name: "Local Feeds",
|
||||
url: "/columns/newsfeed",
|
||||
},
|
||||
});
|
||||
},
|
||||
openNotification: async () => {
|
||||
await getCurrentWindow().emit("columns", {
|
||||
type: "add",
|
||||
column: {
|
||||
label: "notification",
|
||||
name: "Notification",
|
||||
url: "/columns/notification",
|
||||
},
|
||||
});
|
||||
},
|
||||
openSearch: async () => {
|
||||
await getCurrentWindow().emit("columns", {
|
||||
type: "add",
|
||||
column: {
|
||||
label: "search",
|
||||
name: "Search",
|
||||
url: "/columns/search",
|
||||
},
|
||||
});
|
||||
},
|
||||
openEvent: async (event: NostrEvent | LumeEvent) => {
|
||||
const eTags = event.tags.filter((tag) => tag[0] === "e" || tag[0] === "q");
|
||||
@@ -14,44 +57,29 @@ export const LumeWindow = {
|
||||
const reply: string =
|
||||
eTags.find((el) => el[3] === "reply")?.[1] ?? eTags[1]?.[1];
|
||||
|
||||
const url = `/events/${root ?? reply ?? event.id}`;
|
||||
const url = `/columns/events/${root ?? reply ?? event.id}`;
|
||||
const label = `event-${root ?? reply ?? event.id}`;
|
||||
|
||||
const query = await commands.openWindow({
|
||||
label,
|
||||
url,
|
||||
title: "Thread",
|
||||
width: 500,
|
||||
height: 800,
|
||||
maximizable: true,
|
||||
minimizable: true,
|
||||
hidden_title: false,
|
||||
});
|
||||
|
||||
if (query.status === "ok") {
|
||||
return query.data;
|
||||
} else {
|
||||
throw new Error(query.error);
|
||||
}
|
||||
LumeWindow.openColumn({ label, url, name: "Thread" });
|
||||
},
|
||||
openProfile: async (pubkey: string) => {
|
||||
const label = `user-${pubkey}`;
|
||||
const query = await commands.openWindow({
|
||||
label,
|
||||
url: `/users/${pubkey}`,
|
||||
title: "Profile",
|
||||
width: 500,
|
||||
height: 800,
|
||||
maximizable: true,
|
||||
minimizable: true,
|
||||
hidden_title: true,
|
||||
});
|
||||
|
||||
if (query.status === "ok") {
|
||||
return query.data;
|
||||
} else {
|
||||
throw new Error(query.error);
|
||||
}
|
||||
LumeWindow.openColumn({
|
||||
label,
|
||||
url: `/columns/users/${pubkey}`,
|
||||
name: "Profile",
|
||||
});
|
||||
},
|
||||
openHashtag: async (hashtag: string) => {
|
||||
const content = hashtag.replace("#", "");
|
||||
const label = `hashtag-${content}`;
|
||||
|
||||
LumeWindow.openColumn({
|
||||
label,
|
||||
url: `/columns/hashtags/${content}`,
|
||||
name: hashtag,
|
||||
});
|
||||
},
|
||||
openEditor: async (reply_to?: string, quote?: string) => {
|
||||
let url: string;
|
||||
@@ -104,11 +132,10 @@ export const LumeWindow = {
|
||||
await LumeWindow.openSettings("bitcoin-connect");
|
||||
}
|
||||
},
|
||||
openSettings: async (path?: string) => {
|
||||
const label = "settings";
|
||||
openSettings: async (account: string, path?: string) => {
|
||||
const query = await commands.openWindow({
|
||||
label,
|
||||
url: path ? `/settings/${path}` : "/settings/general",
|
||||
label: "settings",
|
||||
url: path ? `${account}/${path}` : `${account}/general`,
|
||||
title: "Settings",
|
||||
width: 800,
|
||||
height: 500,
|
||||
@@ -123,29 +150,8 @@ export const LumeWindow = {
|
||||
throw new Error(query.error);
|
||||
}
|
||||
},
|
||||
openSearch: async (searchType: "notes" | "users", searchQuery: string) => {
|
||||
const url = `/search/${searchType}?query=${searchQuery}`;
|
||||
const label = `search-${searchQuery
|
||||
.toLowerCase()
|
||||
.replace(/[^\w ]+/g, "")
|
||||
.replace(/ +/g, "_")
|
||||
.replace(/_+/g, "_")}`;
|
||||
|
||||
const query = await commands.openWindow({
|
||||
label,
|
||||
url,
|
||||
title: "Search",
|
||||
width: 400,
|
||||
height: 600,
|
||||
maximizable: false,
|
||||
minimizable: false,
|
||||
hidden_title: true,
|
||||
});
|
||||
|
||||
if (query.status === "ok") {
|
||||
return query.data;
|
||||
} else {
|
||||
throw new Error(query.error);
|
||||
}
|
||||
openMainWindow: async () => {
|
||||
const query = await commands.reopenLume();
|
||||
return query;
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user