Make Lume Faster (#208)

* chore: fix some lint issues

* feat: refactor contact list

* feat: refactor relay hint

* feat: add missing commands

* feat: use new cache layer for react query

* feat: refactor column

* feat: improve relay hint

* fix: replace break with continue in parser

* refactor: publish function

* feat: add reply command

* feat: improve editor

* fix: quote

* chore: update deps

* refactor: note component

* feat: improve repost

* feat: improve cache

* fix: backup screen

* refactor: column manager
This commit is contained in:
雨宮蓮
2024-06-17 13:52:06 +07:00
committed by GitHub
parent 7c99ed39e4
commit 843895d876
79 changed files with 1738 additions and 1975 deletions

View File

@@ -2,16 +2,6 @@ export * from "./src/constants";
export * from "./src/delay";
export * from "./src/formater";
export * from "./src/editor";
export * from "./src/nip01";
export * from "./src/nip94";
export * from "./src/notification";
export * from "./src/cn";
export * from "./src/image";
export * from "./src/parser";
export * from "./src/groupBy";
export * from "./src/invoice";
export * from "./src/update";
// Hooks
export * from "./src/hooks/useNetworkStatus";
export * from "./src/hooks/useOpenGraph";

View File

@@ -8,7 +8,6 @@
"access": "public"
},
"dependencies": {
"@tanstack/react-query": "^5.40.1",
"bitcoin-units": "^1.0.0",
"clsx": "^2.1.1",
"dayjs": "^1.11.11",
@@ -17,7 +16,7 @@
"react": "^18.3.1",
"react-dom": "^18.3.1",
"slate": "^0.103.0",
"slate-react": "^0.104.0"
"slate-react": "^0.105.0"
},
"devDependencies": {
"@lume/tsconfig": "workspace:^",

View File

@@ -1,21 +0,0 @@
export const groupBy = <T>(
array: T[],
predicate: (value: T, index: number, array: T[]) => string,
) =>
array.reduce(
(acc, value, index, array) => {
(acc[predicate(value, index, array)] ||= []).push(value);
return acc;
},
{} as { [key: string]: T[] },
);
export const groupByToMap = <T, Q>(
array: T[],
predicate: (value: T, index: number, array: T[]) => Q,
) =>
array.reduce((map, value, index, array) => {
const key = predicate(value, index, array);
map.get(key)?.push(value) ?? map.set(key, [value]);
return map;
}, new Map<Q, T[]>());

View File

@@ -1,25 +0,0 @@
import { useEffect, useState } from "react";
const getOnLineStatus = () =>
typeof navigator !== "undefined" && typeof navigator.onLine === "boolean"
? navigator.onLine
: true;
export function useNetworkStatus() {
const [status, setStatus] = useState(getOnLineStatus());
const setOnline = () => setStatus(true);
const setOffline = () => setStatus(false);
useEffect(() => {
window.addEventListener("online", setOnline);
window.addEventListener("offline", setOffline);
return () => {
window.removeEventListener("online", setOnline);
window.removeEventListener("offline", setOffline);
};
}, []);
return status;
}

View File

@@ -1,27 +0,0 @@
import type { Opengraph } from "@lume/types";
import { useQuery } from "@tanstack/react-query";
import { invoke } from "@tauri-apps/api/core";
export function useOpenGraph(url: string) {
const { isLoading, isError, data } = useQuery({
queryKey: ["opg", url],
queryFn: async () => {
try {
const res: Opengraph = await invoke("fetch_opg", { url });
return res;
} catch {
throw new Error("fetch preview failed");
}
},
staleTime: Number.POSITIVE_INFINITY,
refetchOnWindowFocus: false,
refetchOnMount: false,
refetchOnReconnect: false,
});
return {
isLoading,
isError,
data,
};
}

View File

@@ -1,10 +0,0 @@
export function getImageMeta(
url: string,
): Promise<{ width: number; height: number }> {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = () => reject();
img.src = url;
});
}

View File

@@ -7,10 +7,10 @@ export function decodeZapInvoice(tags?: string[][]) {
const decodedInvoice = decode(invoice);
const amountSection = decodedInvoice.sections.find(
(s: any) => s.name === "amount",
(s: { name: string }) => s.name === "amount",
);
const amount = parseInt(amountSection.value);
const amount = Number.parseInt(amountSection.value);
const displayValue = getBitcoinDisplayValues(amount);
return displayValue;

View File

@@ -1,96 +0,0 @@
import { nip19 } from "nostr-tools";
import type { EventPointer, ProfilePointer } from "nostr-tools/lib/types/nip19";
// Borrow from NDK
// https://github.com/nostr-dev-kit/ndk/blob/master/ndk/src/events/content-tagger.ts
export async function generateContentTags(content: string) {
const promises: Promise<void>[] = [];
const tags: string[][] = [];
const tagRegex = /(@|nostr:)(npub|nprofile|note|nevent|naddr)[a-zA-Z0-9]+/g;
const hashtagRegex = /#(\w+)/g;
const addTagIfNew = (t: string[]) => {
if (!tags.find((t2) => t2[0] === t[0] && t2[1] === t[1])) {
tags.push(t);
}
};
content = content.replace(tagRegex, (tag) => {
try {
const entity = tag.split(/(@|nostr:)/)[2];
const { type, data } = nip19.decode(entity);
let t: string[] | undefined;
switch (type) {
case "npub":
t = ["p", data as string];
break;
case "nprofile":
t = ["p", (data as ProfilePointer).pubkey as string];
break;
case "note":
promises.push(
new Promise(async (resolve) => {
addTagIfNew(["e", data, "", "mention"]);
resolve();
}),
);
break;
case "nevent":
promises.push(
new Promise(async (resolve) => {
let { id, relays, author } = data as EventPointer;
// If the nevent doesn't have a relay specified, try to get one
if (!relays || relays.length === 0) {
relays = [""];
}
addTagIfNew(["e", id, relays[0], "mention"]);
if (author) addTagIfNew(["p", author]);
resolve();
}),
);
break;
case "naddr":
promises.push(
new Promise(async (resolve) => {
const id = [data.kind, data.pubkey, data.identifier].join(":");
let relays = data.relays ?? [];
// If the naddr doesn't have a relay specified, try to get one
if (relays.length === 0) {
relays = [""];
}
addTagIfNew(["a", id, relays[0], "mention"]);
addTagIfNew(["p", data.pubkey]);
resolve();
}),
);
break;
default:
return tag;
}
if (t) addTagIfNew(t);
return `nostr:${entity}`;
} catch (error) {
return tag;
}
});
await Promise.all(promises);
content = content.replace(hashtagRegex, (tag, word) => {
const t: string[] = ["t", word];
if (!tags.find((t2) => t2[0] === t[0] && t2[1] === t[1])) {
tags.push(t);
}
return tag; // keep the original tag in the content
});
return { content, tags };
}

View File

@@ -1,15 +0,0 @@
export function fileType(url: string) {
if (url.match(/\.(jpg|jpeg|gif|png|webp|avif|tiff)$/)) {
return "image";
}
if (url.match(/\.(mp4|mov|webm|wmv|flv|mts|avi|ogv|mkv)$/)) {
return "video";
}
if (url.match(/\.(mp3|ogg|wav)$/)) {
return "audio";
}
return "link";
}

View File

@@ -1,16 +0,0 @@
import {
isPermissionGranted,
requestPermission,
sendNotification,
} from "@tauri-apps/plugin-notification";
export async function sendNativeNotification(content: string, title?: string) {
let permissionGranted = await isPermissionGranted();
if (!permissionGranted) {
const permission = await requestPermission();
permissionGranted = permission === "granted";
}
if (permissionGranted) {
sendNotification({ title: title || "Lume", body: content });
}
}

View File

@@ -1,78 +0,0 @@
import { Meta } from "@lume/types";
import { IMAGES, NOSTR_EVENTS, NOSTR_MENTIONS, VIDEOS } from "./constants";
import { fetch } from "@tauri-apps/plugin-http";
export async function parser(
content: string,
abortController?: AbortController,
) {
const words = content.split(/( |\n)/);
const urls = content.match(/(https?:\/\/\S+)/gi);
// Extract hashtags
const hashtags = words.filter((word) => word.startsWith("#"));
// Extract nostr events
const events = words.filter((word) =>
NOSTR_EVENTS.some((el) => word.startsWith(el)),
);
// Extract nostr mentions
const mentions = words.filter((word) =>
NOSTR_MENTIONS.some((el) => word.startsWith(el)),
);
// Extract images and videos from content
const images: string[] = [];
const videos: string[] = [];
let text: string = content;
if (urls) {
for (const url of urls) {
const ext = new URL(url).pathname.split(".")[1];
if (IMAGES.includes(ext)) {
text = text.replace(url, "");
images.push(url);
break;
}
if (VIDEOS.includes(ext)) {
text = text.replace(url, "");
videos.push(url);
break;
}
if (urls.length <= 3) {
try {
const res = await fetch(url, {
method: "HEAD",
priority: "high",
signal: abortController.signal,
// proxy: settings.proxy;
});
if (res.headers.get("Content-Type").startsWith("image")) {
text = text.replace(url, "");
images.push(url);
break;
}
} catch {
break;
}
}
}
}
const meta: Meta = {
content: text.trim(),
images,
videos,
events,
mentions,
hashtags,
};
return meta;
}