mirror of
https://github.com/luminous-devs/lume.git
synced 2024-10-01 09:21:07 +00:00
chore: fixed circular import
This commit is contained in:
parent
a9d10ff93b
commit
ad6ae6745d
@ -1,9 +1,19 @@
|
|||||||
import react from "@vitejs/plugin-react-swc";
|
import react from "@vitejs/plugin-react-swc";
|
||||||
|
import million from "million/compiler";
|
||||||
import { defineConfig } from "vite";
|
import { defineConfig } from "vite";
|
||||||
import viteTsconfigPaths from "vite-tsconfig-paths";
|
import viteTsconfigPaths from "vite-tsconfig-paths";
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react(), viteTsconfigPaths()],
|
plugins: [
|
||||||
|
million.vite({
|
||||||
|
auto: {
|
||||||
|
threshold: 0.05,
|
||||||
|
},
|
||||||
|
mute: true,
|
||||||
|
}),
|
||||||
|
react(),
|
||||||
|
viteTsconfigPaths(),
|
||||||
|
],
|
||||||
envPrefix: ["VITE_", "TAURI_"],
|
envPrefix: ["VITE_", "TAURI_"],
|
||||||
build: {
|
build: {
|
||||||
target: process.env.TAURI_PLATFORM === "windows" ? "chrome105" : "safari13",
|
target: process.env.TAURI_PLATFORM === "windows" ? "chrome105" : "safari13",
|
||||||
|
@ -15,5 +15,8 @@
|
|||||||
"packageManager": "pnpm@8.9.0",
|
"packageManager": "pnpm@8.9.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"million": "^2.6.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ import NDK, {
|
|||||||
NDKUser,
|
NDKUser,
|
||||||
NostrEvent,
|
NostrEvent,
|
||||||
} from "@nostr-dev-kit/ndk";
|
} from "@nostr-dev-kit/ndk";
|
||||||
|
import { ndkAdapter } from "@nostr-fetch/adapter-ndk";
|
||||||
import { open } from "@tauri-apps/plugin-dialog";
|
import { open } from "@tauri-apps/plugin-dialog";
|
||||||
import { readFile } from "@tauri-apps/plugin-fs";
|
import { readFile } from "@tauri-apps/plugin-fs";
|
||||||
import { fetch } from "@tauri-apps/plugin-http";
|
import { fetch } from "@tauri-apps/plugin-http";
|
||||||
@ -20,22 +21,17 @@ import { nip19 } from "nostr-tools";
|
|||||||
|
|
||||||
export class Ark {
|
export class Ark {
|
||||||
#storage: LumeStorage;
|
#storage: LumeStorage;
|
||||||
#fetcher: NostrFetcher;
|
|
||||||
public ndk: NDK;
|
public ndk: NDK;
|
||||||
|
|
||||||
constructor({
|
constructor({
|
||||||
ndk,
|
ndk,
|
||||||
storage,
|
storage,
|
||||||
|
|
||||||
fetcher,
|
|
||||||
}: {
|
}: {
|
||||||
ndk: NDK;
|
ndk: NDK;
|
||||||
storage: LumeStorage;
|
storage: LumeStorage;
|
||||||
fetcher: NostrFetcher;
|
|
||||||
}) {
|
}) {
|
||||||
this.ndk = ndk;
|
this.ndk = ndk;
|
||||||
this.#storage = storage;
|
this.#storage = storage;
|
||||||
this.#fetcher = fetcher;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async connectDepot() {
|
public async connectDepot() {
|
||||||
@ -303,17 +299,15 @@ export class Ark {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getThreads({
|
public async getThreads({ id }: { id: string }) {
|
||||||
id,
|
const fetcher = NostrFetcher.withCustomPool(ndkAdapter(this.ndk));
|
||||||
data,
|
|
||||||
}: { id: string; data?: NDKEventWithReplies[] }) {
|
|
||||||
let events = data || null;
|
|
||||||
|
|
||||||
if (!data) {
|
try {
|
||||||
const relayUrls = [...this.ndk.pool.relays.values()].map(
|
const relayUrls = [...this.ndk.pool.relays.values()].map(
|
||||||
(item) => item.url,
|
(item) => item.url,
|
||||||
);
|
);
|
||||||
const rawEvents = (await this.#fetcher.fetchAllEvents(
|
|
||||||
|
const rawEvents = (await fetcher.fetchAllEvents(
|
||||||
relayUrls,
|
relayUrls,
|
||||||
{
|
{
|
||||||
kinds: [NDKKind.Text],
|
kinds: [NDKKind.Text],
|
||||||
@ -322,64 +316,76 @@ export class Ark {
|
|||||||
{ since: 0 },
|
{ since: 0 },
|
||||||
{ sort: true },
|
{ sort: true },
|
||||||
)) as unknown as NostrEvent[];
|
)) as unknown as NostrEvent[];
|
||||||
events = rawEvents.map(
|
|
||||||
|
const events = rawEvents.map(
|
||||||
(event) => new NDKEvent(this.ndk, event),
|
(event) => new NDKEvent(this.ndk, event),
|
||||||
) as NDKEvent[] as NDKEventWithReplies[];
|
) as NDKEvent[] as NDKEventWithReplies[];
|
||||||
}
|
|
||||||
|
|
||||||
if (events.length > 0) {
|
if (events.length > 0) {
|
||||||
const replies = new Set();
|
const replies = new Set();
|
||||||
for (const event of events) {
|
for (const event of events) {
|
||||||
const tags = event.tags.filter((el) => el[0] === "e" && el[1] !== id);
|
const tags = event.tags.filter((el) => el[0] === "e" && el[1] !== id);
|
||||||
if (tags.length > 0) {
|
if (tags.length > 0) {
|
||||||
for (const tag of tags) {
|
for (const tag of tags) {
|
||||||
const rootIndex = events.findIndex((el) => el.id === tag[1]);
|
const rootIndex = events.findIndex((el) => el.id === tag[1]);
|
||||||
if (rootIndex !== -1) {
|
if (rootIndex !== -1) {
|
||||||
const rootEvent = events[rootIndex];
|
const rootEvent = events[rootIndex];
|
||||||
if (rootEvent?.replies) {
|
if (rootEvent?.replies) {
|
||||||
rootEvent.replies.push(event);
|
rootEvent.replies.push(event);
|
||||||
} else {
|
} else {
|
||||||
rootEvent.replies = [event];
|
rootEvent.replies = [event];
|
||||||
|
}
|
||||||
|
replies.add(event.id);
|
||||||
}
|
}
|
||||||
replies.add(event.id);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const cleanEvents = events.filter((ev) => !replies.has(ev.id));
|
||||||
|
return cleanEvents;
|
||||||
}
|
}
|
||||||
const cleanEvents = events.filter((ev) => !replies.has(ev.id));
|
|
||||||
return cleanEvents;
|
|
||||||
}
|
|
||||||
|
|
||||||
return events;
|
return events;
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
} finally {
|
||||||
|
fetcher.shutdown();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getAllRelaysFromContacts() {
|
public async getAllRelaysFromContacts() {
|
||||||
const LIMIT = 1;
|
const fetcher = NostrFetcher.withCustomPool(ndkAdapter(this.ndk));
|
||||||
const connectedRelays = this.ndk.pool
|
try {
|
||||||
.connectedRelays()
|
const LIMIT = 1;
|
||||||
.map((item) => item.url);
|
const connectedRelays = this.ndk.pool
|
||||||
const relayMap = new Map<string, string[]>();
|
.connectedRelays()
|
||||||
const relayEvents = this.#fetcher.fetchLatestEventsPerAuthor(
|
.map((item) => item.url);
|
||||||
{
|
const relayMap = new Map<string, string[]>();
|
||||||
authors: this.#storage.account.contacts,
|
const relayEvents = fetcher.fetchLatestEventsPerAuthor(
|
||||||
relayUrls: connectedRelays,
|
{
|
||||||
},
|
authors: this.#storage.account.contacts,
|
||||||
{ kinds: [NDKKind.RelayList] },
|
relayUrls: connectedRelays,
|
||||||
LIMIT,
|
},
|
||||||
);
|
{ kinds: [NDKKind.RelayList] },
|
||||||
|
LIMIT,
|
||||||
|
);
|
||||||
|
|
||||||
for await (const { author, events } of relayEvents) {
|
for await (const { author, events } of relayEvents) {
|
||||||
if (events[0]) {
|
if (events[0]) {
|
||||||
for (const tag of events[0].tags) {
|
for (const tag of events[0].tags) {
|
||||||
const users = relayMap.get(tag[1]);
|
const users = relayMap.get(tag[1]);
|
||||||
|
|
||||||
if (!users) relayMap.set(tag[1], [author]);
|
if (!users) relayMap.set(tag[1], [author]);
|
||||||
users.push(author);
|
users.push(author);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return relayMap;
|
return relayMap;
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
} finally {
|
||||||
|
fetcher.shutdown();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getInfiniteEvents({
|
public async getInfiniteEvents({
|
||||||
@ -395,51 +401,52 @@ export class Ark {
|
|||||||
signal?: AbortSignal;
|
signal?: AbortSignal;
|
||||||
dedup?: boolean;
|
dedup?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const seenIds = new Set<string>();
|
const fetcher = NostrFetcher.withCustomPool(ndkAdapter(this.ndk));
|
||||||
const dedupQueue = new Set<string>();
|
|
||||||
|
|
||||||
const relayUrls = [...this.ndk.pool.relays.values()].map(
|
const relayUrls = [...this.ndk.pool.relays.values()].map(
|
||||||
(item) => item.url,
|
(item) => item.url,
|
||||||
);
|
);
|
||||||
|
const seenIds = new Set<string>();
|
||||||
|
const dedupQueue = new Set<string>();
|
||||||
|
|
||||||
const events = await this.#fetcher.fetchLatestEvents(
|
try {
|
||||||
relayUrls,
|
const events = await fetcher.fetchLatestEvents(relayUrls, filter, limit, {
|
||||||
filter,
|
|
||||||
limit,
|
|
||||||
{
|
|
||||||
asOf: pageParam === 0 ? undefined : pageParam,
|
asOf: pageParam === 0 ? undefined : pageParam,
|
||||||
abortSignal: signal,
|
abortSignal: signal,
|
||||||
},
|
});
|
||||||
);
|
|
||||||
|
|
||||||
const ndkEvents = events.map((event) => {
|
const ndkEvents = events.map((event) => {
|
||||||
return new NDKEvent(this.ndk, event);
|
return new NDKEvent(this.ndk, event);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (dedup) {
|
if (dedup) {
|
||||||
for (const event of ndkEvents) {
|
for (const event of ndkEvents) {
|
||||||
const tags = event.tags
|
const tags = event.tags
|
||||||
.filter((el) => el[0] === "e")
|
.filter((el) => el[0] === "e")
|
||||||
?.map((item) => item[1]);
|
?.map((item) => item[1]);
|
||||||
|
|
||||||
if (tags.length) {
|
if (tags.length) {
|
||||||
for (const tag of tags) {
|
for (const tag of tags) {
|
||||||
if (seenIds.has(tag)) {
|
if (seenIds.has(tag)) {
|
||||||
dedupQueue.add(event.id);
|
dedupQueue.add(event.id);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
seenIds.add(tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
seenIds.add(tag);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return ndkEvents
|
||||||
|
.filter((event) => !dedupQueue.has(event.id))
|
||||||
|
.sort((a, b) => b.created_at - a.created_at);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ndkEvents
|
return ndkEvents.sort((a, b) => b.created_at - a.created_at);
|
||||||
.filter((event) => !dedupQueue.has(event.id))
|
} catch (e) {
|
||||||
.sort((a, b) => b.created_at - a.created_at);
|
console.log(e);
|
||||||
|
} finally {
|
||||||
|
fetcher.shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
return ndkEvents.sort((a, b) => b.created_at - a.created_at);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getRelayEvents({
|
public async getRelayEvents({
|
||||||
@ -456,21 +463,29 @@ export class Ark {
|
|||||||
signal?: AbortSignal;
|
signal?: AbortSignal;
|
||||||
dedup?: boolean;
|
dedup?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const events = await this.#fetcher.fetchLatestEvents(
|
const fetcher = NostrFetcher.withCustomPool(ndkAdapter(this.ndk));
|
||||||
[normalizeRelayUrl(relayUrl)],
|
|
||||||
filter,
|
|
||||||
limit,
|
|
||||||
{
|
|
||||||
asOf: pageParam === 0 ? undefined : pageParam,
|
|
||||||
abortSignal: signal,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const ndkEvents = events.map((event) => {
|
try {
|
||||||
return new NDKEvent(this.ndk, event);
|
const events = await fetcher.fetchLatestEvents(
|
||||||
});
|
[normalizeRelayUrl(relayUrl)],
|
||||||
|
filter,
|
||||||
|
limit,
|
||||||
|
{
|
||||||
|
asOf: pageParam === 0 ? undefined : pageParam,
|
||||||
|
abortSignal: signal,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
return ndkEvents.sort((a, b) => b.created_at - a.created_at);
|
const ndkEvents = events.map((event) => {
|
||||||
|
return new NDKEvent(this.ndk, event);
|
||||||
|
});
|
||||||
|
|
||||||
|
return ndkEvents.sort((a, b) => b.created_at - a.created_at);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
} finally {
|
||||||
|
fetcher.shutdown();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -11,5 +11,3 @@ export const Column = {
|
|||||||
Content: ColumnContent,
|
Content: ColumnContent,
|
||||||
Route: Route,
|
Route: Route,
|
||||||
};
|
};
|
||||||
|
|
||||||
export * from "./provider";
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { PinIcon } from "@lume/icons";
|
import { PinIcon } from "@lume/icons";
|
||||||
import { COL_TYPES } from "@lume/utils";
|
import { COL_TYPES } from "@lume/utils";
|
||||||
import * as Tooltip from "@radix-ui/react-tooltip";
|
import * as Tooltip from "@radix-ui/react-tooltip";
|
||||||
import { useNoteContext } from "..";
|
import { useColumnContext } from "../../column/provider";
|
||||||
import { useColumnContext } from "../../column";
|
import { useNoteContext } from "../provider";
|
||||||
|
|
||||||
export function NotePin() {
|
export function NotePin() {
|
||||||
const event = useNoteContext();
|
const event = useNoteContext();
|
||||||
|
@ -15,17 +15,15 @@ import { nip19 } from "nostr-tools";
|
|||||||
import { ReactNode, useMemo, useState } from "react";
|
import { ReactNode, useMemo, useState } from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import reactStringReplace from "react-string-replace";
|
import reactStringReplace from "react-string-replace";
|
||||||
import {
|
import { useStorage } from "../../provider";
|
||||||
Hashtag,
|
import { Hashtag } from "./mentions/hashtag";
|
||||||
ImagePreview,
|
import { MentionNote } from "./mentions/note";
|
||||||
LinkPreview,
|
import { MentionUser } from "./mentions/user";
|
||||||
MentionNote,
|
|
||||||
MentionUser,
|
|
||||||
VideoPreview,
|
|
||||||
useNoteContext,
|
|
||||||
useStorage,
|
|
||||||
} from "../..";
|
|
||||||
import { NIP89 } from "./nip89";
|
import { NIP89 } from "./nip89";
|
||||||
|
import { ImagePreview } from "./preview/image";
|
||||||
|
import { LinkPreview } from "./preview/link";
|
||||||
|
import { VideoPreview } from "./preview/video";
|
||||||
|
import { useNoteContext } from "./provider";
|
||||||
|
|
||||||
export function NoteContent({
|
export function NoteContent({
|
||||||
className,
|
className,
|
||||||
@ -229,7 +227,7 @@ export function NoteContent({
|
|||||||
|
|
||||||
return parsedContent;
|
return parsedContent;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("[parser] parse failed: ", e);
|
console.warn(event.id, `[parser] parse failed: ${e}`);
|
||||||
return parsedContent;
|
return parsedContent;
|
||||||
}
|
}
|
||||||
}, [content]);
|
}, [content]);
|
||||||
@ -251,7 +249,7 @@ export function NoteContent({
|
|||||||
setContent(data.translatedText);
|
setContent(data.translatedText);
|
||||||
setTranslated(true);
|
setTranslated(true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(String(e));
|
console.error(event.id, String(e));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -260,7 +258,7 @@ export function NoteContent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn("", className)}>
|
<div className={cn(className)}>
|
||||||
<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>
|
||||||
|
@ -25,17 +25,3 @@ export const Note = {
|
|||||||
Child: NoteChild,
|
Child: NoteChild,
|
||||||
Thread: NoteThread,
|
Thread: NoteThread,
|
||||||
};
|
};
|
||||||
|
|
||||||
export * from "./provider";
|
|
||||||
export * from "./primitives/text";
|
|
||||||
export * from "./primitives/repost";
|
|
||||||
export * from "./primitives/skeleton";
|
|
||||||
export * from "./primitives/thread";
|
|
||||||
export * from "./primitives/reply";
|
|
||||||
export * from "./preview/image";
|
|
||||||
export * from "./preview/link";
|
|
||||||
export * from "./preview/video";
|
|
||||||
export * from "./mentions/note";
|
|
||||||
export * from "./mentions/user";
|
|
||||||
export * from "./mentions/hashtag";
|
|
||||||
export * from "./mentions/invoice";
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { COL_TYPES } from "@lume/utils";
|
import { COL_TYPES } from "@lume/utils";
|
||||||
import { useColumnContext } from "../../column";
|
import { useColumnContext } from "../../column/provider";
|
||||||
|
|
||||||
export function Hashtag({ tag }: { tag: string }) {
|
export function Hashtag({ tag }: { tag: string }) {
|
||||||
const { addColumn } = useColumnContext();
|
const { addColumn } = useColumnContext();
|
||||||
|
@ -3,7 +3,7 @@ import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
|
|||||||
import { memo } from "react";
|
import { memo } from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { useProfile } from "../../../hooks/useProfile";
|
import { useProfile } from "../../../hooks/useProfile";
|
||||||
import { useColumnContext } from "../../column";
|
import { useColumnContext } from "../../column/provider";
|
||||||
|
|
||||||
export const MentionUser = memo(function MentionUser({
|
export const MentionUser = memo(function MentionUser({
|
||||||
pubkey,
|
pubkey,
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { HorizontalDotsIcon, ShareIcon } from "@lume/icons";
|
import { HorizontalDotsIcon } from "@lume/icons";
|
||||||
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
|
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
|
||||||
import { writeText } from "@tauri-apps/plugin-clipboard-manager";
|
import { writeText } from "@tauri-apps/plugin-clipboard-manager";
|
||||||
import { nip19 } from "nostr-tools";
|
import { nip19 } from "nostr-tools";
|
||||||
import { EventPointer } from "nostr-tools/lib/types/nip19";
|
import { type EventPointer } from "nostr-tools/lib/types/nip19";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { useNoteContext } from "./provider";
|
import { useNoteContext } from "./provider";
|
||||||
|
@ -2,9 +2,8 @@ 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, useNoteContext } from ".";
|
import { Note } from ".";
|
||||||
import { useArk } from "../..";
|
import { useArk, useColumnContext, useNoteContext } from "../..";
|
||||||
import { useColumnContext } from "../column";
|
|
||||||
|
|
||||||
export function NoteThread({
|
export function NoteThread({
|
||||||
className,
|
className,
|
||||||
|
@ -67,7 +67,7 @@ export function NoteUser({
|
|||||||
decoding="async"
|
decoding="async"
|
||||||
className="h-6 w-6 shrink-0 object-cover rounded-md"
|
className="h-6 w-6 shrink-0 object-cover rounded-md"
|
||||||
/>
|
/>
|
||||||
<Avatar.Fallback delayMs={300}>
|
<Avatar.Fallback delayMs={150}>
|
||||||
<img
|
<img
|
||||||
src={fallbackAvatar}
|
src={fallbackAvatar}
|
||||||
alt={event.pubkey}
|
alt={event.pubkey}
|
||||||
@ -120,7 +120,7 @@ export function NoteUser({
|
|||||||
decoding="async"
|
decoding="async"
|
||||||
className="h-6 w-6 rounded object-cover"
|
className="h-6 w-6 rounded object-cover"
|
||||||
/>
|
/>
|
||||||
<Avatar.Fallback delayMs={300}>
|
<Avatar.Fallback delayMs={150}>
|
||||||
<img
|
<img
|
||||||
src={fallbackAvatar}
|
src={fallbackAvatar}
|
||||||
alt={event.pubkey}
|
alt={event.pubkey}
|
||||||
@ -165,7 +165,7 @@ export function NoteUser({
|
|||||||
decoding="async"
|
decoding="async"
|
||||||
className="h-10 w-10 rounded-lg object-cover ring-1 ring-neutral-200/50 dark:ring-neutral-800/50"
|
className="h-10 w-10 rounded-lg object-cover ring-1 ring-neutral-200/50 dark:ring-neutral-800/50"
|
||||||
/>
|
/>
|
||||||
<Avatar.Fallback delayMs={300}>
|
<Avatar.Fallback delayMs={150}>
|
||||||
<img
|
<img
|
||||||
src={fallbackAvatar}
|
src={fallbackAvatar}
|
||||||
alt={event.pubkey}
|
alt={event.pubkey}
|
||||||
@ -216,7 +216,7 @@ export function NoteUser({
|
|||||||
decoding="async"
|
decoding="async"
|
||||||
className="h-9 w-9 rounded-lg bg-white object-cover ring-1 ring-neutral-200/50 dark:ring-neutral-800/50"
|
className="h-9 w-9 rounded-lg bg-white object-cover ring-1 ring-neutral-200/50 dark:ring-neutral-800/50"
|
||||||
/>
|
/>
|
||||||
<Avatar.Fallback delayMs={300}>
|
<Avatar.Fallback delayMs={150}>
|
||||||
<img
|
<img
|
||||||
src={fallbackAvatar}
|
src={fallbackAvatar}
|
||||||
alt={event.pubkey}
|
alt={event.pubkey}
|
||||||
|
@ -1,246 +0,0 @@
|
|||||||
import getUrls from "get-urls";
|
|
||||||
import { nanoid } from "nanoid";
|
|
||||||
import { nip19 } from "nostr-tools";
|
|
||||||
import { ReactNode } from "react";
|
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
import reactStringReplace from "react-string-replace";
|
|
||||||
import {
|
|
||||||
Hashtag,
|
|
||||||
ImagePreview,
|
|
||||||
LinkPreview,
|
|
||||||
MentionNote,
|
|
||||||
MentionUser,
|
|
||||||
VideoPreview,
|
|
||||||
} from "../components/note";
|
|
||||||
import { useStorage } from "../provider";
|
|
||||||
|
|
||||||
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 useRichContent(content: string) {
|
|
||||||
const storage = useStorage();
|
|
||||||
|
|
||||||
let parsedContent: string | ReactNode[] = content.replace(/\n+/g, "\n");
|
|
||||||
let linkPreview: string;
|
|
||||||
let images: string[] = [];
|
|
||||||
let videos: string[] = [];
|
|
||||||
let audios: string[] = [];
|
|
||||||
let events: string[] = [];
|
|
||||||
|
|
||||||
const text = parsedContent;
|
|
||||||
const words = text.split(/( |\n)/);
|
|
||||||
const urls = [...getUrls(text)];
|
|
||||||
|
|
||||||
if (storage.settings.media && !storage.settings.lowPower) {
|
|
||||||
images = urls.filter((word) =>
|
|
||||||
IMAGES.some((el) => {
|
|
||||||
const url = new URL(word);
|
|
||||||
const extension = url.pathname.split(".")[1];
|
|
||||||
if (extension === el) return true;
|
|
||||||
return false;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
videos = urls.filter((word) =>
|
|
||||||
VIDEOS.some((el) => {
|
|
||||||
const url = new URL(word);
|
|
||||||
const extension = url.pathname.split(".")[1];
|
|
||||||
if (extension === el) return true;
|
|
||||||
return false;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
audios = urls.filter((word) =>
|
|
||||||
AUDIOS.some((el) => {
|
|
||||||
const url = new URL(word);
|
|
||||||
const extension = url.pathname.split(".")[1];
|
|
||||||
if (extension === el) return true;
|
|
||||||
return false;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
events = words.filter((word) =>
|
|
||||||
NOSTR_EVENTS.some((el) => word.startsWith(el)),
|
|
||||||
);
|
|
||||||
|
|
||||||
const hashtags = words.filter((word) => word.startsWith("#"));
|
|
||||||
const mentions = words.filter((word) =>
|
|
||||||
NOSTR_MENTIONS.some((el) => word.startsWith(el)),
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (images.length) {
|
|
||||||
for (const image of images) {
|
|
||||||
parsedContent = reactStringReplace(parsedContent, image, (match, i) => (
|
|
||||||
<ImagePreview key={match + i} url={match} />
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (videos.length) {
|
|
||||||
for (const video of videos) {
|
|
||||||
parsedContent = reactStringReplace(parsedContent, video, (match, i) => (
|
|
||||||
<VideoPreview key={match + i} url={match} />
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (audios.length) {
|
|
||||||
for (const audio of audios) {
|
|
||||||
parsedContent = reactStringReplace(parsedContent, audio, (match, i) => (
|
|
||||||
<VideoPreview key={match + i} url={match} />
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hashtags.length) {
|
|
||||||
for (const hashtag of hashtags) {
|
|
||||||
parsedContent = reactStringReplace(
|
|
||||||
parsedContent,
|
|
||||||
hashtag,
|
|
||||||
(match, i) => {
|
|
||||||
if (storage.settings.hashtag)
|
|
||||||
return <Hashtag key={match + i} tag={hashtag} />;
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (events.length) {
|
|
||||||
for (const event of events) {
|
|
||||||
const address = event
|
|
||||||
.replace("nostr:", "")
|
|
||||||
.replace(/[^a-zA-Z0-9]/g, "");
|
|
||||||
const decoded = nip19.decode(address);
|
|
||||||
|
|
||||||
if (decoded.type === "note") {
|
|
||||||
parsedContent = reactStringReplace(
|
|
||||||
parsedContent,
|
|
||||||
event,
|
|
||||||
(match, i) => (
|
|
||||||
<MentionNote key={match + i} eventId={decoded.data} />
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (decoded.type === "nevent") {
|
|
||||||
parsedContent = reactStringReplace(
|
|
||||||
parsedContent,
|
|
||||||
event,
|
|
||||||
(match, i) => (
|
|
||||||
<MentionNote key={match + i} eventId={decoded.data.id} />
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
if (!linkPreview) {
|
|
||||||
linkPreview = match;
|
|
||||||
return <LinkPreview key={match + i} url={url.toString()} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
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>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
parsedContent = reactStringReplace(parsedContent, "\n", () => {
|
|
||||||
return <div key={nanoid()} className="h-3" />;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (typeof parsedContent[0] === "string") {
|
|
||||||
parsedContent[0] = parsedContent[0].trimStart();
|
|
||||||
}
|
|
||||||
|
|
||||||
return parsedContent;
|
|
||||||
} catch (e) {
|
|
||||||
console.warn("[parser] parse failed: ", e);
|
|
||||||
return parsedContent;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,8 +1,21 @@
|
|||||||
export * from "./ark";
|
export * from "./ark";
|
||||||
export * from "./provider";
|
export * from "./provider";
|
||||||
export * from "./components/column";
|
|
||||||
export * from "./components/note";
|
|
||||||
export * from "./hooks/useRichContent";
|
|
||||||
export * from "./hooks/useEvent";
|
export * from "./hooks/useEvent";
|
||||||
export * from "./hooks/useProfile";
|
export * from "./hooks/useProfile";
|
||||||
export * from "./hooks/useRelay";
|
export * from "./hooks/useRelay";
|
||||||
|
export * from "./components/column/provider";
|
||||||
|
export * from "./components/column";
|
||||||
|
export * from "./components/note";
|
||||||
|
export * from "./components/note/provider";
|
||||||
|
export * from "./components/note/primitives/text";
|
||||||
|
export * from "./components/note/primitives/repost";
|
||||||
|
export * from "./components/note/primitives/skeleton";
|
||||||
|
export * from "./components/note/primitives/thread";
|
||||||
|
export * from "./components/note/primitives/reply";
|
||||||
|
export * from "./components/note/preview/image";
|
||||||
|
export * from "./components/note/preview/link";
|
||||||
|
export * from "./components/note/preview/video";
|
||||||
|
export * from "./components/note/mentions/note";
|
||||||
|
export * from "./components/note/mentions/user";
|
||||||
|
export * from "./components/note/mentions/hashtag";
|
||||||
|
export * from "./components/note/mentions/invoice";
|
||||||
|
@ -10,7 +10,6 @@ import NDK, {
|
|||||||
NDKRelay,
|
NDKRelay,
|
||||||
NDKRelayAuthPolicies,
|
NDKRelayAuthPolicies,
|
||||||
} from "@nostr-dev-kit/ndk";
|
} from "@nostr-dev-kit/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 { locale, platform } from "@tauri-apps/plugin-os";
|
import { locale, platform } from "@tauri-apps/plugin-os";
|
||||||
@ -18,11 +17,7 @@ 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";
|
||||||
import Markdown from "markdown-to-jsx";
|
import Markdown from "markdown-to-jsx";
|
||||||
import {
|
import { normalizeRelayUrl, normalizeRelayUrlSet } from "nostr-fetch";
|
||||||
NostrFetcher,
|
|
||||||
normalizeRelayUrl,
|
|
||||||
normalizeRelayUrlSet,
|
|
||||||
} from "nostr-fetch";
|
|
||||||
import { PropsWithChildren, useEffect, useState } from "react";
|
import { PropsWithChildren, useEffect, useState } from "react";
|
||||||
import { createContext, useContextSelector } from "use-context-selector";
|
import { createContext, useContextSelector } from "use-context-selector";
|
||||||
import { Ark } from "./ark";
|
import { Ark } from "./ark";
|
||||||
@ -38,8 +33,6 @@ const LumeContext = createContext<Context>({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const LumeProvider = ({ children }: PropsWithChildren<object>) => {
|
const LumeProvider = ({ children }: PropsWithChildren<object>) => {
|
||||||
const queryClient = useQueryClient();
|
|
||||||
|
|
||||||
const [context, setContext] = useState<Context>(undefined);
|
const [context, setContext] = useState<Context>(undefined);
|
||||||
const [isNewVersion, setIsNewVersion] = useState(false);
|
const [isNewVersion, setIsNewVersion] = useState(false);
|
||||||
|
|
||||||
@ -219,11 +212,8 @@ const LumeProvider = ({ children }: PropsWithChildren<object>) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// init nostr fetcher
|
|
||||||
const fetcher = NostrFetcher.withCustomPool(ndkAdapter(ndk));
|
|
||||||
|
|
||||||
// ark utils
|
// ark utils
|
||||||
const ark = new Ark({ storage, ndk, fetcher });
|
const ark = new Ark({ storage, ndk });
|
||||||
|
|
||||||
// update context
|
// update context
|
||||||
setContext({ ark, storage });
|
setContext({ ark, storage });
|
||||||
|
849
pnpm-lock.yaml
849
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user