From b726ae3c7c5d8f7a0260efb1e74d01fbfa7ddab4 Mon Sep 17 00:00:00 2001 From: reya Date: Sat, 20 Jan 2024 09:06:00 +0700 Subject: [PATCH] feat: add for you column --- apps/desktop/package.json | 1 + apps/desktop/src/routes/auth/create.tsx | 6 + apps/desktop/src/routes/home/index.tsx | 3 + packages/ark/package.json | 1 + .../ark/src/components/column/provider.tsx | 6 + packages/ark/src/components/note/content.tsx | 34 ++-- .../ark/src/components/note/mentions/note.tsx | 90 ++++++----- .../ark/src/components/note/mentions/user.tsx | 7 +- .../ark/src/components/note/preview/link.tsx | 8 +- packages/icons/index.ts | 1 + packages/icons/src/foryou.tsx | 24 +++ packages/lume-column-foryou/package.json | 27 ++++ packages/lume-column-foryou/src/home.tsx | 118 ++++++++++++++ packages/lume-column-foryou/src/index.tsx | 51 ++++++ .../lume-column-foryou/tailwind.config.js | 8 + packages/lume-column-foryou/tsconfig.json | 8 + packages/lume-column-timeline/src/home.tsx | 8 +- packages/lume-column-timeline/src/index.tsx | 4 +- packages/storage/src/storage.ts | 24 ++- packages/types/index.d.ts | 6 + packages/ui/src/onboarding/home.tsx | 10 +- packages/ui/src/onboarding/modal.tsx | 2 +- packages/ui/src/routes/event.tsx | 2 +- packages/utils/src/state.ts | 5 +- pnpm-lock.yaml | 148 ++++++++++++++++++ 25 files changed, 511 insertions(+), 91 deletions(-) create mode 100644 packages/icons/src/foryou.tsx create mode 100644 packages/lume-column-foryou/package.json create mode 100644 packages/lume-column-foryou/src/home.tsx create mode 100644 packages/lume-column-foryou/src/index.tsx create mode 100644 packages/lume-column-foryou/tailwind.config.js create mode 100644 packages/lume-column-foryou/tsconfig.json 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'}