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:
@@ -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";
|
||||
|
||||
@@ -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:^",
|
||||
|
||||
@@ -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[]>());
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user