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:
雨宮蓮
2024-08-27 19:37:30 +07:00
committed by GitHub
parent 26ae473521
commit 61ad96ca63
318 changed files with 5564 additions and 8458 deletions

View File

@@ -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;

View File

@@ -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);

View File

@@ -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 };
}

View File

@@ -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 };

View File

@@ -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);
}
},
};

View File

@@ -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;
},
};