feat: polish note component
This commit is contained in:
@@ -29,8 +29,6 @@
|
|||||||
"@tauri-apps/plugin-sql": "2.0.0-alpha.5",
|
"@tauri-apps/plugin-sql": "2.0.0-alpha.5",
|
||||||
"@tauri-apps/plugin-updater": "2.0.0-alpha.5",
|
"@tauri-apps/plugin-updater": "2.0.0-alpha.5",
|
||||||
"@tauri-apps/plugin-upload": "2.0.0-alpha.5",
|
"@tauri-apps/plugin-upload": "2.0.0-alpha.5",
|
||||||
"@tiptap/extension-mention": "^2.1.13",
|
|
||||||
"@tiptap/react": "^2.1.13",
|
|
||||||
"@vidstack/react": "^1.9.8",
|
"@vidstack/react": "^1.9.8",
|
||||||
"get-urls": "^12.1.0",
|
"get-urls": "^12.1.0",
|
||||||
"jotai": "^2.6.1",
|
"jotai": "^2.6.1",
|
||||||
|
|||||||
@@ -267,10 +267,16 @@ export class Ark {
|
|||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getEventThread({ tags }: { tags: NDKTag[] }) {
|
public getEventThread({
|
||||||
|
content,
|
||||||
|
tags,
|
||||||
|
}: { content: string; tags: NDKTag[] }) {
|
||||||
let rootEventId: string = null;
|
let rootEventId: string = null;
|
||||||
let replyEventId: string = null;
|
let replyEventId: string = null;
|
||||||
|
|
||||||
|
if (content.includes("nostr:note1") || content.includes("nostr:nevent1"))
|
||||||
|
return null;
|
||||||
|
|
||||||
const events = tags.filter((el) => el[0] === "e");
|
const events = tags.filter((el) => el[0] === "e");
|
||||||
|
|
||||||
if (!events.length) return null;
|
if (!events.length) return null;
|
||||||
|
|||||||
@@ -1,9 +1,97 @@
|
|||||||
import { useEvent } from '../../hooks/useEvent';
|
import { NOSTR_MENTIONS } from "@lume/utils";
|
||||||
import { NoteChildUser } from './childUser';
|
import { nip19 } from "nostr-tools";
|
||||||
|
import { ReactNode, useMemo } from "react";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import reactStringReplace from "react-string-replace";
|
||||||
|
import { useEvent } from "../../hooks/useEvent";
|
||||||
|
import { NoteChildUser } from "./childUser";
|
||||||
|
import { Hashtag } from "./mentions/hashtag";
|
||||||
|
import { MentionUser } from "./mentions/user";
|
||||||
|
|
||||||
export function NoteChild({ eventId, isRoot }: { eventId: string; isRoot?: boolean }) {
|
export function NoteChild({
|
||||||
|
eventId,
|
||||||
|
isRoot,
|
||||||
|
}: { eventId: string; isRoot?: boolean }) {
|
||||||
const { isLoading, isError, data } = useEvent(eventId);
|
const { isLoading, isError, data } = useEvent(eventId);
|
||||||
|
|
||||||
|
const richContent = useMemo(() => {
|
||||||
|
if (!data) return "";
|
||||||
|
|
||||||
|
let parsedContent: string | ReactNode[] = data.content.replace(
|
||||||
|
/\n+/g,
|
||||||
|
"\n",
|
||||||
|
);
|
||||||
|
|
||||||
|
const text = parsedContent;
|
||||||
|
const words = text.split(/( |\n)/);
|
||||||
|
|
||||||
|
const hashtags = words.filter((word) => word.startsWith("#"));
|
||||||
|
const mentions = words.filter((word) =>
|
||||||
|
NOSTR_MENTIONS.some((el) => word.startsWith(el)),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (hashtags.length) {
|
||||||
|
for (const hashtag of hashtags) {
|
||||||
|
parsedContent = reactStringReplace(
|
||||||
|
parsedContent,
|
||||||
|
hashtag,
|
||||||
|
(match, i) => {
|
||||||
|
return <Hashtag key={match + i} tag={hashtag} />;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mentions.length) {
|
||||||
|
for (const mention of mentions) {
|
||||||
|
const address = mention
|
||||||
|
.replace("nostr:", "")
|
||||||
|
.replace("@", "")
|
||||||
|
.replace(/[^a-zA-Z0-9]/g, "");
|
||||||
|
const decoded = nip19.decode(address);
|
||||||
|
|
||||||
|
if (decoded.type === "npub") {
|
||||||
|
parsedContent = reactStringReplace(
|
||||||
|
parsedContent,
|
||||||
|
mention,
|
||||||
|
(match, i) => <MentionUser key={match + i} pubkey={decoded.data} />,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decoded.type === "nprofile" || decoded.type === "naddr") {
|
||||||
|
parsedContent = reactStringReplace(
|
||||||
|
parsedContent,
|
||||||
|
mention,
|
||||||
|
(match, i) => (
|
||||||
|
<MentionUser key={match + i} pubkey={decoded.data.pubkey} />
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedContent = reactStringReplace(
|
||||||
|
parsedContent,
|
||||||
|
/(https?:\/\/\S+)/g,
|
||||||
|
(match, i) => {
|
||||||
|
const url = new URL(match);
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
key={match + i}
|
||||||
|
to={url.toString()}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="break-all font-normal text-blue-500 hover:text-blue-600"
|
||||||
|
>
|
||||||
|
{url.toString()}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return parsedContent;
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="relative flex gap-3">
|
<div className="relative flex gap-3">
|
||||||
@@ -29,10 +117,13 @@ export function NoteChild({ eventId, isRoot }: { eventId: string; isRoot?: boole
|
|||||||
<div className="relative flex-1 rounded-md bg-neutral-200 px-2 py-2 dark:bg-neutral-800">
|
<div className="relative flex-1 rounded-md bg-neutral-200 px-2 py-2 dark:bg-neutral-800">
|
||||||
<div className="absolute right-0 top-[18px] h-3 w-3 -translate-y-1/2 translate-x-1/2 rotate-45 transform bg-neutral-200 dark:bg-neutral-800" />
|
<div className="absolute right-0 top-[18px] h-3 w-3 -translate-y-1/2 translate-x-1/2 rotate-45 transform bg-neutral-200 dark:bg-neutral-800" />
|
||||||
<div className="break-p mt-6 line-clamp-3 select-text leading-normal text-neutral-900 dark:text-neutral-100">
|
<div className="break-p mt-6 line-clamp-3 select-text leading-normal text-neutral-900 dark:text-neutral-100">
|
||||||
{data.content}
|
{richContent}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<NoteChildUser pubkey={data.pubkey} subtext={isRoot ? 'posted' : 'replied'} />
|
<NoteChildUser
|
||||||
|
pubkey={data.pubkey}
|
||||||
|
subtext={isRoot ? "posted" : "replied"}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,12 @@
|
|||||||
import { cn } from "@lume/utils";
|
import {
|
||||||
|
AUDIOS,
|
||||||
|
IMAGES,
|
||||||
|
NOSTR_EVENTS,
|
||||||
|
NOSTR_MENTIONS,
|
||||||
|
VIDEOS,
|
||||||
|
cn,
|
||||||
|
regionNames,
|
||||||
|
} from "@lume/utils";
|
||||||
import { NDKKind } from "@nostr-dev-kit/ndk";
|
import { NDKKind } from "@nostr-dev-kit/ndk";
|
||||||
import { fetch } from "@tauri-apps/plugin-http";
|
import { fetch } from "@tauri-apps/plugin-http";
|
||||||
import getUrls from "get-urls";
|
import getUrls from "get-urls";
|
||||||
@@ -19,55 +27,12 @@ import {
|
|||||||
} from "../..";
|
} from "../..";
|
||||||
import { NIP89 } from "./nip89";
|
import { NIP89 } from "./nip89";
|
||||||
|
|
||||||
const NOSTR_MENTIONS = [
|
|
||||||
"@npub1",
|
|
||||||
"nostr:npub1",
|
|
||||||
"nostr:nprofile1",
|
|
||||||
"nostr:naddr1",
|
|
||||||
"npub1",
|
|
||||||
"nprofile1",
|
|
||||||
"naddr1",
|
|
||||||
"Nostr:npub1",
|
|
||||||
"Nostr:nprofile1",
|
|
||||||
"Nostr:naddre1",
|
|
||||||
];
|
|
||||||
|
|
||||||
const NOSTR_EVENTS = [
|
|
||||||
"@nevent1",
|
|
||||||
"@note1",
|
|
||||||
"@nostr:note1",
|
|
||||||
"@nostr:nevent1",
|
|
||||||
"nostr:note1",
|
|
||||||
"note1",
|
|
||||||
"nostr:nevent1",
|
|
||||||
"nevent1",
|
|
||||||
"Nostr:note1",
|
|
||||||
"Nostr:nevent1",
|
|
||||||
];
|
|
||||||
|
|
||||||
// const BITCOINS = ['lnbc', 'bc1p', 'bc1q'];
|
|
||||||
|
|
||||||
const IMAGES = ["jpg", "jpeg", "gif", "png", "webp", "avif", "tiff"];
|
|
||||||
|
|
||||||
const VIDEOS = [
|
|
||||||
"mp4",
|
|
||||||
"mov",
|
|
||||||
"webm",
|
|
||||||
"wmv",
|
|
||||||
"flv",
|
|
||||||
"mts",
|
|
||||||
"avi",
|
|
||||||
"ogv",
|
|
||||||
"mkv",
|
|
||||||
"m3u8",
|
|
||||||
];
|
|
||||||
|
|
||||||
const AUDIOS = ["mp3", "ogg", "wav"];
|
|
||||||
|
|
||||||
export function NoteContent({
|
export function NoteContent({
|
||||||
className,
|
className,
|
||||||
|
isTranslatable = false,
|
||||||
}: {
|
}: {
|
||||||
className?: string;
|
className?: string;
|
||||||
|
isTranslatable?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const storage = useStorage();
|
const storage = useStorage();
|
||||||
const event = useNoteContext();
|
const event = useNoteContext();
|
||||||
@@ -79,7 +44,7 @@ export function NoteContent({
|
|||||||
if (event.kind !== NDKKind.Text) return content;
|
if (event.kind !== NDKKind.Text) return content;
|
||||||
|
|
||||||
let parsedContent: string | ReactNode[] = content.replace(/\n+/g, "\n");
|
let parsedContent: string | ReactNode[] = content.replace(/\n+/g, "\n");
|
||||||
let linkPreview: string;
|
let linkPreview: string = undefined;
|
||||||
let images: string[] = [];
|
let images: string[] = [];
|
||||||
let videos: string[] = [];
|
let videos: string[] = [];
|
||||||
let audios: string[] = [];
|
let audios: string[] = [];
|
||||||
@@ -299,7 +264,7 @@ export function NoteContent({
|
|||||||
<div className="break-p select-text whitespace-pre-line text-balance leading-normal">
|
<div className="break-p select-text whitespace-pre-line text-balance leading-normal">
|
||||||
{richContent}
|
{richContent}
|
||||||
</div>
|
</div>
|
||||||
{storage.settings.translation ? (
|
{isTranslatable && storage.settings.translation ? (
|
||||||
translated ? (
|
translated ? (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -307,7 +272,7 @@ export function NoteContent({
|
|||||||
setTranslated(false);
|
setTranslated(false);
|
||||||
setContent(event.content);
|
setContent(event.content);
|
||||||
}}
|
}}
|
||||||
className="mt-2 text-sm text-blue-500 hover:text-blue-600 border-none shadow-none focus:outline-none"
|
className="mt-3 text-sm text-blue-500 hover:text-blue-600 border-none shadow-none focus:outline-none"
|
||||||
>
|
>
|
||||||
Show original content
|
Show original content
|
||||||
</button>
|
</button>
|
||||||
@@ -315,9 +280,9 @@ export function NoteContent({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={translate}
|
onClick={translate}
|
||||||
className="mt-2 text-sm text-blue-500 hover:text-blue-600 border-none shadow-none focus:outline-none"
|
className="mt-3 text-sm text-blue-500 hover:text-blue-600 border-none shadow-none focus:outline-none"
|
||||||
>
|
>
|
||||||
Translate to Vietnamese
|
Translate to {regionNames.of(storage.locale)}
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { CheckCircleIcon, DownloadIcon } from "@lume/icons";
|
import { CheckCircleIcon, DownloadIcon } from "@lume/icons";
|
||||||
import { getImageMeta } from "@lume/utils";
|
|
||||||
import { downloadDir } from "@tauri-apps/api/path";
|
import { downloadDir } from "@tauri-apps/api/path";
|
||||||
import { Window } from "@tauri-apps/api/window";
|
import { Window } from "@tauri-apps/api/window";
|
||||||
import { download } from "@tauri-apps/plugin-upload";
|
import { download } from "@tauri-apps/plugin-upload";
|
||||||
@@ -24,12 +23,9 @@ export function ImagePreview({ url }: { url: string }) {
|
|||||||
|
|
||||||
const open = async () => {
|
const open = async () => {
|
||||||
const name = new URL(url).pathname.split("/").pop();
|
const name = new URL(url).pathname.split("/").pop();
|
||||||
const image = await getImageMeta(url);
|
|
||||||
return new Window("image-viewer", {
|
return new Window("image-viewer", {
|
||||||
url,
|
url,
|
||||||
title: name,
|
title: name,
|
||||||
width: image.width,
|
|
||||||
height: image.height,
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ export function VideoPreview({ url }: { url: string }) {
|
|||||||
<MediaPlayer
|
<MediaPlayer
|
||||||
src={url}
|
src={url}
|
||||||
className="w-full my-1 overflow-hidden rounded-lg"
|
className="w-full my-1 overflow-hidden rounded-lg"
|
||||||
aspectRatio="16/9"
|
|
||||||
load="visible"
|
load="visible"
|
||||||
>
|
>
|
||||||
<MediaProvider />
|
<MediaProvider />
|
||||||
|
|||||||
@@ -1,14 +1,10 @@
|
|||||||
import { NDKEvent } from "@nostr-dev-kit/ndk";
|
import { NDKEvent } from "@nostr-dev-kit/ndk";
|
||||||
import { Note } from "..";
|
import { Note } from "..";
|
||||||
import { useArk } from "../../../provider";
|
|
||||||
|
|
||||||
export function TextNote({
|
export function TextNote({
|
||||||
event,
|
event,
|
||||||
className,
|
className,
|
||||||
}: { event: NDKEvent; className?: string }) {
|
}: { event: NDKEvent; className?: string }) {
|
||||||
const ark = useArk();
|
|
||||||
const thread = ark.getEventThread({ tags: event.tags });
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Note.Provider event={event}>
|
<Note.Provider event={event}>
|
||||||
<Note.Root className={className}>
|
<Note.Root className={className}>
|
||||||
@@ -16,7 +12,7 @@ export function TextNote({
|
|||||||
<Note.User className="flex-1 pr-1" />
|
<Note.User className="flex-1 pr-1" />
|
||||||
<Note.Menu />
|
<Note.Menu />
|
||||||
</div>
|
</div>
|
||||||
<Note.Thread thread={thread} className="mb-2" />
|
<Note.Thread className="mb-2" />
|
||||||
<Note.Content className="min-w-0 px-3" />
|
<Note.Content className="min-w-0 px-3" />
|
||||||
<div className="flex items-center justify-between px-3 h-14">
|
<div className="flex items-center justify-between px-3 h-14">
|
||||||
<Note.Pin />
|
<Note.Pin />
|
||||||
|
|||||||
@@ -1,32 +1,9 @@
|
|||||||
import { NDKEvent, NDKKind } from "@nostr-dev-kit/ndk";
|
|
||||||
import { Note } from "..";
|
import { Note } from "..";
|
||||||
import { useEvent } from "../../../hooks/useEvent";
|
import { useEvent } from "../../../hooks/useEvent";
|
||||||
import { useArk } from "../../../provider";
|
|
||||||
|
|
||||||
export function ThreadNote({ eventId }: { eventId: string }) {
|
export function ThreadNote({ eventId }: { eventId: string }) {
|
||||||
const ark = useArk();
|
|
||||||
const { isLoading, data } = useEvent(eventId);
|
const { isLoading, data } = useEvent(eventId);
|
||||||
|
|
||||||
const renderEventKind = (event: NDKEvent) => {
|
|
||||||
const thread = ark.getEventThread({ tags: data.tags });
|
|
||||||
switch (event.kind) {
|
|
||||||
case NDKKind.Text:
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Note.Thread thread={thread} className="mb-2" />
|
|
||||||
<Note.Content className="min-w-0 px-3" />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Note.Thread thread={thread} className="mb-2" />
|
|
||||||
<Note.Content className="min-w-0 px-3" />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <div>Loading...</div>;
|
return <div>Loading...</div>;
|
||||||
}
|
}
|
||||||
@@ -38,7 +15,8 @@ export function ThreadNote({ eventId }: { eventId: string }) {
|
|||||||
<Note.User className="flex-1 pr-1" />
|
<Note.User className="flex-1 pr-1" />
|
||||||
<Note.Menu />
|
<Note.Menu />
|
||||||
</div>
|
</div>
|
||||||
{renderEventKind(data)}
|
<Note.Thread className="mb-2" />
|
||||||
|
<Note.Content className="min-w-0 px-3" isTranslatable />
|
||||||
<div className="flex items-center justify-between px-3 h-14">
|
<div className="flex items-center justify-between px-3 h-14">
|
||||||
<Note.Pin />
|
<Note.Pin />
|
||||||
<div className="inline-flex items-center gap-10">
|
<div className="inline-flex items-center gap-10">
|
||||||
|
|||||||
@@ -2,16 +2,22 @@ import { PinIcon } from "@lume/icons";
|
|||||||
import { COL_TYPES } from "@lume/utils";
|
import { COL_TYPES } from "@lume/utils";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
import { Note } from ".";
|
import { Note, useNoteContext } from ".";
|
||||||
|
import { useArk } from "../..";
|
||||||
import { useColumnContext } from "../column";
|
import { useColumnContext } from "../column";
|
||||||
|
|
||||||
export function NoteThread({
|
export function NoteThread({
|
||||||
thread,
|
|
||||||
className,
|
className,
|
||||||
}: {
|
}: {
|
||||||
thread: { rootEventId: string; replyEventId: string };
|
|
||||||
className?: string;
|
className?: string;
|
||||||
}) {
|
}) {
|
||||||
|
const ark = useArk();
|
||||||
|
const event = useNoteContext();
|
||||||
|
const thread = ark.getEventThread({
|
||||||
|
content: event.content,
|
||||||
|
tags: event.tags,
|
||||||
|
});
|
||||||
|
|
||||||
const { addColumn } = useColumnContext();
|
const { addColumn } = useColumnContext();
|
||||||
|
|
||||||
if (!thread) return null;
|
if (!thread) return null;
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
import { LoaderIcon } from "@lume/icons";
|
import { LoaderIcon } from "@lume/icons";
|
||||||
import { NDKCacheAdapterTauri } from "@lume/ndk-cache-tauri";
|
import { NDKCacheAdapterTauri } from "@lume/ndk-cache-tauri";
|
||||||
import { LumeStorage } from "@lume/storage";
|
import { LumeStorage } from "@lume/storage";
|
||||||
import {
|
import { QUOTES, delay, sendNativeNotification } from "@lume/utils";
|
||||||
FETCH_LIMIT,
|
|
||||||
QUOTES,
|
|
||||||
delay,
|
|
||||||
sendNativeNotification,
|
|
||||||
} from "@lume/utils";
|
|
||||||
import NDK, {
|
import NDK, {
|
||||||
NDKEvent,
|
NDKEvent,
|
||||||
NDKKind,
|
NDKKind,
|
||||||
@@ -18,7 +13,7 @@ import NDK, {
|
|||||||
import { ndkAdapter } from "@nostr-fetch/adapter-ndk";
|
import { ndkAdapter } from "@nostr-fetch/adapter-ndk";
|
||||||
import { useQueryClient } from "@tanstack/react-query";
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
import { fetch } from "@tauri-apps/plugin-http";
|
import { fetch } from "@tauri-apps/plugin-http";
|
||||||
import { platform } from "@tauri-apps/plugin-os";
|
import { locale, platform } from "@tauri-apps/plugin-os";
|
||||||
import { relaunch } from "@tauri-apps/plugin-process";
|
import { relaunch } from "@tauri-apps/plugin-process";
|
||||||
import Database from "@tauri-apps/plugin-sql";
|
import Database from "@tauri-apps/plugin-sql";
|
||||||
import { check } from "@tauri-apps/plugin-updater";
|
import { check } from "@tauri-apps/plugin-updater";
|
||||||
@@ -101,9 +96,10 @@ const LumeProvider = ({ children }: PropsWithChildren<object>) => {
|
|||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
const platformName = await platform();
|
const platformName = await platform();
|
||||||
|
const osLocale = await locale();
|
||||||
const sqliteAdapter = await Database.load("sqlite:lume_v3.db");
|
const sqliteAdapter = await Database.load("sqlite:lume_v3.db");
|
||||||
|
|
||||||
const storage = new LumeStorage(sqliteAdapter, platformName);
|
const storage = new LumeStorage(sqliteAdapter, platformName, osLocale);
|
||||||
await storage.init();
|
await storage.init();
|
||||||
|
|
||||||
// check for new update
|
// check for new update
|
||||||
@@ -145,9 +141,9 @@ const LumeProvider = ({ children }: PropsWithChildren<object>) => {
|
|||||||
explicitRelayUrls,
|
explicitRelayUrls,
|
||||||
outboxRelayUrls,
|
outboxRelayUrls,
|
||||||
blacklistRelayUrls,
|
blacklistRelayUrls,
|
||||||
enableOutboxModel: !storage.settings.lowPowerMode,
|
enableOutboxModel: !storage.settings.lowPower,
|
||||||
autoConnectUserRelays: !storage.settings.lowPowerMode,
|
autoConnectUserRelays: !storage.settings.lowPower,
|
||||||
autoFetchUserMutelist: !storage.settings.lowPowerMode,
|
autoFetchUserMutelist: !storage.settings.lowPower,
|
||||||
// clientName: 'Lume',
|
// clientName: 'Lume',
|
||||||
// clientNip89: '',
|
// clientNip89: '',
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export function HomeRoute({ colKey }: { colKey: string }) {
|
|||||||
const storage = useStorage();
|
const storage = useStorage();
|
||||||
const ref = useRef<VListHandle>();
|
const ref = useRef<VListHandle>();
|
||||||
const cacheKey = `${colKey}-vlist`;
|
const cacheKey = `${colKey}-vlist`;
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const [offset, cache] = useMemo(() => {
|
const [offset, cache] = useMemo(() => {
|
||||||
const serialized = sessionStorage.getItem(cacheKey);
|
const serialized = sessionStorage.getItem(cacheKey);
|
||||||
@@ -48,7 +49,6 @@ export function HomeRoute({ colKey }: { colKey: string }) {
|
|||||||
return lastEvent.created_at - 1;
|
return lastEvent.created_at - 1;
|
||||||
},
|
},
|
||||||
initialData: () => {
|
initialData: () => {
|
||||||
const queryClient = useQueryClient();
|
|
||||||
const queryCacheData = queryClient.getQueryState([colKey])
|
const queryCacheData = queryClient.getQueryState([colKey])
|
||||||
?.data as NDKEvent[];
|
?.data as NDKEvent[];
|
||||||
if (queryCacheData) {
|
if (queryCacheData) {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ export class LumeStorage {
|
|||||||
#db: Database;
|
#db: Database;
|
||||||
#depot: Child;
|
#depot: Child;
|
||||||
readonly platform: Platform;
|
readonly platform: Platform;
|
||||||
|
readonly locale: string;
|
||||||
public account: Account;
|
public account: Account;
|
||||||
public settings: {
|
public settings: {
|
||||||
autoupdate: boolean;
|
autoupdate: boolean;
|
||||||
@@ -30,8 +31,9 @@ export class LumeStorage {
|
|||||||
translateApiKey: string;
|
translateApiKey: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(db: Database, platform: Platform) {
|
constructor(db: Database, platform: Platform, locale: string) {
|
||||||
this.#db = db;
|
this.#db = db;
|
||||||
|
this.locale = locale;
|
||||||
this.platform = platform;
|
this.platform = platform;
|
||||||
this.settings = {
|
this.settings = {
|
||||||
autoupdate: false,
|
autoupdate: false,
|
||||||
|
|||||||
@@ -5,7 +5,12 @@ import {
|
|||||||
useProfile,
|
useProfile,
|
||||||
useStorage,
|
useStorage,
|
||||||
} from "@lume/ark";
|
} from "@lume/ark";
|
||||||
import { ArrowLeftIcon, ArrowRightCircleIcon, LoaderIcon } from "@lume/icons";
|
import {
|
||||||
|
ArrowLeftIcon,
|
||||||
|
ArrowRightCircleIcon,
|
||||||
|
ArrowRightIcon,
|
||||||
|
LoaderIcon,
|
||||||
|
} from "@lume/icons";
|
||||||
import { FETCH_LIMIT, displayNpub } from "@lume/utils";
|
import { FETCH_LIMIT, displayNpub } from "@lume/utils";
|
||||||
import { NDKEvent, NDKKind } from "@nostr-dev-kit/ndk";
|
import { NDKEvent, NDKKind } from "@nostr-dev-kit/ndk";
|
||||||
import { useInfiniteQuery } from "@tanstack/react-query";
|
import { useInfiniteQuery } from "@tanstack/react-query";
|
||||||
@@ -104,14 +109,20 @@ export function UserRoute() {
|
|||||||
return (
|
return (
|
||||||
<div className="pb-5 overflow-y-auto">
|
<div className="pb-5 overflow-y-auto">
|
||||||
<WindowVirtualizer>
|
<WindowVirtualizer>
|
||||||
<div className="h-11 bg-neutral-50 dark:bg-neutral-950 border-b flex items-center px-3 border-neutral-100 dark:border-neutral-900 mb-3">
|
<div className="h-11 bg-neutral-50 dark:bg-neutral-950 border-b flex items-center justify-start gap-2 px-3 border-neutral-100 dark:border-neutral-900 mb-3">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="inline-flex items-center gap-2.5 text-sm font-medium"
|
className="size-9 hover:bg-neutral-100 hover:text-blue-500 dark:hover:bg-neutral-900 rounded-lg inline-flex items-center justify-center"
|
||||||
onClick={() => navigate(-1)}
|
onClick={() => navigate(-1)}
|
||||||
>
|
>
|
||||||
<ArrowLeftIcon className="size-4" />
|
<ArrowLeftIcon className="size-5" />
|
||||||
Back
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="size-9 hover:bg-neutral-100 hover:text-blue-500 dark:hover:bg-neutral-900 rounded-lg inline-flex items-center justify-center"
|
||||||
|
onClick={() => navigate(1)}
|
||||||
|
>
|
||||||
|
<ArrowRightIcon className="size-5" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="px-3">
|
<div className="px-3">
|
||||||
|
|||||||
@@ -1,5 +1,50 @@
|
|||||||
export const FETCH_LIMIT = 20;
|
export const FETCH_LIMIT = 20;
|
||||||
|
|
||||||
|
export const NOSTR_MENTIONS = [
|
||||||
|
"@npub1",
|
||||||
|
"nostr:npub1",
|
||||||
|
"nostr:nprofile1",
|
||||||
|
"nostr:naddr1",
|
||||||
|
"npub1",
|
||||||
|
"nprofile1",
|
||||||
|
"naddr1",
|
||||||
|
"Nostr:npub1",
|
||||||
|
"Nostr:nprofile1",
|
||||||
|
"Nostr:naddre1",
|
||||||
|
];
|
||||||
|
|
||||||
|
export const NOSTR_EVENTS = [
|
||||||
|
"@nevent1",
|
||||||
|
"@note1",
|
||||||
|
"@nostr:note1",
|
||||||
|
"@nostr:nevent1",
|
||||||
|
"nostr:note1",
|
||||||
|
"note1",
|
||||||
|
"nostr:nevent1",
|
||||||
|
"nevent1",
|
||||||
|
"Nostr:note1",
|
||||||
|
"Nostr:nevent1",
|
||||||
|
];
|
||||||
|
|
||||||
|
// const BITCOINS = ['lnbc', 'bc1p', 'bc1q'];
|
||||||
|
|
||||||
|
export const IMAGES = ["jpg", "jpeg", "gif", "png", "webp", "avif", "tiff"];
|
||||||
|
|
||||||
|
export const VIDEOS = [
|
||||||
|
"mp4",
|
||||||
|
"mov",
|
||||||
|
"webm",
|
||||||
|
"wmv",
|
||||||
|
"flv",
|
||||||
|
"mts",
|
||||||
|
"avi",
|
||||||
|
"ogv",
|
||||||
|
"mkv",
|
||||||
|
"m3u8",
|
||||||
|
];
|
||||||
|
|
||||||
|
export const AUDIOS = ["mp3", "ogg", "wav"];
|
||||||
|
|
||||||
export const HASHTAGS = [
|
export const HASHTAGS = [
|
||||||
{ hashtag: "#food" },
|
{ hashtag: "#food" },
|
||||||
{ hashtag: "#gaming" },
|
{ hashtag: "#gaming" },
|
||||||
|
|||||||
@@ -65,3 +65,6 @@ export function displayNpub(pubkey: string, len: number) {
|
|||||||
|
|
||||||
// convert number to K, M, B, T, etc.
|
// convert number to K, M, B, T, etc.
|
||||||
export const compactNumber = Intl.NumberFormat("en", { notation: "compact" });
|
export const compactNumber = Intl.NumberFormat("en", { notation: "compact" });
|
||||||
|
|
||||||
|
// country name
|
||||||
|
export const regionNames = new Intl.DisplayNames(["en"], { type: "language" });
|
||||||
|
|||||||
886
pnpm-lock.yaml
generated
886
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user