diff --git a/apps/desktop/package.json b/apps/desktop/package.json
index 0057448c..987150aa 100644
--- a/apps/desktop/package.json
+++ b/apps/desktop/package.json
@@ -9,6 +9,7 @@
"dependencies": {
"@columns/antenas": "workspace:^",
"@columns/default": "workspace:^",
+ "@columns/foryou": "workspace:^",
"@columns/group": "workspace:^",
"@columns/hashtag": "workspace:^",
"@columns/thread": "workspace:^",
diff --git a/apps/desktop/src/routes/auth/create.tsx b/apps/desktop/src/routes/auth/create.tsx
index e17fa188..69623dcb 100644
--- a/apps/desktop/src/routes/auth/create.tsx
+++ b/apps/desktop/src/routes/auth/create.tsx
@@ -1,6 +1,7 @@
import { useArk } from "@lume/ark";
import { CheckIcon, ChevronDownIcon, LoaderIcon } from "@lume/icons";
import { useStorage } from "@lume/storage";
+import { onboardingAtom } from "@lume/utils";
import NDK, {
NDKEvent,
NDKKind,
@@ -13,6 +14,7 @@ import { desktopDir } from "@tauri-apps/api/path";
import { Window } from "@tauri-apps/api/window";
import { save } from "@tauri-apps/plugin-dialog";
import { writeTextFile } from "@tauri-apps/plugin-fs";
+import { useSetAtom } from "jotai";
import { nanoid } from "nanoid";
import { getPublicKey, nip19 } from "nostr-tools";
import { useState } from "react";
@@ -40,6 +42,7 @@ export function CreateAccountScreen() {
const ark = useArk();
const storage = useStorage();
const services = useLoaderData() as NDKEvent[];
+ const setOnboarding = useSetAtom(onboardingAtom);
const navigate = useNavigate();
const [serviceId, setServiceId] = useState(services?.[0]?.id);
@@ -85,6 +88,8 @@ export function CreateAccountScreen() {
privkey: signer.privateKey,
});
+ setOnboarding({ open: true, newUser: true });
+
return navigate("/auth/onboarding", { replace: true });
};
@@ -176,6 +181,7 @@ export function CreateAccountScreen() {
await ark.createEvent({ kind: NDKKind.Contacts, content: "", tags: [] });
setIsLoading(false);
+ setOnboarding({ open: true, newUser: true });
return navigate("/auth/onboarding", { replace: true });
} catch (e) {
diff --git a/apps/desktop/src/routes/home/index.tsx b/apps/desktop/src/routes/home/index.tsx
index 9480eea6..5d6b04ad 100644
--- a/apps/desktop/src/routes/home/index.tsx
+++ b/apps/desktop/src/routes/home/index.tsx
@@ -1,5 +1,6 @@
import { Antenas } from "@columns/antenas";
import { Default } from "@columns/default";
+import { ForYou } from "@columns/foryou";
import { Group } from "@columns/group";
import { Hashtag } from "@columns/hashtag";
import { Thread } from "@columns/thread";
@@ -24,6 +25,8 @@ export function HomeScreen() {
return ;
case COL_TYPES.newsfeed:
return ;
+ case COL_TYPES.foryou:
+ return ;
case COL_TYPES.thread:
return ;
case COL_TYPES.user:
diff --git a/packages/ark/package.json b/packages/ark/package.json
index 606a280d..b3bd2497 100644
--- a/packages/ark/package.json
+++ b/packages/ark/package.json
@@ -35,6 +35,7 @@
"react-router-dom": "^6.21.3",
"react-string-replace": "^1.1.1",
"sonner": "^1.3.1",
+ "string-strip-html": "^13.4.5",
"tippy.js": "^6.3.7",
"use-context-selector": "^1.4.1"
},
diff --git a/packages/ark/src/components/column/provider.tsx b/packages/ark/src/components/column/provider.tsx
index c5580625..b727bdf5 100644
--- a/packages/ark/src/components/column/provider.tsx
+++ b/packages/ark/src/components/column/provider.tsx
@@ -30,6 +30,12 @@ export function ColumnProvider({ children }: { children: ReactNode }) {
content: "",
kind: COL_TYPES.newsfeed,
},
+ {
+ id: 9998,
+ title: "For You",
+ content: "",
+ kind: COL_TYPES.foryou,
+ },
]);
const loadAllColumns = useCallback(async () => {
diff --git a/packages/ark/src/components/note/content.tsx b/packages/ark/src/components/note/content.tsx
index 6c08db85..119a7e4f 100644
--- a/packages/ark/src/components/note/content.tsx
+++ b/packages/ark/src/components/note/content.tsx
@@ -13,10 +13,11 @@ import { NDKKind } from "@nostr-dev-kit/ndk";
import { fetch } from "@tauri-apps/plugin-http";
import getUrls from "get-urls";
import { nanoid } from "nanoid";
-import { ReactNode, useEffect, useMemo, useState } from "react";
+import { ReactNode, useMemo, useState } from "react";
import { Link } from "react-router-dom";
import reactStringReplace from "react-string-replace";
import { toast } from "sonner";
+import { stripHtml } from "string-strip-html";
import { Hashtag } from "./mentions/hashtag";
import { MentionNote } from "./mentions/note";
import { MentionUser } from "./mentions/user";
@@ -28,10 +29,8 @@ import { useNoteContext } from "./provider";
export function NoteContent({
className,
- mini = false,
}: {
className?: string;
- mini?: boolean;
}) {
const storage = useStorage();
const event = useNoteContext();
@@ -45,7 +44,9 @@ export function NoteContent({
const richContent = useMemo(() => {
if (event.kind !== NDKKind.Text) return content;
- let parsedContent: string | ReactNode[] = content.replace(/\n+/g, "\n");
+ let parsedContent: string | ReactNode[] = stripHtml(
+ content.replace(/\n{2,}\s*/g, "\n"),
+ ).result;
let linkPreview: string = undefined;
let images: string[] = [];
let videos: string[] = [];
@@ -56,7 +57,7 @@ export function NoteContent({
const words = text.split(/( |\n)/);
const urls = [...getUrls(text)];
- if (storage.settings.media && !storage.settings.lowPower && !mini) {
+ if (storage.settings.media && !storage.settings.lowPower) {
images = urls.filter((word) =>
IMAGES.some((el) => {
const url = new URL(word);
@@ -83,11 +84,9 @@ export function NoteContent({
);
}
- if (!mini) {
- events = words.filter((word) =>
- NOSTR_EVENTS.some((el) => word.startsWith(el)),
- );
- }
+ events = words.filter((word) =>
+ NOSTR_EVENTS.some((el) => word.startsWith(el)),
+ );
const hashtags = words.filter((word) => word.startsWith("#"));
const mentions = words.filter((word) =>
@@ -184,11 +183,9 @@ export function NoteContent({
},
);
- if (!mini) {
- parsedContent = reactStringReplace(parsedContent, "\n", () => {
- return
;
- });
- }
+ parsedContent = reactStringReplace(parsedContent, "\n", () => {
+ return ;
+ });
if (typeof parsedContent[0] === "string") {
parsedContent[0] = parsedContent[0].trimStart();
@@ -235,12 +232,7 @@ export function NoteContent({
return (
-
+
{richContent}
{storage.settings.translation && translate.translatable ? (
diff --git a/packages/ark/src/components/note/mentions/note.tsx b/packages/ark/src/components/note/mentions/note.tsx
index 536e8974..4a6b2172 100644
--- a/packages/ark/src/components/note/mentions/note.tsx
+++ b/packages/ark/src/components/note/mentions/note.tsx
@@ -1,6 +1,6 @@
import { PinIcon } from "@lume/icons";
import { COL_TYPES, NOSTR_MENTIONS } from "@lume/utils";
-import { ReactNode, memo, useMemo } from "react";
+import { ReactNode, useMemo } from "react";
import { Link } from "react-router-dom";
import reactStringReplace from "react-string-replace";
import { useEvent } from "../../../hooks/useEvent";
@@ -9,7 +9,7 @@ import { User } from "../../user";
import { Hashtag } from "./hashtag";
import { MentionUser } from "./user";
-export const MentionNote = memo(function MentionNote({
+export function MentionNote({
eventId,
openable = true,
}: { eventId: string; openable?: boolean }) {
@@ -66,7 +66,7 @@ export const MentionNote = memo(function MentionNote({
to={url.toString()}
target="_blank"
rel="noreferrer"
- className="break-p font-normal text-blue-500 hover:text-blue-600"
+ className="break-p inline-block truncate w-full font-normal text-blue-500 hover:text-blue-600"
>
{url.toString()}
@@ -104,50 +104,48 @@ export const MentionNote = memo(function MentionNote({
}
return (
-
-
-
-
-
-
-
- ·
-
-
-
-
-
- {richContent}
-
- {openable ? (
-
-
- Show more
-
-
+
+
+
+
+
+
+ ·
+
- ) : (
-
- )}
+
+
+
+ {richContent}
+ {openable ? (
+
+
+ Show more
+
+
+
+ ) : (
+
+ )}
);
-});
+}
diff --git a/packages/ark/src/components/note/mentions/user.tsx b/packages/ark/src/components/note/mentions/user.tsx
index c236658b..b47fad9f 100644
--- a/packages/ark/src/components/note/mentions/user.tsx
+++ b/packages/ark/src/components/note/mentions/user.tsx
@@ -1,14 +1,11 @@
import { COL_TYPES } from "@lume/utils";
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
-import { memo } from "react";
import { Link } from "react-router-dom";
import { useArk } from "../../../hooks/useArk";
import { useProfile } from "../../../hooks/useProfile";
import { useColumnContext } from "../../column/provider";
-export const MentionUser = memo(function MentionUser({
- pubkey,
-}: { pubkey: string }) {
+export function MentionUser({ pubkey }: { pubkey: string }) {
const ark = useArk();
const cleanPubkey = ark.getCleanPubkey(pubkey);
@@ -51,4 +48,4 @@ export const MentionUser = memo(function MentionUser({
);
-});
+}
diff --git a/packages/ark/src/components/note/preview/link.tsx b/packages/ark/src/components/note/preview/link.tsx
index d888080e..ee1d4e75 100644
--- a/packages/ark/src/components/note/preview/link.tsx
+++ b/packages/ark/src/components/note/preview/link.tsx
@@ -11,7 +11,7 @@ export function LinkPreview({ url }: { url: string }) {
if (status === "pending") {
return (
-
+
@@ -24,7 +24,7 @@ export function LinkPreview({ url }: { url: string }) {
);
}
- if (!data.title && !data.image) {
+ if (!data.title && !data.image && !data.description) {
return (
) : null}
@@ -59,7 +61,7 @@ export function LinkPreview({ url }: { url: string }) {
) : null}
{data.description ? (
-
+
{data.description}
) : null}
diff --git a/packages/icons/index.ts b/packages/icons/index.ts
index c992993c..be1c7d44 100644
--- a/packages/icons/index.ts
+++ b/packages/icons/index.ts
@@ -107,3 +107,4 @@ export * from "./src/popperFilled";
export * from "./src/composeFilled";
export * from "./src/settingsFilled";
export * from "./src/bellFilled";
+export * from "./src/foryou";
diff --git a/packages/icons/src/foryou.tsx b/packages/icons/src/foryou.tsx
new file mode 100644
index 00000000..9bca69ce
--- /dev/null
+++ b/packages/icons/src/foryou.tsx
@@ -0,0 +1,24 @@
+import { SVGProps } from "react";
+
+export function ForyouIcon(
+ props: JSX.IntrinsicAttributes & SVGProps
,
+) {
+ return (
+
+ );
+}
diff --git a/packages/lume-column-foryou/package.json b/packages/lume-column-foryou/package.json
new file mode 100644
index 00000000..bec98934
--- /dev/null
+++ b/packages/lume-column-foryou/package.json
@@ -0,0 +1,27 @@
+{
+ "name": "@columns/foryou",
+ "version": "0.0.0",
+ "private": true,
+ "main": "./src/index.tsx",
+ "dependencies": {
+ "@lume/ark": "workspace:^",
+ "@lume/icons": "workspace:^",
+ "@lume/storage": "workspace:^",
+ "@lume/ui": "workspace:^",
+ "@lume/utils": "workspace:^",
+ "@nostr-dev-kit/ndk": "^2.3.3",
+ "@tanstack/react-query": "^5.17.15",
+ "react": "^18.2.0",
+ "react-router-dom": "^6.21.3",
+ "sonner": "^1.3.1",
+ "virtua": "^0.20.5"
+ },
+ "devDependencies": {
+ "@lume/tailwindcss": "workspace:^",
+ "@lume/tsconfig": "workspace:^",
+ "@lume/types": "workspace:^",
+ "@types/react": "^18.2.48",
+ "tailwind": "^4.0.0",
+ "typescript": "^5.3.3"
+ }
+}
diff --git a/packages/lume-column-foryou/src/home.tsx b/packages/lume-column-foryou/src/home.tsx
new file mode 100644
index 00000000..11370895
--- /dev/null
+++ b/packages/lume-column-foryou/src/home.tsx
@@ -0,0 +1,118 @@
+import { TextNote, useArk } from "@lume/ark";
+import { ArrowRightCircleIcon, LoaderIcon } from "@lume/icons";
+import { useStorage } from "@lume/storage";
+import { FETCH_LIMIT } from "@lume/utils";
+import { NDKEvent, NDKKind } from "@nostr-dev-kit/ndk";
+import { useInfiniteQuery, useQueryClient } from "@tanstack/react-query";
+import { useEffect, useMemo, useRef } from "react";
+import { CacheSnapshot, VList, VListHandle } from "virtua";
+
+export function HomeRoute({ colKey }: { colKey: string }) {
+ const ark = useArk();
+ const storage = useStorage();
+ const ref = useRef();
+ const cacheKey = `${colKey}-vlist`;
+ const queryClient = useQueryClient();
+
+ const [offset, cache] = useMemo(() => {
+ const serialized = sessionStorage.getItem(cacheKey);
+ if (!serialized) return [];
+ return JSON.parse(serialized) as [number, CacheSnapshot];
+ }, []);
+
+ const { data, hasNextPage, isLoading, isFetchingNextPage, fetchNextPage } =
+ useInfiniteQuery({
+ queryKey: [colKey],
+ initialPageParam: 0,
+ queryFn: async ({
+ signal,
+ pageParam,
+ }: {
+ signal: AbortSignal;
+ pageParam: number;
+ }) => {
+ const events = await ark.getInfiniteEvents({
+ filter: {
+ kinds: [NDKKind.Text],
+ "#t": storage.interests.hashtags,
+ },
+ limit: FETCH_LIMIT,
+ pageParam,
+ signal,
+ });
+
+ return events;
+ },
+ getNextPageParam: (lastPage) => {
+ const lastEvent = lastPage.at(-1);
+ if (!lastEvent) return;
+ return lastEvent.created_at - 1;
+ },
+ initialData: () => {
+ const queryCacheData = queryClient.getQueryState([colKey])
+ ?.data as NDKEvent[];
+ if (queryCacheData) {
+ return {
+ pageParams: [undefined, 1],
+ pages: [queryCacheData],
+ };
+ }
+ },
+ select: (data) => data?.pages.flatMap((page) => page),
+ staleTime: 120 * 1000,
+ refetchOnWindowFocus: false,
+ refetchOnMount: false,
+ });
+
+ useEffect(() => {
+ if (!ref.current) return;
+
+ const handle = ref.current;
+
+ if (offset) {
+ handle.scrollTo(offset);
+ }
+
+ return () => {
+ sessionStorage.setItem(
+ cacheKey,
+ JSON.stringify([handle.scrollOffset, handle.cache]),
+ );
+ };
+ }, []);
+
+ return (
+
+
+ {isLoading ? (
+
+
+
+ ) : (
+ data.map((event) => (
+
+ ))
+ )}
+
+ {hasNextPage ? (
+
+ ) : null}
+
+
+
+ );
+}
diff --git a/packages/lume-column-foryou/src/index.tsx b/packages/lume-column-foryou/src/index.tsx
new file mode 100644
index 00000000..b36a998a
--- /dev/null
+++ b/packages/lume-column-foryou/src/index.tsx
@@ -0,0 +1,51 @@
+import { Column } from "@lume/ark";
+import { ForyouIcon } from "@lume/icons";
+import { useStorage } from "@lume/storage";
+import { IColumn } from "@lume/types";
+import { EventRoute, UserRoute } from "@lume/ui";
+import { NDKEvent, NDKKind } from "@nostr-dev-kit/ndk";
+import { useQueryClient } from "@tanstack/react-query";
+import { useRef } from "react";
+import { HomeRoute } from "./home";
+
+export function ForYou({ column }: { column: IColumn }) {
+ const colKey = `foryou-${column.id}`;
+ const storage = useStorage();
+ const queryClient = useQueryClient();
+ const since = useRef(Math.floor(Date.now() / 1000));
+
+ const refresh = async (events: NDKEvent[]) => {
+ const uniqEvents = new Set(events);
+ await queryClient.setQueryData(
+ [colKey],
+ (prev: { pageParams: number; pages: Array }) => ({
+ ...prev,
+ pages: [[...uniqEvents], ...prev.pages],
+ }),
+ );
+ };
+
+ return (
+
+ }
+ />
+
+
+ } />
+ } />
+ } />
+
+
+ );
+}
diff --git a/packages/lume-column-foryou/tailwind.config.js b/packages/lume-column-foryou/tailwind.config.js
new file mode 100644
index 00000000..49c48c7a
--- /dev/null
+++ b/packages/lume-column-foryou/tailwind.config.js
@@ -0,0 +1,8 @@
+import sharedConfig from "@lume/tailwindcss";
+
+const config = {
+ content: ["./src/**/*.{js,ts,jsx,tsx}"],
+ presets: [sharedConfig],
+};
+
+export default config;
diff --git a/packages/lume-column-foryou/tsconfig.json b/packages/lume-column-foryou/tsconfig.json
new file mode 100644
index 00000000..34a32891
--- /dev/null
+++ b/packages/lume-column-foryou/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "extends": "@lume/tsconfig/base.json",
+ "compilerOptions": {
+ "outDir": "dist"
+ },
+ "include": ["src"],
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/packages/lume-column-timeline/src/home.tsx b/packages/lume-column-timeline/src/home.tsx
index 9e10fcf1..50b0d4bd 100644
--- a/packages/lume-column-timeline/src/home.tsx
+++ b/packages/lume-column-timeline/src/home.tsx
@@ -57,16 +57,12 @@ export function HomeRoute({ colKey }: { colKey: string }) {
};
}
},
+ select: (data) => data?.pages.flatMap((page) => page),
staleTime: 120 * 1000,
refetchOnWindowFocus: false,
refetchOnMount: false,
});
- const allEvents = useMemo(
- () => (data ? data.pages.flatMap((page) => page) : []),
- [data],
- );
-
const renderItem = (event: NDKEvent) => {
switch (event.kind) {
case NDKKind.Text:
@@ -110,7 +106,7 @@ export function HomeRoute({ colKey }: { colKey: string }) {
) : (
- allEvents.map((item) => renderItem(item))
+ data.map((item) => renderItem(item))
)}
{hasNextPage ? (
diff --git a/packages/lume-column-timeline/src/index.tsx b/packages/lume-column-timeline/src/index.tsx
index 2e991362..238e15b2 100644
--- a/packages/lume-column-timeline/src/index.tsx
+++ b/packages/lume-column-timeline/src/index.tsx
@@ -13,7 +13,7 @@ export function Timeline({ column }: { column: IColumn }) {
const queryClient = useQueryClient();
const since = useRef(Math.floor(Date.now() / 1000));
- const refreshTimeline = async (events: NDKEvent[]) => {
+ const refresh = async (events: NDKEvent[]) => {
const uniqEvents = new Set(events);
await queryClient.setQueryData(
[colKey],
@@ -40,7 +40,7 @@ export function Timeline({ column }: { column: IColumn }) {
: ark.account.contacts,
since: since.current,
}}
- onClick={refreshTimeline}
+ onClick={refresh}
/>
} />
diff --git a/packages/storage/src/storage.ts b/packages/storage/src/storage.ts
index 375fa45c..12a7c982 100644
--- a/packages/storage/src/storage.ts
+++ b/packages/storage/src/storage.ts
@@ -1,6 +1,7 @@
import {
Account,
IColumn,
+ Interests,
NDKCacheEvent,
NDKCacheEventTag,
NDKCacheUser,
@@ -19,6 +20,7 @@ export class LumeStorage {
readonly platform: Platform;
readonly locale: string;
public currentUser: Account;
+ public interests: Interests;
public nwc: string;
public settings: {
autoupdate: boolean;
@@ -37,6 +39,7 @@ export class LumeStorage {
this.#db = db;
this.locale = locale;
this.platform = platform;
+ this.interests = null;
this.nwc = null;
this.settings = {
autoupdate: false,
@@ -64,7 +67,18 @@ export class LumeStorage {
}
const account = await this.getActiveAccount();
- if (account) this.currentUser = account;
+
+ if (account) {
+ this.currentUser = account;
+
+ const interests = await this.getInterests();
+ if (interests) {
+ interests.hashtags = interests.hashtags.map((item: string) =>
+ item.replace("#", "").toLowerCase(),
+ );
+ this.interests = interests;
+ }
+ }
}
async #keyring_save(key: string, value: string) {
@@ -412,6 +426,14 @@ export class LumeStorage {
return results[0].value;
}
+ public async getInterests() {
+ const results: { key: string; value: string }[] = await this.#db.select(
+ "SELECT * FROM settings WHERE key = 'interests' ORDER BY id DESC LIMIT 1;",
+ );
+ if (!results.length) return null;
+ return JSON.parse(results[0].value) as Interests;
+ }
+
public async clearCache() {
await this.#db.execute("DELETE FROM ndk_events;");
await this.#db.execute("DELETE FROM ndk_eventtags;");
diff --git a/packages/types/index.d.ts b/packages/types/index.d.ts
index fb74ba79..2ae39b1e 100644
--- a/packages/types/index.d.ts
+++ b/packages/types/index.d.ts
@@ -115,3 +115,9 @@ export interface NIP05 {
};
};
}
+
+export interface Interests {
+ hashtags: string[];
+ users: string[];
+ words: string[];
+}
diff --git a/packages/ui/src/onboarding/home.tsx b/packages/ui/src/onboarding/home.tsx
index 661024a8..fa863d75 100644
--- a/packages/ui/src/onboarding/home.tsx
+++ b/packages/ui/src/onboarding/home.tsx
@@ -1,12 +1,12 @@
import { ArrowRightIcon, PopperFilledIcon } from "@lume/icons";
import { onboardingAtom } from "@lume/utils";
import { motion } from "framer-motion";
-import { useSetAtom } from "jotai";
+import { useAtom } from "jotai";
import { useNavigate } from "react-router-dom";
export function OnboardingHomeScreen() {
const navigate = useNavigate();
- const setOnboarding = useSetAtom(onboardingAtom);
+ const [onboarding, setOnboarding] = useAtom(onboardingAtom);
return (
-
+
diff --git a/packages/utils/src/state.ts b/packages/utils/src/state.ts
index ec8c2a9b..c349789e 100644
--- a/packages/utils/src/state.ts
+++ b/packages/utils/src/state.ts
@@ -11,7 +11,10 @@ export const editorValueAtom = atom([
]);
// Onboarding
-export const onboardingAtom = atom(true);
+export const onboardingAtom = atomWithStorage("onboarding", {
+ open: true,
+ newUser: false,
+});
// Activity
export const activityAtom = atom(false);
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 510084bb..cc5e9f95 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -69,6 +69,9 @@ importers:
'@columns/default':
specifier: workspace:^
version: link:../../packages/lume-column-default
+ '@columns/foryou':
+ specifier: workspace:^
+ version: link:../../packages/lume-column-foryou
'@columns/group':
specifier: workspace:^
version: link:../../packages/lume-column-group
@@ -328,6 +331,9 @@ importers:
sonner:
specifier: ^1.3.1
version: 1.3.1(react-dom@18.2.0)(react@18.2.0)
+ string-strip-html:
+ specifier: ^13.4.5
+ version: 13.4.5
tippy.js:
specifier: ^6.3.7
version: 6.3.7
@@ -529,6 +535,61 @@ importers:
specifier: ^5.3.3
version: 5.3.3
+ packages/lume-column-foryou:
+ dependencies:
+ '@lume/ark':
+ specifier: workspace:^
+ version: link:../ark
+ '@lume/icons':
+ specifier: workspace:^
+ version: link:../icons
+ '@lume/storage':
+ specifier: workspace:^
+ version: link:../storage
+ '@lume/ui':
+ specifier: workspace:^
+ version: link:../ui
+ '@lume/utils':
+ specifier: workspace:^
+ version: link:../utils
+ '@nostr-dev-kit/ndk':
+ specifier: ^2.3.3
+ version: 2.3.3(typescript@5.3.3)
+ '@tanstack/react-query':
+ specifier: ^5.17.15
+ version: 5.17.15(react@18.2.0)
+ react:
+ specifier: ^18.2.0
+ version: 18.2.0
+ react-router-dom:
+ specifier: ^6.21.3
+ version: 6.21.3(react-dom@18.2.0)(react@18.2.0)
+ sonner:
+ specifier: ^1.3.1
+ version: 1.3.1(react-dom@18.2.0)(react@18.2.0)
+ virtua:
+ specifier: ^0.20.5
+ version: 0.20.5(react-dom@18.2.0)(react@18.2.0)
+ devDependencies:
+ '@lume/tailwindcss':
+ specifier: workspace:^
+ version: link:../tailwindcss
+ '@lume/tsconfig':
+ specifier: workspace:^
+ version: link:../tsconfig
+ '@lume/types':
+ specifier: workspace:^
+ version: link:../types
+ '@types/react':
+ specifier: ^18.2.48
+ version: 18.2.48
+ tailwind:
+ specifier: ^4.0.0
+ version: 4.0.0
+ typescript:
+ specifier: ^5.3.3
+ version: 5.3.3
+
packages/lume-column-group:
dependencies:
'@lume/ark':
@@ -3105,6 +3166,12 @@ packages:
resolution: {integrity: sha512-RvC8KMw5BCac1NvRRyaHgMMEtBaZ6wh0pyPTBu7izn4Sj/AX9Y4aXU5c7rX8PnM/knsuUpC1IeoBkANtxBypsQ==}
dev: false
+ /@types/lodash-es@4.17.12:
+ resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==}
+ dependencies:
+ '@types/lodash': 4.14.202
+ dev: false
+
/@types/lodash@4.14.202:
resolution: {integrity: sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==}
dev: false
@@ -3457,6 +3524,13 @@ packages:
engines: {node: '>=6'}
dev: false
+ /codsen-utils@1.6.3:
+ resolution: {integrity: sha512-jsayHP4Z1gKjXB+NsFhEKrM2dAN4XCpbHbhwzzYfFrVL/DYPw9D/ACob6EjbIiV47PSe3OcxJqX/b1V/T7XK3A==}
+ engines: {node: '>=14.18.0'}
+ dependencies:
+ rfdc: 1.3.1
+ dev: false
+
/color-convert@1.9.3:
resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
dependencies:
@@ -4222,6 +4296,10 @@ packages:
function-bind: 1.1.2
dev: true
+ /html-entities@2.4.0:
+ resolution: {integrity: sha512-igBTJcNNNhvZFRtm8uA6xMY6xYleeDwn3PeBCkDz7tHttv4F2hsDI2aPgNERWzvRcNYHNT3ymRaQzllmXj4YsQ==}
+ dev: false
+
/http-errors@1.6.3:
resolution: {integrity: sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==}
engines: {node: '>= 0.6'}
@@ -4574,6 +4652,10 @@ packages:
resolution: {integrity: sha512-auMesunaJ8yfkHvK4gfg1K0SaKX/6Wn9g2Aac/NwX+l5VdmFZzo/hdPGxEOETj+ryRa4/fiOPjeeKURSAJx1sg==}
dev: false
+ /lodash-es@4.17.21:
+ resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
+ dev: false
+
/lodash.castarray@4.4.0:
resolution: {integrity: sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==}
dev: true
@@ -5080,6 +5162,37 @@ packages:
engines: {node: '>= 0.6'}
dev: true
+ /ranges-apply@7.0.14:
+ resolution: {integrity: sha512-ebPhmznZthJJszHMzGdZIVEHxWxM9uiynCGHChtgbuKO155uYCdrUvwsobX6xeefyqtVgHJcXpQDkTJhX0UFoQ==}
+ engines: {node: '>=14.18.0'}
+ dependencies:
+ ranges-merge: 9.0.14
+ tiny-invariant: 1.3.1
+ dev: false
+
+ /ranges-merge@9.0.14:
+ resolution: {integrity: sha512-0iT8T14RPellWrLsfezpIq636TyqCK8+1oG7pxULjuJHwomq6POJF63fZ3CeQ7c/Dpjogs5iSOFc2hFv+XTI1Q==}
+ engines: {node: '>=14.18.0'}
+ dependencies:
+ ranges-push: 7.0.14
+ ranges-sort: 6.0.11
+ dev: false
+
+ /ranges-push@7.0.14:
+ resolution: {integrity: sha512-EKmOrxtaFT4u3OiIfkoCoYxEeRkN2UuH1DbxvA7K/ok4Ie8/QK/DKaWbD9PnoXNnWbqnPtDdyMyvVgVyhnmGhA==}
+ engines: {node: '>=14.18.0'}
+ dependencies:
+ codsen-utils: 1.6.3
+ ranges-sort: 6.0.11
+ string-collapse-leading-whitespace: 7.0.7
+ string-trim-spaces-only: 5.0.10
+ dev: false
+
+ /ranges-sort@6.0.11:
+ resolution: {integrity: sha512-fhNEG0vGi7bESitNNqNBAfYPdl2efB+1paFlI8BQDCNkruERKuuhG8LkQClDIVqUJLkrmKuOSPQ3xZHqVnVo3Q==}
+ engines: {node: '>=14.18.0'}
+ dev: false
+
/raw-body@2.3.3:
resolution: {integrity: sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==}
engines: {node: '>= 0.8'}
@@ -5293,6 +5406,10 @@ packages:
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
dev: true
+ /rfdc@1.3.1:
+ resolution: {integrity: sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==}
+ dev: false
+
/rollup@3.29.4:
resolution: {integrity: sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==}
engines: {node: '>=14.18.0', npm: '>=8.0.0'}
@@ -5544,6 +5661,37 @@ packages:
node-statsd: 0.1.1
dev: true
+ /string-collapse-leading-whitespace@7.0.7:
+ resolution: {integrity: sha512-jF9eynJoE6ezTCdYI8Qb02/ij/DlU9ItG93Dty4SWfJeLFrotOr+wH9IRiWHTqO3mjCyqBWEiU3uSTIbxYbAEQ==}
+ engines: {node: '>=14.18.0'}
+ dev: false
+
+ /string-left-right@6.0.16:
+ resolution: {integrity: sha512-cQL1I49o8qS52LgaS8IU6EXd9S2HNYVRtizdDyp6XjKzSkytr1oTM/7laDqjV7J53bw4iOQNepp/cTs9rCyFVw==}
+ engines: {node: '>=14.18.0'}
+ dependencies:
+ codsen-utils: 1.6.3
+ rfdc: 1.3.1
+ dev: false
+
+ /string-strip-html@13.4.5:
+ resolution: {integrity: sha512-uf6o6zzYXccZQ+wsKN58cedBfMlbFqrUXcDjrBpptExgQEHcFU+uw1jAQdrfyOrAyH4GQKu7JcCm/wzPppnf5Q==}
+ engines: {node: '>=14.18.0'}
+ dependencies:
+ '@types/lodash-es': 4.17.12
+ codsen-utils: 1.6.3
+ html-entities: 2.4.0
+ lodash-es: 4.17.21
+ ranges-apply: 7.0.14
+ ranges-push: 7.0.14
+ string-left-right: 6.0.16
+ dev: false
+
+ /string-trim-spaces-only@5.0.10:
+ resolution: {integrity: sha512-MhmjE5jNqb1Ylo+BARPRlsdChGLrnPpAUWrT1VOxo9WhWwKVUU6CbZTfjwKaQPYTGS/wsX/4Zek88FM2rEb5iA==}
+ engines: {node: '>=14.18.0'}
+ dev: false
+
/string-width@4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'}