mirror of
https://github.com/luminous-devs/lume.git
synced 2024-09-30 00:41:00 +00:00
chore: clean up
This commit is contained in:
parent
46cc01e0ee
commit
c8e014f33e
@ -29,10 +29,10 @@
|
|||||||
"react-currency-input-field": "^3.8.0",
|
"react-currency-input-field": "^3.8.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-i18next": "^14.1.0",
|
"react-i18next": "^14.1.0",
|
||||||
"slate": "^0.101.5",
|
"slate": "^0.102.0",
|
||||||
"slate-react": "^0.101.6",
|
"slate-react": "^0.102.0",
|
||||||
"sonner": "^1.4.3",
|
"sonner": "^1.4.3",
|
||||||
"virtua": "^0.27.5"
|
"virtua": "^0.29.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@lume/tailwindcss": "workspace:^",
|
"@lume/tailwindcss": "workspace:^",
|
||||||
|
50
apps/desktop2/src/routes/$account.home.tsx
Normal file
50
apps/desktop2/src/routes/$account.home.tsx
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import { LoaderIcon } from "@lume/icons";
|
||||||
|
import { Column } from "@lume/ui";
|
||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/$account/home")({
|
||||||
|
component: Screen,
|
||||||
|
pendingComponent: Pending,
|
||||||
|
loader: async () => {
|
||||||
|
const columns = [
|
||||||
|
{ name: "Tauri v2", content: "https://beta.tauri.app" },
|
||||||
|
{ name: "Tauri v1", content: "https://tauri.app" },
|
||||||
|
{ name: "Lume", content: "https://lume.nu" },
|
||||||
|
{ name: "Snort", content: "https://snort.social" },
|
||||||
|
];
|
||||||
|
return columns;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function Screen() {
|
||||||
|
const data = Route.useLoaderData();
|
||||||
|
const [isScroll, setIsScroll] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative h-full w-full">
|
||||||
|
<div
|
||||||
|
onScroll={() => setIsScroll((state) => !state)}
|
||||||
|
className="flex h-full w-full flex-nowrap gap-3 overflow-x-auto px-3 pb-3 pt-1.5 focus:outline-none"
|
||||||
|
>
|
||||||
|
{data.map((column, index) => (
|
||||||
|
<Column
|
||||||
|
key={column.name + index}
|
||||||
|
column={column}
|
||||||
|
isScroll={isScroll}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Pending() {
|
||||||
|
return (
|
||||||
|
<div className="flex h-full w-full items-center justify-center">
|
||||||
|
<button type="button" disabled>
|
||||||
|
<LoaderIcon className="size-5 animate-spin" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -1,36 +0,0 @@
|
|||||||
import { LumeColumn } from "@lume/types";
|
|
||||||
import { Column } from "@lume/ui";
|
|
||||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
|
||||||
import { useState } from "react";
|
|
||||||
|
|
||||||
const DEFAULT_COLUMNS: LumeColumn[] = [
|
|
||||||
{ name: "Tauri v2", content: "https://beta.tauri.app" },
|
|
||||||
{ name: "Tauri v1", content: "https://tauri.app" },
|
|
||||||
{ name: "Lume", content: "https://lume.nu" },
|
|
||||||
{ name: "Snort", content: "https://snort.social" },
|
|
||||||
];
|
|
||||||
|
|
||||||
export const Route = createLazyFileRoute("/$account/home")({
|
|
||||||
component: Screen,
|
|
||||||
});
|
|
||||||
|
|
||||||
function Screen() {
|
|
||||||
const [isScroll, setIsScroll] = useState(false);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="relative h-full w-full">
|
|
||||||
<div
|
|
||||||
onScroll={() => setIsScroll((state) => !state)}
|
|
||||||
className="flex h-full w-full flex-nowrap gap-3 overflow-x-auto px-3 pb-3 pt-1.5 focus:outline-none"
|
|
||||||
>
|
|
||||||
{DEFAULT_COLUMNS.map((column, index) => (
|
|
||||||
<Column
|
|
||||||
key={column.name + index}
|
|
||||||
column={column}
|
|
||||||
isScroll={isScroll}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -10,7 +10,7 @@
|
|||||||
"astro": "astro"
|
"astro": "astro"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/check": "^0.4.1",
|
"@astrojs/check": "^0.5.9",
|
||||||
"@astrojs/tailwind": "^5.1.0",
|
"@astrojs/tailwind": "^5.1.0",
|
||||||
"@fontsource/geist-mono": "^5.0.2",
|
"@fontsource/geist-mono": "^5.0.2",
|
||||||
"astro": "^4.5.5",
|
"astro": "^4.5.5",
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
"@tanstack/react-query": "^5.28.4",
|
"@tanstack/react-query": "^5.28.4",
|
||||||
"@tanstack/react-router": "^1.20.0",
|
"@tanstack/react-router": "^1.20.0",
|
||||||
"get-urls": "^12.1.0",
|
"get-urls": "^12.1.0",
|
||||||
"media-chrome": "^2.2.5",
|
"media-chrome": "^3.0.2",
|
||||||
"minidenticons": "^4.2.1",
|
"minidenticons": "^4.2.1",
|
||||||
"nanoid": "^5.0.6",
|
"nanoid": "^5.0.6",
|
||||||
"qrcode.react": "^3.1.0",
|
"qrcode.react": "^3.1.0",
|
||||||
@ -28,7 +28,7 @@
|
|||||||
"react-string-replace": "^1.1.1",
|
"react-string-replace": "^1.1.1",
|
||||||
"sonner": "^1.4.3",
|
"sonner": "^1.4.3",
|
||||||
"string-strip-html": "^13.4.6",
|
"string-strip-html": "^13.4.6",
|
||||||
"virtua": "^0.27.5"
|
"virtua": "^0.29.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@lume/tailwindcss": "workspace:^",
|
"@lume/tailwindcss": "workspace:^",
|
||||||
|
48
packages/ark/src/hooks/useEvents.ts
Normal file
48
packages/ark/src/hooks/useEvents.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import { useInfiniteQuery } from "@tanstack/react-query";
|
||||||
|
import { useArk } from "./useArk";
|
||||||
|
|
||||||
|
const FETCH_LIMIT = 20;
|
||||||
|
const QUERY_KEY = "local";
|
||||||
|
const DEDUP = true;
|
||||||
|
|
||||||
|
export function useEvents(key: string, account?: string) {
|
||||||
|
const ark = useArk();
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
isError,
|
||||||
|
isLoading,
|
||||||
|
isRefetching,
|
||||||
|
isFetchingNextPage,
|
||||||
|
hasNextPage,
|
||||||
|
fetchNextPage,
|
||||||
|
} = useInfiniteQuery({
|
||||||
|
queryKey: [key, account],
|
||||||
|
initialPageParam: 0,
|
||||||
|
queryFn: async ({ pageParam }: { pageParam: number }) => {
|
||||||
|
const events = await ark.get_events(
|
||||||
|
QUERY_KEY,
|
||||||
|
FETCH_LIMIT,
|
||||||
|
pageParam,
|
||||||
|
DEDUP,
|
||||||
|
);
|
||||||
|
return events;
|
||||||
|
},
|
||||||
|
getNextPageParam: (lastPage) => {
|
||||||
|
const lastEvent = lastPage?.at(-1);
|
||||||
|
if (!lastEvent) return;
|
||||||
|
return lastEvent.created_at - 1;
|
||||||
|
},
|
||||||
|
select: (data) => data?.pages.flatMap((page) => page),
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
data,
|
||||||
|
isError,
|
||||||
|
isLoading,
|
||||||
|
isRefetching,
|
||||||
|
isFetchingNextPage,
|
||||||
|
hasNextPage,
|
||||||
|
fetchNextPage,
|
||||||
|
};
|
||||||
|
}
|
@ -1,24 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@columns/antenas",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"private": true,
|
|
||||||
"main": "./src/index.tsx",
|
|
||||||
"dependencies": {
|
|
||||||
"@lume/ark": "workspace:^",
|
|
||||||
"@lume/icons": "workspace:^",
|
|
||||||
"@lume/ui": "workspace:^",
|
|
||||||
"@lume/utils": "workspace:^",
|
|
||||||
"@tanstack/react-query": "^5.28.4",
|
|
||||||
"react": "^18.2.0",
|
|
||||||
"sonner": "^1.4.3",
|
|
||||||
"virtua": "^0.27.5"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@lume/tailwindcss": "workspace:^",
|
|
||||||
"@lume/tsconfig": "workspace:^",
|
|
||||||
"@lume/types": "workspace:^",
|
|
||||||
"@types/react": "^18.2.66",
|
|
||||||
"tailwindcss": "^3.4.1",
|
|
||||||
"typescript": "^5.4.2"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,131 +0,0 @@
|
|||||||
import { useColumnContext } from "@lume/ark";
|
|
||||||
import { CancelIcon, PlusIcon } from "@lume/icons";
|
|
||||||
import { useState } from "react";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
|
|
||||||
export function AntenasForm({ id }: { id: number }) {
|
|
||||||
const { updateColumn, removeColumn } = useColumnContext();
|
|
||||||
|
|
||||||
const [title, setTitle] = useState<string>(`Antena-${id}`);
|
|
||||||
const [source, setSource] = useState("contacts");
|
|
||||||
const [hashtag, setHashtag] = useState("");
|
|
||||||
const [hashtags, setHashtags] = useState<string[]>([]);
|
|
||||||
|
|
||||||
const addHashtag = () => {
|
|
||||||
if (!hashtag.startsWith("#"))
|
|
||||||
return toast.error("Hashtag need to start with #");
|
|
||||||
if (hashtag.length > 64) return toast.error("Hashtag too long");
|
|
||||||
setHashtags((prev) => [...prev, hashtag]);
|
|
||||||
setHashtag("");
|
|
||||||
};
|
|
||||||
|
|
||||||
const removeHashtag = (item: string) => {
|
|
||||||
setHashtags((prev) => prev.filter((tag) => tag !== item));
|
|
||||||
};
|
|
||||||
|
|
||||||
const submit = async () => {
|
|
||||||
const content = {
|
|
||||||
hashtags,
|
|
||||||
source,
|
|
||||||
};
|
|
||||||
await updateColumn(id, title, JSON.stringify(content));
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col justify-between h-full">
|
|
||||||
<div className="flex flex-col flex-1 min-h-0">
|
|
||||||
<div className="flex items-center justify-between w-full px-3 border-b h-11 shrink-0 border-neutral-100 dark:border-neutral-900">
|
|
||||||
<h1 className="text-sm font-semibold">Create a new Antena</h1>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={async () => await removeColumn(id)}
|
|
||||||
className="inline-flex items-center justify-center rounded size-6 hover:bg-neutral-100 dark:hover:bg-neutral-900"
|
|
||||||
>
|
|
||||||
<CancelIcon className="size-4" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col h-full gap-5 px-3 pt-2 overflow-y-auto">
|
|
||||||
<div className="flex flex-col gap-1">
|
|
||||||
<label htmlFor="name" className="font-medium">
|
|
||||||
Name
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
name="name"
|
|
||||||
value={title}
|
|
||||||
onChange={(e) => setTitle(e.target.value)}
|
|
||||||
placeholder="Nostrichs..."
|
|
||||||
className="px-2 border border-neutral-100 dark:border-neutral-900 bg-neutral-50 rounded-lg h-10 dark:bg-neutral-950 placeholder:text-neutral-600 focus:border-blue-500 focus:shadow-none focus:ring-0"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col gap-1">
|
|
||||||
<label
|
|
||||||
htmlFor="source"
|
|
||||||
className="select-none text-neutral-950 data-[disabled]:opacity-50 font-medium mb-1 dark:text-white"
|
|
||||||
>
|
|
||||||
Source
|
|
||||||
</label>
|
|
||||||
<select
|
|
||||||
name="source"
|
|
||||||
value={source}
|
|
||||||
onChange={(e) => setSource(e.target.value)}
|
|
||||||
className="px-2 w-full border border-neutral-100 dark:border-neutral-900 bg-neutral-50 rounded-lg dark:bg-neutral-950 placeholder:text-neutral-600 focus:border-blue-500 focus:shadow-none focus:ring-0"
|
|
||||||
>
|
|
||||||
<option value="contacts">Contacts</option>
|
|
||||||
<option value="global">Global</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col gap-1">
|
|
||||||
<label
|
|
||||||
htmlFor="name"
|
|
||||||
className="select-none text-neutral-950 data-[disabled]:opacity-50 font-medium mb-1 dark:text-white"
|
|
||||||
>
|
|
||||||
Hashtags to listen to
|
|
||||||
</label>
|
|
||||||
<div className="flex items-center justify-between gap-2 mb-1">
|
|
||||||
<input
|
|
||||||
name="name"
|
|
||||||
value={hashtag}
|
|
||||||
onChange={(e) => setHashtag(e.target.value)}
|
|
||||||
onKeyPress={(event) => {
|
|
||||||
if (event.key === "Enter") addHashtag();
|
|
||||||
}}
|
|
||||||
placeholder="#nostr..."
|
|
||||||
className="px-2 w-full border border-neutral-100 dark:border-neutral-900 bg-neutral-50 rounded-lg h-10 dark:bg-neutral-950 placeholder:text-neutral-600 focus:border-blue-500 focus:shadow-none focus:ring-0"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => addHashtag()}
|
|
||||||
className="inline-flex items-center justify-center h-full text-white bg-blue-500 rounded-lg aspect-square shrink-0 hover:bg-blue-600"
|
|
||||||
>
|
|
||||||
<PlusIcon className="size-4" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-wrap items-center justify-start gap-2">
|
|
||||||
{hashtags.map((item) => (
|
|
||||||
<button
|
|
||||||
key={item}
|
|
||||||
type="button"
|
|
||||||
onClick={() => removeHashtag(item)}
|
|
||||||
className="inline-flex items-center justify-center h-6 px-2 text-sm rounded-md w-min bg-neutral-100 hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800"
|
|
||||||
>
|
|
||||||
{item}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={submit}
|
|
||||||
disabled={hashtags.length < 1}
|
|
||||||
className="w-full inline-flex items-center justify-center h-10 px-4 font-semibold text-white transform bg-blue-500 rounded-lg active:translate-y-1 hover:bg-blue-600 focus:outline-none disabled:opacity-50"
|
|
||||||
>
|
|
||||||
Create
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,132 +0,0 @@
|
|||||||
import { RepostNote, TextNote, useArk } from "@lume/ark";
|
|
||||||
import { ArrowRightCircleIcon, LoaderIcon } from "@lume/icons";
|
|
||||||
import { FETCH_LIMIT } from "@lume/utils";
|
|
||||||
import { NDKEvent, NDKFilter, NDKKind } from "@nostr-dev-kit/ndk";
|
|
||||||
import { useInfiniteQuery } from "@tanstack/react-query";
|
|
||||||
import { useEffect, useMemo, useRef } from "react";
|
|
||||||
import { CacheSnapshot, VList, VListHandle } from "virtua";
|
|
||||||
|
|
||||||
export function HomeRoute({
|
|
||||||
colKey,
|
|
||||||
content,
|
|
||||||
}: { colKey: string; content: string }) {
|
|
||||||
const ark = useArk();
|
|
||||||
const ref = useRef<VListHandle>();
|
|
||||||
const cacheKey = `${colKey}-vlist`;
|
|
||||||
|
|
||||||
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;
|
|
||||||
}) => {
|
|
||||||
let filter: NDKFilter;
|
|
||||||
const parsed: { hashtags: string[]; source: string } =
|
|
||||||
JSON.parse(content);
|
|
||||||
|
|
||||||
if (parsed.source === "contacts") {
|
|
||||||
filter = {
|
|
||||||
kinds: [NDKKind.Text, NDKKind.Repost],
|
|
||||||
"#t": parsed.hashtags.map((item) => item.replace("#", "")),
|
|
||||||
authors: ark.account.contacts,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
filter = {
|
|
||||||
kinds: [NDKKind.Text, NDKKind.Repost],
|
|
||||||
"#t": parsed.hashtags.map((item) => item.replace("#", "")),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const events = await ark.getInfiniteEvents({
|
|
||||||
filter,
|
|
||||||
limit: FETCH_LIMIT,
|
|
||||||
pageParam,
|
|
||||||
signal,
|
|
||||||
});
|
|
||||||
|
|
||||||
return events;
|
|
||||||
},
|
|
||||||
getNextPageParam: (lastPage) => {
|
|
||||||
const lastEvent = lastPage.at(-1);
|
|
||||||
if (!lastEvent) return;
|
|
||||||
return lastEvent.created_at - 1;
|
|
||||||
},
|
|
||||||
refetchOnWindowFocus: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const allEvents = useMemo(
|
|
||||||
() => (data ? data.pages.flatMap((page) => page) : []),
|
|
||||||
[data],
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderItem = (event: NDKEvent) => {
|
|
||||||
switch (event.kind) {
|
|
||||||
case NDKKind.Text:
|
|
||||||
return <TextNote key={event.id} event={event} className="mt-3" />;
|
|
||||||
case NDKKind.Repost:
|
|
||||||
return <RepostNote key={event.id} event={event} className="mt-3" />;
|
|
||||||
default:
|
|
||||||
return <TextNote key={event.id} event={event} className="mt-3" />;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
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 (
|
|
||||||
<div className="w-full h-full">
|
|
||||||
<VList ref={ref} cache={cache} overscan={2} className="flex-1 px-3">
|
|
||||||
{isLoading ? (
|
|
||||||
<div className="w-full flex h-16 items-center justify-center gap-2 px-3 py-1.5">
|
|
||||||
<LoaderIcon className="size-5 animate-spin" />
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
allEvents.map((item) => renderItem(item))
|
|
||||||
)}
|
|
||||||
<div className="flex items-center justify-center h-16">
|
|
||||||
{hasNextPage ? (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => fetchNextPage()}
|
|
||||||
disabled={!hasNextPage || isFetchingNextPage}
|
|
||||||
className="inline-flex items-center justify-center w-full h-12 gap-2 font-medium bg-neutral-100 hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800 rounded-xl focus:outline-none"
|
|
||||||
>
|
|
||||||
{isFetchingNextPage ? (
|
|
||||||
<LoaderIcon className="size-5 animate-spin" />
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<ArrowRightCircleIcon className="size-5" />
|
|
||||||
Load more
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
</VList>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
import { Column } from "@lume/ark";
|
|
||||||
import { LumeColumn } from "@lume/types";
|
|
||||||
import { EventRoute, UserRoute } from "@lume/ui";
|
|
||||||
import { AntenasForm } from "./components/form";
|
|
||||||
import { HomeRoute } from "./home";
|
|
||||||
|
|
||||||
export function Antenas({ column }: { column: LumeColumn }) {
|
|
||||||
const colKey = `antenas-${column.id}`;
|
|
||||||
const created = !!column.content?.length;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Column.Root>
|
|
||||||
{created ? (
|
|
||||||
<>
|
|
||||||
<Column.Header id={column.id} title={column.title} />
|
|
||||||
<Column.Content>
|
|
||||||
<Column.Route
|
|
||||||
path="/"
|
|
||||||
element={<HomeRoute colKey={colKey} content={column.content} />}
|
|
||||||
/>
|
|
||||||
<Column.Route path="/events/:id" element={<EventRoute />} />
|
|
||||||
<Column.Route path="/users/:id" element={<UserRoute />} />
|
|
||||||
</Column.Content>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<AntenasForm id={column.id} />
|
|
||||||
)}
|
|
||||||
</Column.Root>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
import sharedConfig from "@lume/tailwindcss";
|
|
||||||
|
|
||||||
const config = {
|
|
||||||
content: ["./src/**/*.{js,ts,jsx,tsx}"],
|
|
||||||
presets: [sharedConfig],
|
|
||||||
};
|
|
||||||
|
|
||||||
export default config;
|
|
@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "@lume/tsconfig/base.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"outDir": "dist"
|
|
||||||
},
|
|
||||||
"include": ["src"],
|
|
||||||
"exclude": ["node_modules", "dist"]
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@columns/default",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"private": true,
|
|
||||||
"main": "./src/index.tsx",
|
|
||||||
"dependencies": {
|
|
||||||
"@lume/ark": "workspace:^",
|
|
||||||
"@lume/icons": "workspace:^",
|
|
||||||
"@lume/ui": "workspace:^",
|
|
||||||
"@lume/utils": "workspace:^",
|
|
||||||
"@tanstack/react-query": "^5.28.4",
|
|
||||||
"react": "^18.2.0",
|
|
||||||
"sonner": "^1.4.3",
|
|
||||||
"virtua": "^0.27.5"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@lume/tailwindcss": "workspace:^",
|
|
||||||
"@lume/tsconfig": "workspace:^",
|
|
||||||
"@lume/types": "workspace:^",
|
|
||||||
"@types/react": "^18.2.66",
|
|
||||||
"tailwindcss": "^3.4.1",
|
|
||||||
"typescript": "^5.4.2"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,188 +0,0 @@
|
|||||||
import { Column, useColumnContext } from "@lume/ark";
|
|
||||||
import { LumeColumn } from "@lume/types";
|
|
||||||
import { COL_TYPES } from "@lume/utils";
|
|
||||||
|
|
||||||
export function Default({ column }: { column: LumeColumn }) {
|
|
||||||
const { addColumn } = useColumnContext();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Column.Root>
|
|
||||||
<Column.Header id={column.id} title="Add columns" />
|
|
||||||
<div className="h-full flex-1 px-3 mt-3 flex flex-col gap-3 overflow-y-auto scrollbar-none">
|
|
||||||
<div className="shrink-0 h-11 flex items-center gap-5">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="h-9 w-max px-3 text-sm font-semibold inline-flex items-center justify-center bg-neutral-100 dark:bg-neutral-900 rounded-lg"
|
|
||||||
>
|
|
||||||
Official
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
disabled
|
|
||||||
className="h-9 w-max px-3 text-sm inline-flex items-center justify-center rounded-lg disabled:opacity-50"
|
|
||||||
>
|
|
||||||
Community (Coming Soon)
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div className="shrink-0 flex flex-col rounded-xl overflow-hidden bg-neutral-50 dark:bg-neutral-950 ring-1 ring-neutral-100 dark:ring-neutral-900">
|
|
||||||
<div className="h-[100px] w-full px-3 pt-3">
|
|
||||||
<img
|
|
||||||
src="/columns/group.jpg"
|
|
||||||
srcSet="/columns/group@2x.jpg 2x"
|
|
||||||
alt="group"
|
|
||||||
loading="lazy"
|
|
||||||
decoding="async"
|
|
||||||
className="w-full h-auto object-cover rounded-lg"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="h-16 shrink-0 px-3 flex items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<h1 className="font-semibold">Group Feeds</h1>
|
|
||||||
<p className="max-w-[18rem] truncate text-sm text-neutral-600 dark:text-neutral-500">
|
|
||||||
Collective of people you're interested in.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => {
|
|
||||||
addColumn({ kind: COL_TYPES.group, title: "", content: "" });
|
|
||||||
}}
|
|
||||||
className="shrink-0 w-16 h-8 rounded-lg text-sm font-semibold bg-neutral-100 dark:bg-neutral-900 text-blue-500 hover:bg-neutral-200 dark:hover:bg-neutral-800 inline-flex items-center justify-center"
|
|
||||||
>
|
|
||||||
Add
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="shrink-0 flex flex-col rounded-xl overflow-hidden bg-neutral-50 dark:bg-neutral-950 ring-1 ring-neutral-100 dark:ring-neutral-900">
|
|
||||||
<div className="h-[100px] w-full px-3 pt-3">
|
|
||||||
<img
|
|
||||||
src="/columns/antenas.jpg"
|
|
||||||
srcSet="/columns/antenas@2x.jpg 2x"
|
|
||||||
alt="antenas"
|
|
||||||
loading="lazy"
|
|
||||||
decoding="async"
|
|
||||||
className="w-full h-auto object-cover rounded-lg"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="h-16 shrink-0 px-3 flex items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<h1 className="font-semibold">Antenas</h1>
|
|
||||||
<p className="max-w-[18rem] truncate text-sm text-neutral-600 dark:text-neutral-500">
|
|
||||||
Keep track to specific content.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => {
|
|
||||||
addColumn({ kind: COL_TYPES.antenas, title: "", content: "" });
|
|
||||||
}}
|
|
||||||
className="shrink-0 w-16 h-8 rounded-lg text-sm font-semibold bg-neutral-100 dark:bg-neutral-900 text-blue-500 hover:bg-neutral-200 dark:hover:bg-neutral-800 inline-flex items-center justify-center"
|
|
||||||
>
|
|
||||||
Add
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="shrink-0 flex flex-col rounded-xl overflow-hidden bg-neutral-50 dark:bg-neutral-950 ring-1 ring-neutral-100 dark:ring-neutral-900">
|
|
||||||
<div className="h-[100px] w-full px-3 pt-3">
|
|
||||||
<img
|
|
||||||
src="/columns/trending-notes.jpg"
|
|
||||||
srcSet="/columns/trending-notes@2x.jpg 2x"
|
|
||||||
alt="trendingNotes"
|
|
||||||
loading="lazy"
|
|
||||||
decoding="async"
|
|
||||||
className="w-full h-auto object-cover rounded-lg"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="h-16 shrink-0 px-3 flex items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<h1 className="font-semibold">Trending Notes</h1>
|
|
||||||
<p className="max-w-[18rem] truncate text-sm text-neutral-600 dark:text-neutral-500">
|
|
||||||
What is trending on Nostr?.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => {
|
|
||||||
addColumn({
|
|
||||||
kind: COL_TYPES.trendingNotes,
|
|
||||||
title: "",
|
|
||||||
content: "",
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
className="shrink-0 w-16 h-8 rounded-lg text-sm font-semibold bg-neutral-100 dark:bg-neutral-900 text-blue-500 hover:bg-neutral-200 dark:hover:bg-neutral-800 inline-flex items-center justify-center"
|
|
||||||
>
|
|
||||||
Add
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="shrink-0 flex flex-col rounded-xl overflow-hidden bg-neutral-50 dark:bg-neutral-950 ring-1 ring-neutral-100 dark:ring-neutral-900">
|
|
||||||
<div className="h-[100px] w-full px-3 pt-3">
|
|
||||||
<img
|
|
||||||
src="/columns/global.jpg"
|
|
||||||
srcSet="/columns/global@2x.jpg 2x"
|
|
||||||
alt="global"
|
|
||||||
loading="lazy"
|
|
||||||
decoding="async"
|
|
||||||
className="w-full h-auto object-cover rounded-lg"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="h-16 shrink-0 px-3 flex items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<h1 className="font-semibold">Global</h1>
|
|
||||||
<p className="max-w-[18rem] truncate text-sm text-neutral-600 dark:text-neutral-500">
|
|
||||||
All things around the world.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => {
|
|
||||||
addColumn({
|
|
||||||
kind: COL_TYPES.global,
|
|
||||||
title: "",
|
|
||||||
content: "",
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
className="shrink-0 w-16 h-8 rounded-lg text-sm font-semibold bg-neutral-100 dark:bg-neutral-900 text-blue-500 hover:bg-neutral-200 dark:hover:bg-neutral-800 inline-flex items-center justify-center"
|
|
||||||
>
|
|
||||||
Add
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="shrink-0 flex flex-col rounded-xl overflow-hidden bg-neutral-50 dark:bg-neutral-950 ring-1 ring-neutral-100 dark:ring-neutral-900">
|
|
||||||
<div className="h-[100px] w-full px-3 pt-3">
|
|
||||||
<img
|
|
||||||
src="/columns/waifu.jpg"
|
|
||||||
srcSet="/columns/waifu@2x.jpg 2x"
|
|
||||||
alt="waifu"
|
|
||||||
loading="lazy"
|
|
||||||
decoding="async"
|
|
||||||
className="w-full h-auto object-cover rounded-lg"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="h-16 shrink-0 px-3 flex items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<h1 className="font-semibold">Waifu</h1>
|
|
||||||
<p className="max-w-[18rem] truncate text-sm text-neutral-600 dark:text-neutral-500">
|
|
||||||
Show a random waifu image to boost your morale.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => {
|
|
||||||
addColumn({
|
|
||||||
kind: COL_TYPES.waifu,
|
|
||||||
title: "Waifu",
|
|
||||||
content: "",
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
className="shrink-0 w-16 h-8 rounded-lg text-sm font-semibold bg-neutral-100 dark:bg-neutral-900 text-blue-500 hover:bg-neutral-200 dark:hover:bg-neutral-800 inline-flex items-center justify-center"
|
|
||||||
>
|
|
||||||
Add
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="h-3" />
|
|
||||||
</div>
|
|
||||||
</Column.Root>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
import sharedConfig from "@lume/tailwindcss";
|
|
||||||
|
|
||||||
const config = {
|
|
||||||
content: ["./src/**/*.{js,ts,jsx,tsx}"],
|
|
||||||
presets: [sharedConfig],
|
|
||||||
};
|
|
||||||
|
|
||||||
export default config;
|
|
@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "@lume/tsconfig/base.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"outDir": "dist"
|
|
||||||
},
|
|
||||||
"include": ["src"],
|
|
||||||
"exclude": ["node_modules", "dist"]
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@columns/foryou",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"private": true,
|
|
||||||
"main": "./src/index.tsx",
|
|
||||||
"dependencies": {
|
|
||||||
"@lume/ark": "workspace:^",
|
|
||||||
"@lume/icons": "workspace:^",
|
|
||||||
"@lume/ui": "workspace:^",
|
|
||||||
"@lume/utils": "workspace:^",
|
|
||||||
"@tanstack/react-query": "^5.28.4",
|
|
||||||
"react": "^18.2.0",
|
|
||||||
"sonner": "^1.4.3",
|
|
||||||
"virtua": "^0.27.5"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@lume/tailwindcss": "workspace:^",
|
|
||||||
"@lume/tsconfig": "workspace:^",
|
|
||||||
"@lume/types": "workspace:^",
|
|
||||||
"@types/react": "^18.2.66",
|
|
||||||
"tailwindcss": "^3.4.1",
|
|
||||||
"typescript": "^5.4.2"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,139 +0,0 @@
|
|||||||
import { TextNote, useArk } from "@lume/ark";
|
|
||||||
import { InterestModal } from "@lume/ark/src/components/column/interestModal";
|
|
||||||
import { ArrowRightCircleIcon, ForyouIcon, LoaderIcon } from "@lume/icons";
|
|
||||||
import { useStorage } from "@lume/storage";
|
|
||||||
import { EmptyFeed } from "@lume/ui";
|
|
||||||
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<VListHandle>();
|
|
||||||
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;
|
|
||||||
}) => {
|
|
||||||
if (!storage.interests?.hashtags) return [];
|
|
||||||
|
|
||||||
const events = await ark.getInfiniteEvents({
|
|
||||||
filter: {
|
|
||||||
kinds: [NDKKind.Text],
|
|
||||||
"#t": storage.interests.hashtags.map((item: string) =>
|
|
||||||
item.replace("#", "").toLowerCase(),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
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]),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
if (!storage.interests?.hashtags?.length) {
|
|
||||||
return (
|
|
||||||
<div className="px-3 mt-3">
|
|
||||||
<EmptyFeed subtext="You can more interests to build up your timeline" />
|
|
||||||
<InterestModal
|
|
||||||
queryKey={[colKey]}
|
|
||||||
className="mt-3 w-full text-sm font-medium inline-flex items-center justify-center rounded-lg h-9 bg-blue-500 hover:bg-blue-600 text-white"
|
|
||||||
>
|
|
||||||
<ForyouIcon className="size-5" />
|
|
||||||
Add interest
|
|
||||||
</InterestModal>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="w-full h-full">
|
|
||||||
<VList ref={ref} cache={cache} overscan={2} className="flex-1 px-3">
|
|
||||||
{isLoading ? (
|
|
||||||
<div className="w-full flex h-16 items-center justify-center gap-2 px-3 py-1.5">
|
|
||||||
<LoaderIcon className="size-5 animate-spin" />
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
data.map((event) => (
|
|
||||||
<TextNote key={event.id} event={event} className="mt-3" />
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
<div className="flex items-center justify-center h-16">
|
|
||||||
{hasNextPage ? (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => fetchNextPage()}
|
|
||||||
disabled={!hasNextPage || isFetchingNextPage}
|
|
||||||
className="inline-flex items-center justify-center w-full h-12 gap-2 font-medium bg-neutral-100 hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800 rounded-xl focus:outline-none"
|
|
||||||
>
|
|
||||||
{isFetchingNextPage ? (
|
|
||||||
<LoaderIcon className="size-5 animate-spin" />
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<ArrowRightCircleIcon className="size-5" />
|
|
||||||
Load more
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
</VList>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,49 +0,0 @@
|
|||||||
import { Column } from "@lume/ark";
|
|
||||||
import { useStorage } from "@lume/storage";
|
|
||||||
import { LumeColumn } 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: LumeColumn }) {
|
|
||||||
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<NDKEvent[]> }) => ({
|
|
||||||
...prev,
|
|
||||||
pages: [[...uniqEvents], ...prev.pages],
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Column.Root>
|
|
||||||
<Column.Header id={column.id} queryKey={[colKey]} title="For You" />
|
|
||||||
{storage.interests?.hashtags ? (
|
|
||||||
<Column.Live
|
|
||||||
filter={{
|
|
||||||
kinds: [NDKKind.Text],
|
|
||||||
"#t": storage.interests.hashtags.map((item: string) =>
|
|
||||||
item.replace("#", "").toLowerCase(),
|
|
||||||
),
|
|
||||||
since: since.current,
|
|
||||||
}}
|
|
||||||
onClick={refresh}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
<Column.Content>
|
|
||||||
<Column.Route path="/" element={<HomeRoute colKey={colKey} />} />
|
|
||||||
<Column.Route path="/events/:id" element={<EventRoute />} />
|
|
||||||
<Column.Route path="/users/:id" element={<UserRoute />} />
|
|
||||||
</Column.Content>
|
|
||||||
</Column.Root>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
import sharedConfig from "@lume/tailwindcss";
|
|
||||||
|
|
||||||
const config = {
|
|
||||||
content: ["./src/**/*.{js,ts,jsx,tsx}"],
|
|
||||||
presets: [sharedConfig],
|
|
||||||
};
|
|
||||||
|
|
||||||
export default config;
|
|
@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "@lume/tsconfig/base.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"outDir": "dist"
|
|
||||||
},
|
|
||||||
"include": ["src"],
|
|
||||||
"exclude": ["node_modules", "dist"]
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@columns/global",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"private": true,
|
|
||||||
"main": "./src/index.tsx",
|
|
||||||
"dependencies": {
|
|
||||||
"@lume/ark": "workspace:^",
|
|
||||||
"@lume/icons": "workspace:^",
|
|
||||||
"@lume/ui": "workspace:^",
|
|
||||||
"@lume/utils": "workspace:^",
|
|
||||||
"@tanstack/react-query": "^5.28.4",
|
|
||||||
"react": "^18.2.0",
|
|
||||||
"sonner": "^1.4.3",
|
|
||||||
"virtua": "^0.27.5"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@lume/tailwindcss": "workspace:^",
|
|
||||||
"@lume/tsconfig": "workspace:^",
|
|
||||||
"@lume/types": "workspace:^",
|
|
||||||
"@types/react": "^18.2.66",
|
|
||||||
"tailwindcss": "^3.4.1",
|
|
||||||
"typescript": "^5.4.2"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,126 +0,0 @@
|
|||||||
import { RepostNote, TextNote, useArk } from "@lume/ark";
|
|
||||||
import { ArrowRightCircleIcon, LoaderIcon, SearchIcon } from "@lume/icons";
|
|
||||||
import { EmptyFeed } from "@lume/ui";
|
|
||||||
import { FETCH_LIMIT } from "@lume/utils";
|
|
||||||
import { type NDKEvent, NDKKind } from "@nostr-dev-kit/ndk";
|
|
||||||
import { useInfiniteQuery } from "@tanstack/react-query";
|
|
||||||
import { useEffect, useMemo, useRef } from "react";
|
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
import { CacheSnapshot, VList, VListHandle } from "virtua";
|
|
||||||
|
|
||||||
export function HomeRoute({ colKey }: { colKey: string }) {
|
|
||||||
const ark = useArk();
|
|
||||||
const ref = useRef<VListHandle>();
|
|
||||||
const cacheKey = `${colKey}-vlist`;
|
|
||||||
|
|
||||||
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;
|
|
||||||
}) => {
|
|
||||||
if (!ark.account.contacts.length) return [];
|
|
||||||
|
|
||||||
const events = await ark.getInfiniteEvents({
|
|
||||||
filter: {
|
|
||||||
kinds: [NDKKind.Text, NDKKind.Repost],
|
|
||||||
},
|
|
||||||
limit: FETCH_LIMIT,
|
|
||||||
pageParam,
|
|
||||||
signal,
|
|
||||||
});
|
|
||||||
|
|
||||||
return events;
|
|
||||||
},
|
|
||||||
getNextPageParam: (lastPage) => {
|
|
||||||
const lastEvent = lastPage.at(-1);
|
|
||||||
if (!lastEvent) return;
|
|
||||||
return lastEvent.created_at - 1;
|
|
||||||
},
|
|
||||||
select: (data) => data?.pages.flatMap((page) => page),
|
|
||||||
refetchOnWindowFocus: false,
|
|
||||||
refetchOnMount: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const renderItem = (event: NDKEvent) => {
|
|
||||||
switch (event.kind) {
|
|
||||||
case NDKKind.Text:
|
|
||||||
return <TextNote key={event.id} event={event} className="mt-3" />;
|
|
||||||
case NDKKind.Repost:
|
|
||||||
return <RepostNote key={event.id} event={event} className="mt-3" />;
|
|
||||||
default:
|
|
||||||
return <TextNote key={event.id} event={event} className="mt-3" />;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
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 (
|
|
||||||
<div className="w-full h-full">
|
|
||||||
<VList ref={ref} cache={cache} overscan={2} className="flex-1 px-3">
|
|
||||||
{isLoading ? (
|
|
||||||
<div className="w-full flex h-16 items-center justify-center gap-2 px-3 py-1.5">
|
|
||||||
<LoaderIcon className="size-5 animate-spin" />
|
|
||||||
</div>
|
|
||||||
) : !data.length ? (
|
|
||||||
<div className="px-3 mt-3">
|
|
||||||
<EmptyFeed />
|
|
||||||
<Link
|
|
||||||
to="/suggest"
|
|
||||||
className="mt-3 w-full gap-2 inline-flex items-center justify-center text-sm font-medium rounded-lg h-9 bg-blue-500 hover:bg-blue-600 text-white"
|
|
||||||
>
|
|
||||||
<SearchIcon className="size-5" />
|
|
||||||
Find accounts to follow
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
data.map((item) => renderItem(item))
|
|
||||||
)}
|
|
||||||
<div className="flex items-center justify-center h-16">
|
|
||||||
{hasNextPage ? (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => fetchNextPage()}
|
|
||||||
disabled={!hasNextPage || isFetchingNextPage}
|
|
||||||
className="inline-flex items-center justify-center w-full h-12 gap-2 font-medium bg-neutral-100 hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800 rounded-xl focus:outline-none"
|
|
||||||
>
|
|
||||||
{isFetchingNextPage ? (
|
|
||||||
<LoaderIcon className="size-5 animate-spin" />
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<ArrowRightCircleIcon className="size-5" />
|
|
||||||
Load more
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
</VList>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
import { Column } from "@lume/ark";
|
|
||||||
import { LumeColumn } from "@lume/types";
|
|
||||||
import { EventRoute, UserRoute } from "@lume/ui";
|
|
||||||
import { HomeRoute } from "./home";
|
|
||||||
|
|
||||||
export function Global({ column }: { column: LumeColumn }) {
|
|
||||||
const colKey = `global-${column.id}`;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Column.Root>
|
|
||||||
<Column.Header id={column.id} queryKey={[colKey]} title="Global" />
|
|
||||||
<Column.Content>
|
|
||||||
<Column.Route path="/" element={<HomeRoute colKey={colKey} />} />
|
|
||||||
<Column.Route path="/events/:id" element={<EventRoute />} />
|
|
||||||
<Column.Route path="/users/:id" element={<UserRoute />} />
|
|
||||||
</Column.Content>
|
|
||||||
</Column.Root>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
import sharedConfig from "@lume/tailwindcss";
|
|
||||||
|
|
||||||
const config = {
|
|
||||||
content: ["./src/**/*.{js,ts,jsx,tsx}"],
|
|
||||||
presets: [sharedConfig],
|
|
||||||
};
|
|
||||||
|
|
||||||
export default config;
|
|
@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "@lume/tsconfig/base.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"outDir": "dist"
|
|
||||||
},
|
|
||||||
"include": ["src"],
|
|
||||||
"exclude": ["node_modules", "dist"]
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@columns/group",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"private": true,
|
|
||||||
"main": "./src/index.tsx",
|
|
||||||
"dependencies": {
|
|
||||||
"@lume/ark": "workspace:^",
|
|
||||||
"@lume/icons": "workspace:^",
|
|
||||||
"@lume/ui": "workspace:^",
|
|
||||||
"@lume/utils": "workspace:^",
|
|
||||||
"@tanstack/react-query": "^5.28.4",
|
|
||||||
"react": "^18.2.0",
|
|
||||||
"sonner": "^1.4.3",
|
|
||||||
"virtua": "^0.27.5"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@lume/tailwindcss": "workspace:^",
|
|
||||||
"@lume/tsconfig": "workspace:^",
|
|
||||||
"@lume/types": "workspace:^",
|
|
||||||
"@types/react": "^18.2.66",
|
|
||||||
"tailwindcss": "^3.4.1",
|
|
||||||
"typescript": "^5.4.2"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,92 +0,0 @@
|
|||||||
import { useArk, useColumnContext } from "@lume/ark";
|
|
||||||
import { CancelIcon, CheckCircleIcon } from "@lume/icons";
|
|
||||||
import { User } from "@lume/ui";
|
|
||||||
import { useState } from "react";
|
|
||||||
|
|
||||||
export function GroupForm({ id }: { id: number }) {
|
|
||||||
const ark = useArk();
|
|
||||||
const { updateColumn, removeColumn } = useColumnContext();
|
|
||||||
|
|
||||||
const [title, setTitle] = useState<string>("Just a new group");
|
|
||||||
const [users, setUsers] = useState<Array<string>>([]);
|
|
||||||
|
|
||||||
// toggle follow state
|
|
||||||
const toggleUser = (pubkey: string) => {
|
|
||||||
const arr = users.includes(pubkey)
|
|
||||||
? users.filter((i) => i !== pubkey)
|
|
||||||
: [...users, pubkey];
|
|
||||||
setUsers(arr);
|
|
||||||
};
|
|
||||||
|
|
||||||
const submit = async () => {
|
|
||||||
await updateColumn(id, title, JSON.stringify(users));
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col justify-between h-full">
|
|
||||||
<div className="flex flex-col flex-1 min-h-0">
|
|
||||||
<div className="flex items-center justify-between w-full px-3 border-b h-11 shrink-0 border-neutral-100 dark:border-neutral-900">
|
|
||||||
<h1 className="text-sm font-semibold">Create a new Group</h1>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={async () => await removeColumn(id)}
|
|
||||||
className="inline-flex items-center justify-center rounded size-6 hover:bg-neutral-100 dark:hover:bg-neutral-900"
|
|
||||||
>
|
|
||||||
<CancelIcon className="size-4" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col gap-5 px-3 pt-2 overflow-y-auto">
|
|
||||||
<div className="flex flex-col gap-1.5">
|
|
||||||
<label
|
|
||||||
htmlFor="name"
|
|
||||||
className="font-medium text-neutral-700 dark:text-neutral-300"
|
|
||||||
>
|
|
||||||
Group Name
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
name="name"
|
|
||||||
value={title}
|
|
||||||
onChange={(e) => setTitle(e.target.value)}
|
|
||||||
placeholder="Nostrichs..."
|
|
||||||
className="px-2 border border-neutral-100 dark:border-neutral-900 bg-neutral-50 rounded-lg h-10 dark:bg-neutral-950 placeholder:text-neutral-600 focus:border-blue-500 focus:shadow-none focus:ring-0"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col gap-1">
|
|
||||||
<div className="inline-flex items-center justify-between">
|
|
||||||
<span className="font-medium text-neutral-700 dark:text-neutral-300">
|
|
||||||
Pick user
|
|
||||||
</span>
|
|
||||||
<span className="text-sm font-medium text-neutral-600 dark:text-neutral-400">{`${users.length} / ∞`}</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
{ark.account?.contacts?.map((item: string) => (
|
|
||||||
<button
|
|
||||||
key={item}
|
|
||||||
type="button"
|
|
||||||
onClick={() => toggleUser(item)}
|
|
||||||
className="inline-flex items-center justify-between px-3 py-2 rounded-xl bg-neutral-50 dark:bg-neutral-950 hover:bg-neutral-100 dark:hover:bg-neutral-900"
|
|
||||||
>
|
|
||||||
<User pubkey={item} variant="simple" />
|
|
||||||
{users.includes(item) ? (
|
|
||||||
<CheckCircleIcon className="w-5 h-5 text-teal-500" />
|
|
||||||
) : null}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
<div className="h-20" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="absolute z-10 flex items-center justify-center w-full bottom-3">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={submit}
|
|
||||||
disabled={users.length < 1}
|
|
||||||
className="inline-flex items-center justify-center gap-2 px-6 font-medium text-white transform bg-blue-500 rounded-full active:translate-y-1 w-36 h-11 hover:bg-blue-600 focus:outline-none disabled:cursor-not-allowed"
|
|
||||||
>
|
|
||||||
Create
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,119 +0,0 @@
|
|||||||
import { RepostNote, TextNote, useArk } from "@lume/ark";
|
|
||||||
import { ArrowRightCircleIcon, LoaderIcon } from "@lume/icons";
|
|
||||||
import { FETCH_LIMIT } from "@lume/utils";
|
|
||||||
import { NDKEvent, NDKKind } from "@nostr-dev-kit/ndk";
|
|
||||||
import { useInfiniteQuery } from "@tanstack/react-query";
|
|
||||||
import { useEffect, useMemo, useRef } from "react";
|
|
||||||
import { CacheSnapshot, VList, VListHandle } from "virtua";
|
|
||||||
|
|
||||||
export function HomeRoute({
|
|
||||||
colKey,
|
|
||||||
content,
|
|
||||||
}: { colKey: string; content: string }) {
|
|
||||||
const ark = useArk();
|
|
||||||
const ref = useRef<VListHandle>();
|
|
||||||
const cacheKey = `${colKey}-vlist`;
|
|
||||||
|
|
||||||
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 authors: string[] = JSON.parse(content);
|
|
||||||
const events = await ark.getInfiniteEvents({
|
|
||||||
filter: {
|
|
||||||
kinds: [NDKKind.Text, NDKKind.Repost],
|
|
||||||
authors: authors,
|
|
||||||
},
|
|
||||||
limit: FETCH_LIMIT,
|
|
||||||
pageParam,
|
|
||||||
signal,
|
|
||||||
});
|
|
||||||
|
|
||||||
return events;
|
|
||||||
},
|
|
||||||
getNextPageParam: (lastPage) => {
|
|
||||||
const lastEvent = lastPage.at(-1);
|
|
||||||
if (!lastEvent) return;
|
|
||||||
return lastEvent.created_at - 1;
|
|
||||||
},
|
|
||||||
refetchOnWindowFocus: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const allEvents = useMemo(
|
|
||||||
() => (data ? data.pages.flatMap((page) => page) : []),
|
|
||||||
[data],
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderItem = (event: NDKEvent) => {
|
|
||||||
switch (event.kind) {
|
|
||||||
case NDKKind.Text:
|
|
||||||
return <TextNote key={event.id} event={event} className="mt-3" />;
|
|
||||||
case NDKKind.Repost:
|
|
||||||
return <RepostNote key={event.id} event={event} className="mt-3" />;
|
|
||||||
default:
|
|
||||||
return <TextNote key={event.id} event={event} className="mt-3" />;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
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 (
|
|
||||||
<div className="w-full h-full">
|
|
||||||
<VList ref={ref} cache={cache} overscan={2} className="flex-1 px-3">
|
|
||||||
{isLoading ? (
|
|
||||||
<div className="w-full flex h-16 items-center justify-center gap-2 px-3 py-1.5">
|
|
||||||
<LoaderIcon className="size-5 animate-spin" />
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
allEvents.map((item) => renderItem(item))
|
|
||||||
)}
|
|
||||||
<div className="flex items-center justify-center h-16">
|
|
||||||
{hasNextPage ? (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => fetchNextPage()}
|
|
||||||
disabled={!hasNextPage || isFetchingNextPage}
|
|
||||||
className="inline-flex items-center justify-center w-full h-12 gap-2 font-medium bg-neutral-100 hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800 rounded-xl focus:outline-none"
|
|
||||||
>
|
|
||||||
{isFetchingNextPage ? (
|
|
||||||
<LoaderIcon className="size-5 animate-spin" />
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<ArrowRightCircleIcon className="size-5" />
|
|
||||||
Load more
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
</VList>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
import { Column } from "@lume/ark";
|
|
||||||
import { LumeColumn } from "@lume/types";
|
|
||||||
import { EventRoute, UserRoute } from "@lume/ui";
|
|
||||||
import { GroupForm } from "./components/form";
|
|
||||||
import { HomeRoute } from "./home";
|
|
||||||
|
|
||||||
export function Group({ column }: { column: LumeColumn }) {
|
|
||||||
const colKey = `group-${column.id}`;
|
|
||||||
const created = !!column.content?.length;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Column.Root>
|
|
||||||
{created ? (
|
|
||||||
<>
|
|
||||||
<Column.Header id={column.id} title={column.title} />
|
|
||||||
<Column.Content>
|
|
||||||
<Column.Route
|
|
||||||
path="/"
|
|
||||||
element={<HomeRoute colKey={colKey} content={column.content} />}
|
|
||||||
/>
|
|
||||||
<Column.Route path="/events/:id" element={<EventRoute />} />
|
|
||||||
<Column.Route path="/users/:id" element={<UserRoute />} />
|
|
||||||
</Column.Content>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<GroupForm id={column.id} />
|
|
||||||
)}
|
|
||||||
</Column.Root>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
import sharedConfig from "@lume/tailwindcss";
|
|
||||||
|
|
||||||
const config = {
|
|
||||||
content: ["./src/**/*.{js,ts,jsx,tsx}"],
|
|
||||||
presets: [sharedConfig],
|
|
||||||
};
|
|
||||||
|
|
||||||
export default config;
|
|
@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "@lume/tsconfig/base.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"outDir": "dist"
|
|
||||||
},
|
|
||||||
"include": ["src"],
|
|
||||||
"exclude": ["node_modules", "dist"]
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@columns/hashtag",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"private": true,
|
|
||||||
"main": "./src/index.tsx",
|
|
||||||
"dependencies": {
|
|
||||||
"@lume/ark": "workspace:^",
|
|
||||||
"@lume/icons": "workspace:^",
|
|
||||||
"@lume/ui": "workspace:^",
|
|
||||||
"@lume/utils": "workspace:^",
|
|
||||||
"@tanstack/react-query": "^5.28.4",
|
|
||||||
"react": "^18.2.0",
|
|
||||||
"sonner": "^1.4.3",
|
|
||||||
"virtua": "^0.27.5"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@lume/tailwindcss": "workspace:^",
|
|
||||||
"@lume/tsconfig": "workspace:^",
|
|
||||||
"@lume/types": "workspace:^",
|
|
||||||
"@types/react": "^18.2.66",
|
|
||||||
"tailwindcss": "^3.4.1",
|
|
||||||
"typescript": "^5.4.2"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,109 +0,0 @@
|
|||||||
import { TextNote, useArk } from "@lume/ark";
|
|
||||||
import { ArrowRightCircleIcon, LoaderIcon } from "@lume/icons";
|
|
||||||
import { FETCH_LIMIT } from "@lume/utils";
|
|
||||||
import { NDKKind } from "@nostr-dev-kit/ndk";
|
|
||||||
import { useInfiniteQuery } from "@tanstack/react-query";
|
|
||||||
import { useEffect, useMemo, useRef } from "react";
|
|
||||||
import { CacheSnapshot, VList, VListHandle } from "virtua";
|
|
||||||
|
|
||||||
export function HomeRoute({
|
|
||||||
colKey,
|
|
||||||
hashtag,
|
|
||||||
}: { colKey: string; hashtag: string }) {
|
|
||||||
const ark = useArk();
|
|
||||||
const ref = useRef<VListHandle>();
|
|
||||||
const cacheKey = `${colKey}-vlist`;
|
|
||||||
|
|
||||||
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": [hashtag],
|
|
||||||
},
|
|
||||||
limit: FETCH_LIMIT,
|
|
||||||
pageParam,
|
|
||||||
signal,
|
|
||||||
});
|
|
||||||
|
|
||||||
return events;
|
|
||||||
},
|
|
||||||
getNextPageParam: (lastPage) => {
|
|
||||||
const lastEvent = lastPage.at(-1);
|
|
||||||
if (!lastEvent) return;
|
|
||||||
return lastEvent.created_at - 1;
|
|
||||||
},
|
|
||||||
refetchOnWindowFocus: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const allEvents = useMemo(
|
|
||||||
() => (data ? data.pages.flatMap((page) => page) : []),
|
|
||||||
[data],
|
|
||||||
);
|
|
||||||
|
|
||||||
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 (
|
|
||||||
<div className="w-full h-full">
|
|
||||||
<VList ref={ref} cache={cache} overscan={2} className="flex-1 px-3">
|
|
||||||
{isLoading ? (
|
|
||||||
<div className="w-full flex h-16 items-center justify-center gap-2 px-3 py-1.5">
|
|
||||||
<LoaderIcon className="size-5 animate-spin" />
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
allEvents.map((item) => (
|
|
||||||
<TextNote key={item.id} event={item} className="mt-3" />
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
<div className="flex items-center justify-center h-16">
|
|
||||||
{hasNextPage ? (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => fetchNextPage()}
|
|
||||||
disabled={!hasNextPage || isFetchingNextPage}
|
|
||||||
className="inline-flex items-center justify-center w-full h-12 gap-2 font-medium bg-neutral-100 hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800 rounded-xl focus:outline-none"
|
|
||||||
>
|
|
||||||
{isFetchingNextPage ? (
|
|
||||||
<LoaderIcon className="size-5 animate-spin" />
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<ArrowRightCircleIcon className="size-5" />
|
|
||||||
Load more
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
</VList>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
import { Column } from "@lume/ark";
|
|
||||||
import { LumeColumn } from "@lume/types";
|
|
||||||
import { EventRoute, UserRoute } from "@lume/ui";
|
|
||||||
import { HomeRoute } from "./home";
|
|
||||||
|
|
||||||
export function Hashtag({ column }: { column: LumeColumn }) {
|
|
||||||
const colKey = `hashtag-${column.id}`;
|
|
||||||
const hashtag = column.content.replace("#", "");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Column.Root>
|
|
||||||
<Column.Header id={column.id} queryKey={[colKey]} title={hashtag} />
|
|
||||||
<Column.Content>
|
|
||||||
<Column.Route
|
|
||||||
path="/"
|
|
||||||
element={<HomeRoute colKey={colKey} hashtag={hashtag} />}
|
|
||||||
/>
|
|
||||||
<Column.Route path="/events/:id" element={<EventRoute />} />
|
|
||||||
<Column.Route path="/users/:id" element={<UserRoute />} />
|
|
||||||
</Column.Content>
|
|
||||||
</Column.Root>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
import sharedConfig from "@lume/tailwindcss";
|
|
||||||
|
|
||||||
const config = {
|
|
||||||
content: ["./src/**/*.{js,ts,jsx,tsx}"],
|
|
||||||
presets: [sharedConfig],
|
|
||||||
};
|
|
||||||
|
|
||||||
export default config;
|
|
@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "@lume/tsconfig/base.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"outDir": "dist"
|
|
||||||
},
|
|
||||||
"include": ["src"],
|
|
||||||
"exclude": ["node_modules", "dist"]
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@columns/thread",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"private": true,
|
|
||||||
"main": "./src/index.tsx",
|
|
||||||
"dependencies": {
|
|
||||||
"@lume/ark": "workspace:^",
|
|
||||||
"@lume/icons": "workspace:^",
|
|
||||||
"@lume/ui": "workspace:^",
|
|
||||||
"@lume/utils": "workspace:^",
|
|
||||||
"@tanstack/react-query": "^5.28.4",
|
|
||||||
"react": "^18.2.0",
|
|
||||||
"sonner": "^1.4.3",
|
|
||||||
"virtua": "^0.27.5"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@lume/tailwindcss": "workspace:^",
|
|
||||||
"@lume/tsconfig": "workspace:^",
|
|
||||||
"@lume/types": "workspace:^",
|
|
||||||
"@types/react": "^18.2.66",
|
|
||||||
"tailwindcss": "^3.4.1",
|
|
||||||
"typescript": "^5.4.2"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
import { ThreadNote } from "@lume/ark";
|
|
||||||
import { ReplyList } from "@lume/ui";
|
|
||||||
import { WindowVirtualizer } from "virtua";
|
|
||||||
|
|
||||||
export function HomeRoute({ id }: { id: string }) {
|
|
||||||
return (
|
|
||||||
<div className="overflow-y-auto pb-5">
|
|
||||||
<WindowVirtualizer>
|
|
||||||
<div className="mt-3 px-3">
|
|
||||||
<ThreadNote eventId={id} />
|
|
||||||
<ReplyList eventId={id} className="mt-5" />
|
|
||||||
</div>
|
|
||||||
</WindowVirtualizer>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
import { Column } from "@lume/ark";
|
|
||||||
import { LumeColumn } from "@lume/types";
|
|
||||||
import { HomeRoute } from "./home";
|
|
||||||
import { EventRoute, UserRoute } from "@lume/ui";
|
|
||||||
|
|
||||||
export function Thread({ column }: { column: LumeColumn }) {
|
|
||||||
return (
|
|
||||||
<Column.Root>
|
|
||||||
<Column.Header id={column.id} title={column.title} />
|
|
||||||
<Column.Content>
|
|
||||||
<Column.Route path="/" element={<HomeRoute id={column.content} />} />
|
|
||||||
<Column.Route path="/events/:id" element={<EventRoute />} />
|
|
||||||
<Column.Route path="/users/:id" element={<UserRoute />} />
|
|
||||||
</Column.Content>
|
|
||||||
</Column.Root>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
import sharedConfig from "@lume/tailwindcss";
|
|
||||||
|
|
||||||
const config = {
|
|
||||||
content: ["./src/**/*.{js,ts,jsx,tsx}"],
|
|
||||||
presets: [sharedConfig],
|
|
||||||
};
|
|
||||||
|
|
||||||
export default config;
|
|
@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "@lume/tsconfig/base.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"outDir": "dist"
|
|
||||||
},
|
|
||||||
"include": ["src"],
|
|
||||||
"exclude": ["node_modules", "dist"]
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@columns/timeline",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"private": true,
|
|
||||||
"main": "./src/index.tsx",
|
|
||||||
"dependencies": {
|
|
||||||
"@lume/ark": "workspace:^",
|
|
||||||
"@lume/icons": "workspace:^",
|
|
||||||
"@lume/ui": "workspace:^",
|
|
||||||
"@lume/utils": "workspace:^",
|
|
||||||
"@tanstack/react-query": "^5.28.4",
|
|
||||||
"react": "^18.2.0",
|
|
||||||
"sonner": "^1.4.3",
|
|
||||||
"virtua": "^0.27.5"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@lume/tailwindcss": "workspace:^",
|
|
||||||
"@lume/tsconfig": "workspace:^",
|
|
||||||
"@lume/types": "workspace:^",
|
|
||||||
"@types/react": "^18.2.66",
|
|
||||||
"tailwindcss": "^3.4.1",
|
|
||||||
"typescript": "^5.4.2"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,114 +0,0 @@
|
|||||||
import { RepostNote, TextNote, useArk } from "@lume/ark";
|
|
||||||
import { ArrowRightCircleIcon, LoaderIcon, SearchIcon } from "@lume/icons";
|
|
||||||
import { Event, Kind } from "@lume/types";
|
|
||||||
import { EmptyFeed } from "@lume/ui";
|
|
||||||
import { FETCH_LIMIT } from "@lume/utils";
|
|
||||||
import { useInfiniteQuery } from "@tanstack/react-query";
|
|
||||||
import { useEffect, useMemo, useRef } from "react";
|
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
import { CacheSnapshot, VList, VListHandle } from "virtua";
|
|
||||||
|
|
||||||
export function HomeRoute({ queryKey }: { queryKey: string }) {
|
|
||||||
const ark = useArk();
|
|
||||||
const ref = useRef<VListHandle>();
|
|
||||||
const cacheKey = `${queryKey}-vlist`;
|
|
||||||
|
|
||||||
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: [queryKey],
|
|
||||||
initialPageParam: 0,
|
|
||||||
queryFn: async ({
|
|
||||||
pageParam,
|
|
||||||
}: {
|
|
||||||
pageParam: number;
|
|
||||||
}) => {
|
|
||||||
const events = await ark.get_text_events(FETCH_LIMIT, pageParam);
|
|
||||||
return events;
|
|
||||||
},
|
|
||||||
getNextPageParam: (lastPage) => {
|
|
||||||
const lastEvent = lastPage.at(-1);
|
|
||||||
if (!lastEvent) return;
|
|
||||||
return lastEvent.created_at - 1;
|
|
||||||
},
|
|
||||||
select: (data) => data?.pages.flatMap((page) => page),
|
|
||||||
refetchOnWindowFocus: false,
|
|
||||||
refetchOnMount: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const renderItem = (event: Event) => {
|
|
||||||
switch (event.kind) {
|
|
||||||
case Kind.Text:
|
|
||||||
return <TextNote key={event.id} event={event} className="mt-3" />;
|
|
||||||
case Kind.Repost:
|
|
||||||
return <RepostNote key={event.id} event={event} className="mt-3" />;
|
|
||||||
default:
|
|
||||||
return <TextNote key={event.id} event={event} className="mt-3" />;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
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 (
|
|
||||||
<div className="w-full h-full">
|
|
||||||
<VList ref={ref} cache={cache} overscan={2} className="flex-1 px-3">
|
|
||||||
{isLoading ? (
|
|
||||||
<div className="w-full flex h-16 items-center justify-center gap-2 px-3 py-1.5">
|
|
||||||
<LoaderIcon className="size-5 animate-spin" />
|
|
||||||
</div>
|
|
||||||
) : !data.length ? (
|
|
||||||
<div className="px-3 mt-3">
|
|
||||||
<EmptyFeed />
|
|
||||||
<Link
|
|
||||||
to="/suggest"
|
|
||||||
className="mt-3 w-full gap-2 inline-flex items-center justify-center text-sm font-medium rounded-lg h-9 bg-blue-500 hover:bg-blue-600 text-white"
|
|
||||||
>
|
|
||||||
<SearchIcon className="size-5" />
|
|
||||||
Find accounts to follow
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
data.map((item) => renderItem(item))
|
|
||||||
)}
|
|
||||||
<div className="flex items-center justify-center h-16">
|
|
||||||
{hasNextPage ? (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => fetchNextPage()}
|
|
||||||
disabled={!hasNextPage || isFetchingNextPage}
|
|
||||||
className="inline-flex items-center justify-center w-full h-12 gap-2 font-medium bg-neutral-100 hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800 rounded-xl focus:outline-none"
|
|
||||||
>
|
|
||||||
{isFetchingNextPage ? (
|
|
||||||
<LoaderIcon className="size-5 animate-spin" />
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<ArrowRightCircleIcon className="size-5" />
|
|
||||||
Load more
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
</VList>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
import { Column } from "@lume/ark";
|
|
||||||
import { LumeColumn } from "@lume/types";
|
|
||||||
import { EventRoute, SuggestRoute, UserRoute } from "@lume/ui";
|
|
||||||
import { HomeRoute } from "./home";
|
|
||||||
|
|
||||||
export function Timeline({ column }: { column: LumeColumn }) {
|
|
||||||
const colKey = `timeline-${column.id}`;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Column.Provider column={column}>
|
|
||||||
<Column.Root>
|
|
||||||
<Column.Header queryKey={[colKey]} />
|
|
||||||
<Column.Content>
|
|
||||||
<Column.Route path="/" element={<HomeRoute queryKey={colKey} />} />
|
|
||||||
<Column.Route path="/events/:id" element={<EventRoute />} />
|
|
||||||
<Column.Route path="/users/:id" element={<UserRoute />} />
|
|
||||||
<Column.Route
|
|
||||||
path="/suggest"
|
|
||||||
element={<SuggestRoute queryKey={colKey} />}
|
|
||||||
/>
|
|
||||||
</Column.Content>
|
|
||||||
</Column.Root>
|
|
||||||
</Column.Provider>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
import sharedConfig from "@lume/tailwindcss";
|
|
||||||
|
|
||||||
const config = {
|
|
||||||
content: ["./src/**/*.{js,ts,jsx,tsx}"],
|
|
||||||
presets: [sharedConfig],
|
|
||||||
};
|
|
||||||
|
|
||||||
export default config;
|
|
@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "@lume/tsconfig/base.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"outDir": "dist"
|
|
||||||
},
|
|
||||||
"include": ["src"],
|
|
||||||
"exclude": ["node_modules", "dist"]
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@columns/trending-notes",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"private": true,
|
|
||||||
"main": "./src/index.tsx",
|
|
||||||
"dependencies": {
|
|
||||||
"@lume/ark": "workspace:^",
|
|
||||||
"@lume/icons": "workspace:^",
|
|
||||||
"@lume/ui": "workspace:^",
|
|
||||||
"@lume/utils": "workspace:^",
|
|
||||||
"@tanstack/react-query": "^5.28.4",
|
|
||||||
"react": "^18.2.0",
|
|
||||||
"sonner": "^1.4.3",
|
|
||||||
"virtua": "^0.27.5"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@lume/tailwindcss": "workspace:^",
|
|
||||||
"@lume/tsconfig": "workspace:^",
|
|
||||||
"@lume/types": "workspace:^",
|
|
||||||
"@types/react": "^18.2.66",
|
|
||||||
"tailwindcss": "^3.4.1",
|
|
||||||
"typescript": "^5.4.2"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,71 +0,0 @@
|
|||||||
import { TextNote, useArk } from "@lume/ark";
|
|
||||||
import { LoaderIcon } from "@lume/icons";
|
|
||||||
import { type NDKEvent, type NostrEvent } from "@nostr-dev-kit/ndk";
|
|
||||||
import { useQuery } from "@tanstack/react-query";
|
|
||||||
import { fetch } from "@tauri-apps/plugin-http";
|
|
||||||
import { useEffect, useMemo, useRef } from "react";
|
|
||||||
import { CacheSnapshot, VList, VListHandle } from "virtua";
|
|
||||||
|
|
||||||
export function HomeRoute({ colKey }: { colKey: string }) {
|
|
||||||
const ark = useArk();
|
|
||||||
const ref = useRef<VListHandle>();
|
|
||||||
const cacheKey = `${colKey}-vlist`;
|
|
||||||
|
|
||||||
const [offset, cache] = useMemo(() => {
|
|
||||||
const serialized = sessionStorage.getItem(cacheKey);
|
|
||||||
if (!serialized) return [];
|
|
||||||
return JSON.parse(serialized) as [number, CacheSnapshot];
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const { data, isLoading } = useQuery({
|
|
||||||
queryKey: [colKey],
|
|
||||||
queryFn: async ({ signal }: { signal: AbortSignal }) => {
|
|
||||||
const res = await fetch("https://api.nostr.band/v0/trending/notes", {
|
|
||||||
signal,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!res) throw new Error("Failed to fetch trending notes");
|
|
||||||
|
|
||||||
const data = await res.json();
|
|
||||||
const events = data.notes.map((item: { event: NostrEvent }) =>
|
|
||||||
ark.getNDKEvent(item.event),
|
|
||||||
);
|
|
||||||
|
|
||||||
return events as NDKEvent[];
|
|
||||||
},
|
|
||||||
refetchOnMount: false,
|
|
||||||
refetchOnWindowFocus: 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 (
|
|
||||||
<div className="w-full h-full">
|
|
||||||
<VList ref={ref} cache={cache} overscan={2} className="flex-1 px-3">
|
|
||||||
{isLoading ? (
|
|
||||||
<div className="w-full flex h-16 items-center justify-center gap-2 px-3 py-1.5">
|
|
||||||
<LoaderIcon className="size-5 animate-spin" />
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
data.map((item) => (
|
|
||||||
<TextNote key={item.id} event={item} className="mt-3" />
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</VList>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
import { Column } from "@lume/ark";
|
|
||||||
import { LumeColumn } from "@lume/types";
|
|
||||||
import { EventRoute, UserRoute } from "@lume/ui";
|
|
||||||
import { HomeRoute } from "./home";
|
|
||||||
|
|
||||||
export function TrendingNotes({ column }: { column: LumeColumn }) {
|
|
||||||
const colKey = `trending-notes-${column.id}`;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Column.Root>
|
|
||||||
<Column.Header
|
|
||||||
id={column.id}
|
|
||||||
queryKey={[colKey]}
|
|
||||||
title="Trending Notes"
|
|
||||||
/>
|
|
||||||
<Column.Content>
|
|
||||||
<Column.Route path="/" element={<HomeRoute colKey={colKey} />} />
|
|
||||||
<Column.Route path="/events/:id" element={<EventRoute />} />
|
|
||||||
<Column.Route path="/users/:id" element={<UserRoute />} />
|
|
||||||
</Column.Content>
|
|
||||||
</Column.Root>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
import sharedConfig from "@lume/tailwindcss";
|
|
||||||
|
|
||||||
const config = {
|
|
||||||
content: ["./src/**/*.{js,ts,jsx,tsx}"],
|
|
||||||
presets: [sharedConfig],
|
|
||||||
};
|
|
||||||
|
|
||||||
export default config;
|
|
@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "@lume/tsconfig/base.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"outDir": "dist"
|
|
||||||
},
|
|
||||||
"include": ["src"],
|
|
||||||
"exclude": ["node_modules", "dist"]
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@columns/user",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"private": true,
|
|
||||||
"main": "./src/index.tsx",
|
|
||||||
"dependencies": {
|
|
||||||
"@lume/ark": "workspace:^",
|
|
||||||
"@lume/icons": "workspace:^",
|
|
||||||
"@lume/ui": "workspace:^",
|
|
||||||
"@lume/utils": "workspace:^",
|
|
||||||
"@tanstack/react-query": "^5.28.4",
|
|
||||||
"react": "^18.2.0",
|
|
||||||
"sonner": "^1.4.3",
|
|
||||||
"virtua": "^0.27.5"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@lume/tailwindcss": "workspace:^",
|
|
||||||
"@lume/tsconfig": "workspace:^",
|
|
||||||
"@lume/types": "workspace:^",
|
|
||||||
"@types/react": "^18.2.66",
|
|
||||||
"tailwindcss": "^3.4.1",
|
|
||||||
"typescript": "^5.4.2"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,120 +0,0 @@
|
|||||||
import { RepostNote, TextNote, User, useArk } from "@lume/ark";
|
|
||||||
import { ArrowRightCircleIcon, LoaderIcon } from "@lume/icons";
|
|
||||||
import { FETCH_LIMIT } from "@lume/utils";
|
|
||||||
import { NDKEvent, NDKKind } from "@nostr-dev-kit/ndk";
|
|
||||||
import { useInfiniteQuery } from "@tanstack/react-query";
|
|
||||||
import { useMemo } from "react";
|
|
||||||
import { WindowVirtualizer } from "virtua";
|
|
||||||
|
|
||||||
export function HomeRoute({ id }: { id: string }) {
|
|
||||||
const ark = useArk();
|
|
||||||
const { data, hasNextPage, isLoading, isFetchingNextPage, fetchNextPage } =
|
|
||||||
useInfiniteQuery({
|
|
||||||
queryKey: ["user-posts", id],
|
|
||||||
initialPageParam: 0,
|
|
||||||
queryFn: async ({
|
|
||||||
signal,
|
|
||||||
pageParam,
|
|
||||||
}: {
|
|
||||||
signal: AbortSignal;
|
|
||||||
pageParam: number;
|
|
||||||
}) => {
|
|
||||||
const events = await ark.getInfiniteEvents({
|
|
||||||
filter: {
|
|
||||||
kinds: [NDKKind.Text, NDKKind.Repost],
|
|
||||||
authors: [id],
|
|
||||||
},
|
|
||||||
limit: FETCH_LIMIT,
|
|
||||||
pageParam,
|
|
||||||
signal,
|
|
||||||
});
|
|
||||||
|
|
||||||
return events;
|
|
||||||
},
|
|
||||||
getNextPageParam: (lastPage) => {
|
|
||||||
const lastEvent = lastPage.at(-1);
|
|
||||||
if (!lastEvent) return;
|
|
||||||
return lastEvent.created_at - 1;
|
|
||||||
},
|
|
||||||
refetchOnWindowFocus: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const allEvents = useMemo(
|
|
||||||
() => (data ? data.pages.flatMap((page) => page) : []),
|
|
||||||
[data],
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderItem = (event: NDKEvent) => {
|
|
||||||
switch (event.kind) {
|
|
||||||
case NDKKind.Text:
|
|
||||||
return <TextNote key={event.id} event={event} className="mt-3" />;
|
|
||||||
case NDKKind.Repost:
|
|
||||||
return <RepostNote key={event.id} event={event} className="mt-3" />;
|
|
||||||
default:
|
|
||||||
return <TextNote key={event.id} event={event} className="mt-3" />;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="py-5 overflow-y-auto">
|
|
||||||
<WindowVirtualizer>
|
|
||||||
<div className="px-3">
|
|
||||||
<User.Provider pubkey={id}>
|
|
||||||
<User.Root className="flex flex-col gap-2">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<User.Avatar className="h-12 w-12 shrink-0 rounded-lg object-cover" />
|
|
||||||
<User.Button
|
|
||||||
target={id}
|
|
||||||
className="inline-flex items-center justify-center w-24 text-sm font-semibold border-t rounded-lg border-neutral-900 dark:border-neutral-800 h-9 bg-neutral-950 text-neutral-50 dark:bg-neutral-900 hover:bg-neutral-900 dark:hover:bg-neutral-800"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-1 flex-col gap-1.5">
|
|
||||||
<div className="flex flex-col">
|
|
||||||
<User.Name className="text-lg font-semibold" />
|
|
||||||
<User.NIP05
|
|
||||||
pubkey={id}
|
|
||||||
className="max-w-[15rem] truncate text-sm text-neutral-600 dark:text-neutral-400"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<User.About className="text-neutral-900 dark:text-neutral-100" />
|
|
||||||
</div>
|
|
||||||
</User.Root>
|
|
||||||
</User.Provider>
|
|
||||||
<div className="pt-2 mt-2 border-t border-neutral-100 dark:border-neutral-900">
|
|
||||||
<h3 className="text-lg font-semibold text-neutral-900 dark:text-neutral-100">
|
|
||||||
Latest posts
|
|
||||||
</h3>
|
|
||||||
<div className="flex h-full w-full flex-col justify-between gap-1.5 pb-10">
|
|
||||||
{isLoading ? (
|
|
||||||
<div className="flex items-center justify-center">
|
|
||||||
<LoaderIcon className="w-4 h-4 animate-spin" />
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
allEvents.map((item) => renderItem(item))
|
|
||||||
)}
|
|
||||||
<div className="flex items-center justify-center h-16 px-3 pb-3">
|
|
||||||
{hasNextPage ? (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => fetchNextPage()}
|
|
||||||
disabled={!hasNextPage || isFetchingNextPage}
|
|
||||||
className="inline-flex items-center justify-center w-full h-12 gap-2 font-medium bg-neutral-100 hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800 rounded-xl focus:outline-none"
|
|
||||||
>
|
|
||||||
{isFetchingNextPage ? (
|
|
||||||
<LoaderIcon className="size-5 animate-spin" />
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<ArrowRightCircleIcon className="size-5" />
|
|
||||||
Load more
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</WindowVirtualizer>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
import { Column } from "@lume/ark";
|
|
||||||
import { LumeColumn } from "@lume/types";
|
|
||||||
import { EventRoute, UserRoute } from "@lume/ui";
|
|
||||||
import { HomeRoute } from "./home";
|
|
||||||
|
|
||||||
export function User({ column }: { column: LumeColumn }) {
|
|
||||||
return (
|
|
||||||
<Column.Root>
|
|
||||||
<Column.Header id={column.id} title={column.title} />
|
|
||||||
<Column.Content>
|
|
||||||
<Column.Route path="/" element={<HomeRoute id={column.content} />} />
|
|
||||||
<Column.Route path="/events/:id" element={<EventRoute />} />
|
|
||||||
<Column.Route path="/users/:id" element={<UserRoute />} />
|
|
||||||
</Column.Content>
|
|
||||||
</Column.Root>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
import sharedConfig from "@lume/tailwindcss";
|
|
||||||
|
|
||||||
const config = {
|
|
||||||
content: ["./src/**/*.{js,ts,jsx,tsx}"],
|
|
||||||
presets: [sharedConfig],
|
|
||||||
};
|
|
||||||
|
|
||||||
export default config;
|
|
@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "@lume/tsconfig/base.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"outDir": "dist"
|
|
||||||
},
|
|
||||||
"include": ["src"],
|
|
||||||
"exclude": ["node_modules", "dist"]
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@columns/waifu",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"private": true,
|
|
||||||
"main": "./src/index.tsx",
|
|
||||||
"dependencies": {
|
|
||||||
"@lume/ark": "workspace:^",
|
|
||||||
"@lume/icons": "workspace:^",
|
|
||||||
"@lume/ui": "workspace:^",
|
|
||||||
"@lume/utils": "workspace:^",
|
|
||||||
"@tanstack/react-query": "^5.28.4",
|
|
||||||
"react": "^18.2.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@lume/tailwindcss": "workspace:^",
|
|
||||||
"@lume/tsconfig": "workspace:^",
|
|
||||||
"@lume/types": "workspace:^",
|
|
||||||
"@types/react": "^18.2.66",
|
|
||||||
"tailwindcss": "^3.4.1",
|
|
||||||
"typescript": "^5.4.2"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,69 +0,0 @@
|
|||||||
import { LoaderIcon, RefreshIcon } from "@lume/icons";
|
|
||||||
import { cn } from "@lume/utils";
|
|
||||||
import { useQuery } from "@tanstack/react-query";
|
|
||||||
|
|
||||||
export function HomeRoute({ colKey }: { colKey: string }) {
|
|
||||||
const { data, isLoading, isError, isRefetching, refetch } = useQuery({
|
|
||||||
queryKey: [colKey],
|
|
||||||
queryFn: async ({ signal }: { signal: AbortSignal }) => {
|
|
||||||
const apiUrl = "https://api.waifu.im/search";
|
|
||||||
const params = {
|
|
||||||
included_tags: "waifu",
|
|
||||||
height: ">=2000",
|
|
||||||
};
|
|
||||||
|
|
||||||
const queryParams = new URLSearchParams(params);
|
|
||||||
const requestUrl = `${apiUrl}?${queryParams}`;
|
|
||||||
|
|
||||||
const res = await fetch(requestUrl, { signal });
|
|
||||||
|
|
||||||
if (!res.ok) throw new Error("Failed to get image url");
|
|
||||||
|
|
||||||
const data = await res.json();
|
|
||||||
return data.images[0];
|
|
||||||
},
|
|
||||||
refetchOnMount: false,
|
|
||||||
refetchOnWindowFocus: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="p-3 h-full flex flex-col justify-center items-center">
|
|
||||||
{isLoading ? (
|
|
||||||
<LoaderIcon className="size-5 animate-spin" />
|
|
||||||
) : isError ? (
|
|
||||||
<p className="text-center text-sm font-medium">
|
|
||||||
Failed to get image, please try again later.
|
|
||||||
</p>
|
|
||||||
) : (
|
|
||||||
<div className="relative min-h-0 flex-1 grow-0 w-full rounded-xl flex items-stretch">
|
|
||||||
<img
|
|
||||||
src={data.url}
|
|
||||||
alt={data.signature}
|
|
||||||
loading="lazy"
|
|
||||||
decoding="async"
|
|
||||||
className="object-cover w-full rounded-xl ring-1 ring-black/5 dark:ring-white/5"
|
|
||||||
/>
|
|
||||||
<div className="absolute bottom-3 right-3 flex items-center gap-2">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => refetch()}
|
|
||||||
className="text-sm font-medium px-2 h-7 inline-flex items-center justify-center bg-black/50 hover:bg-black/30 backdrop-blur-2xl rounded-md text-white"
|
|
||||||
>
|
|
||||||
<RefreshIcon
|
|
||||||
className={cn("size-4", isRefetching ? "animate-spin" : "")}
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
<a
|
|
||||||
href={data.source}
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
className="text-sm font-medium px-2 h-7 inline-flex items-center justify-center bg-black/50 hover:bg-black/30 backdrop-blur-2xl rounded-md text-white"
|
|
||||||
>
|
|
||||||
Source
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
import { Column } from "@lume/ark";
|
|
||||||
import { LumeColumn } from "@lume/types";
|
|
||||||
import { HomeRoute } from "./home";
|
|
||||||
|
|
||||||
export function Waifu({ column }: { column: LumeColumn }) {
|
|
||||||
const colKey = `waifu-${column.id}`;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Column.Root>
|
|
||||||
<Column.Header id={column.id} title={column.title} />
|
|
||||||
<Column.Content>
|
|
||||||
<Column.Route path="/" element={<HomeRoute colKey={colKey} />} />
|
|
||||||
</Column.Content>
|
|
||||||
</Column.Root>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
import sharedConfig from "@lume/tailwindcss";
|
|
||||||
|
|
||||||
const config = {
|
|
||||||
content: ["./src/**/*.{js,ts,jsx,tsx}"],
|
|
||||||
presets: [sharedConfig],
|
|
||||||
};
|
|
||||||
|
|
||||||
export default config;
|
|
@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "@lume/tsconfig/base.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"outDir": "dist"
|
|
||||||
},
|
|
||||||
"include": ["src"],
|
|
||||||
"exclude": ["node_modules", "dist"]
|
|
||||||
}
|
|
7
packages/types/index.d.ts
vendored
7
packages/types/index.d.ts
vendored
@ -1,5 +1,3 @@
|
|||||||
import { type Webview } from "@tauri-apps/api/webview";
|
|
||||||
|
|
||||||
export interface Settings {
|
export interface Settings {
|
||||||
autoupdate: boolean;
|
autoupdate: boolean;
|
||||||
nsecbunker: boolean;
|
nsecbunker: boolean;
|
||||||
@ -88,11 +86,6 @@ export interface LumeColumn {
|
|||||||
description?: string;
|
description?: string;
|
||||||
author?: string;
|
author?: string;
|
||||||
logo?: string;
|
logo?: string;
|
||||||
x?: number;
|
|
||||||
y?: number;
|
|
||||||
width?: number;
|
|
||||||
height?: number;
|
|
||||||
window?: Webview;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Opengraph {
|
export interface Opengraph {
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
"@tanstack/react-router": "^1.20.0",
|
"@tanstack/react-router": "^1.20.0",
|
||||||
"framer-motion": "^11.0.14",
|
"framer-motion": "^11.0.14",
|
||||||
"get-urls": "^12.1.0",
|
"get-urls": "^12.1.0",
|
||||||
"media-chrome": "^2.2.5",
|
"media-chrome": "^3.0.2",
|
||||||
"minidenticons": "^4.2.1",
|
"minidenticons": "^4.2.1",
|
||||||
"nanoid": "^5.0.6",
|
"nanoid": "^5.0.6",
|
||||||
"qrcode.react": "^3.1.0",
|
"qrcode.react": "^3.1.0",
|
||||||
@ -34,13 +34,13 @@
|
|||||||
"react-hotkeys-hook": "^4.5.0",
|
"react-hotkeys-hook": "^4.5.0",
|
||||||
"react-i18next": "^14.1.0",
|
"react-i18next": "^14.1.0",
|
||||||
"react-string-replace": "^1.1.1",
|
"react-string-replace": "^1.1.1",
|
||||||
"slate": "^0.101.5",
|
"slate": "^0.102.0",
|
||||||
"slate-react": "^0.101.6",
|
"slate-react": "^0.102.0",
|
||||||
"sonner": "^1.4.3",
|
"sonner": "^1.4.3",
|
||||||
"string-strip-html": "^13.4.6",
|
"string-strip-html": "^13.4.6",
|
||||||
"uqr": "^0.1.2",
|
"uqr": "^0.1.2",
|
||||||
"use-debounce": "^10.0.0",
|
"use-debounce": "^10.0.0",
|
||||||
"virtua": "^0.27.5"
|
"virtua": "^0.29.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@lume/tailwindcss": "workspace:^",
|
"@lume/tailwindcss": "workspace:^",
|
||||||
|
@ -7,6 +7,7 @@ import {
|
|||||||
import { Webview } from "@tauri-apps/api/webview";
|
import { Webview } from "@tauri-apps/api/webview";
|
||||||
import { LumeColumn } from "@lume/types";
|
import { LumeColumn } from "@lume/types";
|
||||||
import { useDebouncedCallback } from "use-debounce";
|
import { useDebouncedCallback } from "use-debounce";
|
||||||
|
import { type UnlistenFn } from "@tauri-apps/api/event";
|
||||||
|
|
||||||
export function Column({
|
export function Column({
|
||||||
column,
|
column,
|
||||||
@ -19,6 +20,7 @@ export function Column({
|
|||||||
const childWindow = useRef<Webview>(null);
|
const childWindow = useRef<Webview>(null);
|
||||||
const divRef = useRef<HTMLDivElement>(null);
|
const divRef = useRef<HTMLDivElement>(null);
|
||||||
const initialRect = useRef<DOMRect>(null);
|
const initialRect = useRef<DOMRect>(null);
|
||||||
|
const unlisten = useRef<UnlistenFn>(null);
|
||||||
const handleResize = useDebouncedCallback(() => {
|
const handleResize = useDebouncedCallback(() => {
|
||||||
const newRect = divRef.current.getBoundingClientRect();
|
const newRect = divRef.current.getBoundingClientRect();
|
||||||
if (initialRect.current.height !== newRect.height) {
|
if (initialRect.current.height !== newRect.height) {
|
||||||
@ -26,16 +28,12 @@ export function Column({
|
|||||||
new LogicalSize(newRect.width, newRect.height),
|
new LogicalSize(newRect.width, newRect.height),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}, 800);
|
}, 500);
|
||||||
|
|
||||||
const trackResize = useCallback(async () => {
|
const trackResize = useCallback(async () => {
|
||||||
const unlisten = await mainWindow.onResized(() => {
|
unlisten.current = await mainWindow.onResized(() => {
|
||||||
handleResize();
|
handleResize();
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
|
||||||
if (unlisten) unlisten();
|
|
||||||
};
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -52,15 +50,12 @@ export function Column({
|
|||||||
if (!divRef.current) return;
|
if (!divRef.current) return;
|
||||||
if (childWindow.current) return;
|
if (childWindow.current) return;
|
||||||
|
|
||||||
// get element dimension
|
|
||||||
const rect = divRef.current.getBoundingClientRect();
|
const rect = divRef.current.getBoundingClientRect();
|
||||||
|
const name = column.name.toLowerCase().replace(/\W/g, "");
|
||||||
|
|
||||||
// create new webview
|
// create new webview
|
||||||
initialRect.current = rect;
|
initialRect.current = rect;
|
||||||
childWindow.current = new Webview(
|
childWindow.current = new Webview(mainWindow, name, {
|
||||||
mainWindow,
|
|
||||||
column.name.toLowerCase().replace(/\W/g, ""),
|
|
||||||
{
|
|
||||||
url: column.content,
|
url: column.content,
|
||||||
x: rect.x,
|
x: rect.x,
|
||||||
y: rect.y,
|
y: rect.y,
|
||||||
@ -68,11 +63,14 @@ export function Column({
|
|||||||
height: rect.height,
|
height: rect.height,
|
||||||
transparent: true,
|
transparent: true,
|
||||||
userAgent: "Lume/4.0",
|
userAgent: "Lume/4.0",
|
||||||
},
|
});
|
||||||
);
|
|
||||||
|
|
||||||
// track window resize event
|
// track window resize event
|
||||||
trackResize();
|
trackResize();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (unlisten.current) unlisten.current();
|
||||||
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -15,8 +15,8 @@
|
|||||||
"nostr-tools": "^2.3.1",
|
"nostr-tools": "^2.3.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"slate": "^0.101.5",
|
"slate": "^0.102.0",
|
||||||
"slate-react": "^0.101.6"
|
"slate-react": "^0.102.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@lume/tsconfig": "workspace:^",
|
"@lume/tsconfig": "workspace:^",
|
||||||
|
570
pnpm-lock.yaml
570
pnpm-lock.yaml
@ -121,17 +121,17 @@ importers:
|
|||||||
specifier: ^14.1.0
|
specifier: ^14.1.0
|
||||||
version: 14.1.0(i18next@23.10.1)(react-dom@18.2.0)(react@18.2.0)
|
version: 14.1.0(i18next@23.10.1)(react-dom@18.2.0)(react@18.2.0)
|
||||||
slate:
|
slate:
|
||||||
specifier: ^0.101.5
|
specifier: ^0.102.0
|
||||||
version: 0.101.5
|
version: 0.102.0
|
||||||
slate-react:
|
slate-react:
|
||||||
specifier: ^0.101.6
|
specifier: ^0.102.0
|
||||||
version: 0.101.6(react-dom@18.2.0)(react@18.2.0)(slate@0.101.5)
|
version: 0.102.0(react-dom@18.2.0)(react@18.2.0)(slate@0.102.0)
|
||||||
sonner:
|
sonner:
|
||||||
specifier: ^1.4.3
|
specifier: ^1.4.3
|
||||||
version: 1.4.3(react-dom@18.2.0)(react@18.2.0)
|
version: 1.4.3(react-dom@18.2.0)(react@18.2.0)
|
||||||
virtua:
|
virtua:
|
||||||
specifier: ^0.27.5
|
specifier: ^0.29.0
|
||||||
version: 0.27.5(react-dom@18.2.0)(react@18.2.0)
|
version: 0.29.0(react-dom@18.2.0)(react@18.2.0)
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@lume/tailwindcss':
|
'@lume/tailwindcss':
|
||||||
specifier: workspace:^
|
specifier: workspace:^
|
||||||
@ -182,8 +182,8 @@ importers:
|
|||||||
apps/web:
|
apps/web:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@astrojs/check':
|
'@astrojs/check':
|
||||||
specifier: ^0.4.1
|
specifier: ^0.5.9
|
||||||
version: 0.4.1(typescript@5.4.2)
|
version: 0.5.9(typescript@5.4.2)
|
||||||
'@astrojs/tailwind':
|
'@astrojs/tailwind':
|
||||||
specifier: ^5.1.0
|
specifier: ^5.1.0
|
||||||
version: 5.1.0(astro@4.5.5)(tailwindcss@3.4.1)
|
version: 5.1.0(astro@4.5.5)(tailwindcss@3.4.1)
|
||||||
@ -255,8 +255,8 @@ importers:
|
|||||||
specifier: ^12.1.0
|
specifier: ^12.1.0
|
||||||
version: 12.1.0
|
version: 12.1.0
|
||||||
media-chrome:
|
media-chrome:
|
||||||
specifier: ^2.2.5
|
specifier: ^3.0.2
|
||||||
version: 2.2.5
|
version: 3.0.2
|
||||||
minidenticons:
|
minidenticons:
|
||||||
specifier: ^4.2.1
|
specifier: ^4.2.1
|
||||||
version: 4.2.1
|
version: 4.2.1
|
||||||
@ -288,8 +288,8 @@ importers:
|
|||||||
specifier: ^13.4.6
|
specifier: ^13.4.6
|
||||||
version: 13.4.6
|
version: 13.4.6
|
||||||
virtua:
|
virtua:
|
||||||
specifier: ^0.27.5
|
specifier: ^0.29.0
|
||||||
version: 0.27.5(react-dom@18.2.0)(react@18.2.0)
|
version: 0.29.0(react-dom@18.2.0)(react@18.2.0)
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@lume/tailwindcss':
|
'@lume/tailwindcss':
|
||||||
specifier: workspace:^
|
specifier: workspace:^
|
||||||
@ -326,506 +326,6 @@ importers:
|
|||||||
specifier: ^5.4.2
|
specifier: ^5.4.2
|
||||||
version: 5.4.2
|
version: 5.4.2
|
||||||
|
|
||||||
packages/lume-column-antenas:
|
|
||||||
dependencies:
|
|
||||||
'@lume/ark':
|
|
||||||
specifier: workspace:^
|
|
||||||
version: link:../ark
|
|
||||||
'@lume/icons':
|
|
||||||
specifier: workspace:^
|
|
||||||
version: link:../icons
|
|
||||||
'@lume/ui':
|
|
||||||
specifier: workspace:^
|
|
||||||
version: link:../ui
|
|
||||||
'@lume/utils':
|
|
||||||
specifier: workspace:^
|
|
||||||
version: link:../utils
|
|
||||||
'@tanstack/react-query':
|
|
||||||
specifier: ^5.28.4
|
|
||||||
version: 5.28.4(react@18.2.0)
|
|
||||||
react:
|
|
||||||
specifier: ^18.2.0
|
|
||||||
version: 18.2.0
|
|
||||||
sonner:
|
|
||||||
specifier: ^1.4.3
|
|
||||||
version: 1.4.3(react-dom@18.2.0)(react@18.2.0)
|
|
||||||
virtua:
|
|
||||||
specifier: ^0.27.5
|
|
||||||
version: 0.27.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.66
|
|
||||||
version: 18.2.66
|
|
||||||
tailwindcss:
|
|
||||||
specifier: ^3.4.1
|
|
||||||
version: 3.4.1
|
|
||||||
typescript:
|
|
||||||
specifier: ^5.4.2
|
|
||||||
version: 5.4.2
|
|
||||||
|
|
||||||
packages/lume-column-default:
|
|
||||||
dependencies:
|
|
||||||
'@lume/ark':
|
|
||||||
specifier: workspace:^
|
|
||||||
version: link:../ark
|
|
||||||
'@lume/icons':
|
|
||||||
specifier: workspace:^
|
|
||||||
version: link:../icons
|
|
||||||
'@lume/ui':
|
|
||||||
specifier: workspace:^
|
|
||||||
version: link:../ui
|
|
||||||
'@lume/utils':
|
|
||||||
specifier: workspace:^
|
|
||||||
version: link:../utils
|
|
||||||
'@tanstack/react-query':
|
|
||||||
specifier: ^5.28.4
|
|
||||||
version: 5.28.4(react@18.2.0)
|
|
||||||
react:
|
|
||||||
specifier: ^18.2.0
|
|
||||||
version: 18.2.0
|
|
||||||
sonner:
|
|
||||||
specifier: ^1.4.3
|
|
||||||
version: 1.4.3(react-dom@18.2.0)(react@18.2.0)
|
|
||||||
virtua:
|
|
||||||
specifier: ^0.27.5
|
|
||||||
version: 0.27.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.66
|
|
||||||
version: 18.2.66
|
|
||||||
tailwindcss:
|
|
||||||
specifier: ^3.4.1
|
|
||||||
version: 3.4.1
|
|
||||||
typescript:
|
|
||||||
specifier: ^5.4.2
|
|
||||||
version: 5.4.2
|
|
||||||
|
|
||||||
packages/lume-column-foryou:
|
|
||||||
dependencies:
|
|
||||||
'@lume/ark':
|
|
||||||
specifier: workspace:^
|
|
||||||
version: link:../ark
|
|
||||||
'@lume/icons':
|
|
||||||
specifier: workspace:^
|
|
||||||
version: link:../icons
|
|
||||||
'@lume/ui':
|
|
||||||
specifier: workspace:^
|
|
||||||
version: link:../ui
|
|
||||||
'@lume/utils':
|
|
||||||
specifier: workspace:^
|
|
||||||
version: link:../utils
|
|
||||||
'@tanstack/react-query':
|
|
||||||
specifier: ^5.28.4
|
|
||||||
version: 5.28.4(react@18.2.0)
|
|
||||||
react:
|
|
||||||
specifier: ^18.2.0
|
|
||||||
version: 18.2.0
|
|
||||||
sonner:
|
|
||||||
specifier: ^1.4.3
|
|
||||||
version: 1.4.3(react-dom@18.2.0)(react@18.2.0)
|
|
||||||
virtua:
|
|
||||||
specifier: ^0.27.5
|
|
||||||
version: 0.27.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.66
|
|
||||||
version: 18.2.66
|
|
||||||
tailwindcss:
|
|
||||||
specifier: ^3.4.1
|
|
||||||
version: 3.4.1
|
|
||||||
typescript:
|
|
||||||
specifier: ^5.4.2
|
|
||||||
version: 5.4.2
|
|
||||||
|
|
||||||
packages/lume-column-global:
|
|
||||||
dependencies:
|
|
||||||
'@lume/ark':
|
|
||||||
specifier: workspace:^
|
|
||||||
version: link:../ark
|
|
||||||
'@lume/icons':
|
|
||||||
specifier: workspace:^
|
|
||||||
version: link:../icons
|
|
||||||
'@lume/ui':
|
|
||||||
specifier: workspace:^
|
|
||||||
version: link:../ui
|
|
||||||
'@lume/utils':
|
|
||||||
specifier: workspace:^
|
|
||||||
version: link:../utils
|
|
||||||
'@tanstack/react-query':
|
|
||||||
specifier: ^5.28.4
|
|
||||||
version: 5.28.4(react@18.2.0)
|
|
||||||
react:
|
|
||||||
specifier: ^18.2.0
|
|
||||||
version: 18.2.0
|
|
||||||
sonner:
|
|
||||||
specifier: ^1.4.3
|
|
||||||
version: 1.4.3(react-dom@18.2.0)(react@18.2.0)
|
|
||||||
virtua:
|
|
||||||
specifier: ^0.27.5
|
|
||||||
version: 0.27.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.66
|
|
||||||
version: 18.2.66
|
|
||||||
tailwindcss:
|
|
||||||
specifier: ^3.4.1
|
|
||||||
version: 3.4.1
|
|
||||||
typescript:
|
|
||||||
specifier: ^5.4.2
|
|
||||||
version: 5.4.2
|
|
||||||
|
|
||||||
packages/lume-column-group:
|
|
||||||
dependencies:
|
|
||||||
'@lume/ark':
|
|
||||||
specifier: workspace:^
|
|
||||||
version: link:../ark
|
|
||||||
'@lume/icons':
|
|
||||||
specifier: workspace:^
|
|
||||||
version: link:../icons
|
|
||||||
'@lume/ui':
|
|
||||||
specifier: workspace:^
|
|
||||||
version: link:../ui
|
|
||||||
'@lume/utils':
|
|
||||||
specifier: workspace:^
|
|
||||||
version: link:../utils
|
|
||||||
'@tanstack/react-query':
|
|
||||||
specifier: ^5.28.4
|
|
||||||
version: 5.28.4(react@18.2.0)
|
|
||||||
react:
|
|
||||||
specifier: ^18.2.0
|
|
||||||
version: 18.2.0
|
|
||||||
sonner:
|
|
||||||
specifier: ^1.4.3
|
|
||||||
version: 1.4.3(react-dom@18.2.0)(react@18.2.0)
|
|
||||||
virtua:
|
|
||||||
specifier: ^0.27.5
|
|
||||||
version: 0.27.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.66
|
|
||||||
version: 18.2.66
|
|
||||||
tailwindcss:
|
|
||||||
specifier: ^3.4.1
|
|
||||||
version: 3.4.1
|
|
||||||
typescript:
|
|
||||||
specifier: ^5.4.2
|
|
||||||
version: 5.4.2
|
|
||||||
|
|
||||||
packages/lume-column-hashtag:
|
|
||||||
dependencies:
|
|
||||||
'@lume/ark':
|
|
||||||
specifier: workspace:^
|
|
||||||
version: link:../ark
|
|
||||||
'@lume/icons':
|
|
||||||
specifier: workspace:^
|
|
||||||
version: link:../icons
|
|
||||||
'@lume/ui':
|
|
||||||
specifier: workspace:^
|
|
||||||
version: link:../ui
|
|
||||||
'@lume/utils':
|
|
||||||
specifier: workspace:^
|
|
||||||
version: link:../utils
|
|
||||||
'@tanstack/react-query':
|
|
||||||
specifier: ^5.28.4
|
|
||||||
version: 5.28.4(react@18.2.0)
|
|
||||||
react:
|
|
||||||
specifier: ^18.2.0
|
|
||||||
version: 18.2.0
|
|
||||||
sonner:
|
|
||||||
specifier: ^1.4.3
|
|
||||||
version: 1.4.3(react-dom@18.2.0)(react@18.2.0)
|
|
||||||
virtua:
|
|
||||||
specifier: ^0.27.5
|
|
||||||
version: 0.27.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.66
|
|
||||||
version: 18.2.66
|
|
||||||
tailwindcss:
|
|
||||||
specifier: ^3.4.1
|
|
||||||
version: 3.4.1
|
|
||||||
typescript:
|
|
||||||
specifier: ^5.4.2
|
|
||||||
version: 5.4.2
|
|
||||||
|
|
||||||
packages/lume-column-thread:
|
|
||||||
dependencies:
|
|
||||||
'@lume/ark':
|
|
||||||
specifier: workspace:^
|
|
||||||
version: link:../ark
|
|
||||||
'@lume/icons':
|
|
||||||
specifier: workspace:^
|
|
||||||
version: link:../icons
|
|
||||||
'@lume/ui':
|
|
||||||
specifier: workspace:^
|
|
||||||
version: link:../ui
|
|
||||||
'@lume/utils':
|
|
||||||
specifier: workspace:^
|
|
||||||
version: link:../utils
|
|
||||||
'@tanstack/react-query':
|
|
||||||
specifier: ^5.28.4
|
|
||||||
version: 5.28.4(react@18.2.0)
|
|
||||||
react:
|
|
||||||
specifier: ^18.2.0
|
|
||||||
version: 18.2.0
|
|
||||||
sonner:
|
|
||||||
specifier: ^1.4.3
|
|
||||||
version: 1.4.3(react-dom@18.2.0)(react@18.2.0)
|
|
||||||
virtua:
|
|
||||||
specifier: ^0.27.5
|
|
||||||
version: 0.27.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.66
|
|
||||||
version: 18.2.66
|
|
||||||
tailwindcss:
|
|
||||||
specifier: ^3.4.1
|
|
||||||
version: 3.4.1
|
|
||||||
typescript:
|
|
||||||
specifier: ^5.4.2
|
|
||||||
version: 5.4.2
|
|
||||||
|
|
||||||
packages/lume-column-timeline:
|
|
||||||
dependencies:
|
|
||||||
'@lume/ark':
|
|
||||||
specifier: workspace:^
|
|
||||||
version: link:../ark
|
|
||||||
'@lume/icons':
|
|
||||||
specifier: workspace:^
|
|
||||||
version: link:../icons
|
|
||||||
'@lume/ui':
|
|
||||||
specifier: workspace:^
|
|
||||||
version: link:../ui
|
|
||||||
'@lume/utils':
|
|
||||||
specifier: workspace:^
|
|
||||||
version: link:../utils
|
|
||||||
'@tanstack/react-query':
|
|
||||||
specifier: ^5.28.4
|
|
||||||
version: 5.28.4(react@18.2.0)
|
|
||||||
react:
|
|
||||||
specifier: ^18.2.0
|
|
||||||
version: 18.2.0
|
|
||||||
sonner:
|
|
||||||
specifier: ^1.4.3
|
|
||||||
version: 1.4.3(react-dom@18.2.0)(react@18.2.0)
|
|
||||||
virtua:
|
|
||||||
specifier: ^0.27.5
|
|
||||||
version: 0.27.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.66
|
|
||||||
version: 18.2.66
|
|
||||||
tailwindcss:
|
|
||||||
specifier: ^3.4.1
|
|
||||||
version: 3.4.1
|
|
||||||
typescript:
|
|
||||||
specifier: ^5.4.2
|
|
||||||
version: 5.4.2
|
|
||||||
|
|
||||||
packages/lume-column-trending-notes:
|
|
||||||
dependencies:
|
|
||||||
'@lume/ark':
|
|
||||||
specifier: workspace:^
|
|
||||||
version: link:../ark
|
|
||||||
'@lume/icons':
|
|
||||||
specifier: workspace:^
|
|
||||||
version: link:../icons
|
|
||||||
'@lume/ui':
|
|
||||||
specifier: workspace:^
|
|
||||||
version: link:../ui
|
|
||||||
'@lume/utils':
|
|
||||||
specifier: workspace:^
|
|
||||||
version: link:../utils
|
|
||||||
'@tanstack/react-query':
|
|
||||||
specifier: ^5.28.4
|
|
||||||
version: 5.28.4(react@18.2.0)
|
|
||||||
react:
|
|
||||||
specifier: ^18.2.0
|
|
||||||
version: 18.2.0
|
|
||||||
sonner:
|
|
||||||
specifier: ^1.4.3
|
|
||||||
version: 1.4.3(react-dom@18.2.0)(react@18.2.0)
|
|
||||||
virtua:
|
|
||||||
specifier: ^0.27.5
|
|
||||||
version: 0.27.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.66
|
|
||||||
version: 18.2.66
|
|
||||||
tailwindcss:
|
|
||||||
specifier: ^3.4.1
|
|
||||||
version: 3.4.1
|
|
||||||
typescript:
|
|
||||||
specifier: ^5.4.2
|
|
||||||
version: 5.4.2
|
|
||||||
|
|
||||||
packages/lume-column-user:
|
|
||||||
dependencies:
|
|
||||||
'@lume/ark':
|
|
||||||
specifier: workspace:^
|
|
||||||
version: link:../ark
|
|
||||||
'@lume/icons':
|
|
||||||
specifier: workspace:^
|
|
||||||
version: link:../icons
|
|
||||||
'@lume/ui':
|
|
||||||
specifier: workspace:^
|
|
||||||
version: link:../ui
|
|
||||||
'@lume/utils':
|
|
||||||
specifier: workspace:^
|
|
||||||
version: link:../utils
|
|
||||||
'@tanstack/react-query':
|
|
||||||
specifier: ^5.28.4
|
|
||||||
version: 5.28.4(react@18.2.0)
|
|
||||||
react:
|
|
||||||
specifier: ^18.2.0
|
|
||||||
version: 18.2.0
|
|
||||||
sonner:
|
|
||||||
specifier: ^1.4.3
|
|
||||||
version: 1.4.3(react-dom@18.2.0)(react@18.2.0)
|
|
||||||
virtua:
|
|
||||||
specifier: ^0.27.5
|
|
||||||
version: 0.27.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.66
|
|
||||||
version: 18.2.66
|
|
||||||
tailwindcss:
|
|
||||||
specifier: ^3.4.1
|
|
||||||
version: 3.4.1
|
|
||||||
typescript:
|
|
||||||
specifier: ^5.4.2
|
|
||||||
version: 5.4.2
|
|
||||||
|
|
||||||
packages/lume-column-waifu:
|
|
||||||
dependencies:
|
|
||||||
'@lume/ark':
|
|
||||||
specifier: workspace:^
|
|
||||||
version: link:../ark
|
|
||||||
'@lume/icons':
|
|
||||||
specifier: workspace:^
|
|
||||||
version: link:../icons
|
|
||||||
'@lume/ui':
|
|
||||||
specifier: workspace:^
|
|
||||||
version: link:../ui
|
|
||||||
'@lume/utils':
|
|
||||||
specifier: workspace:^
|
|
||||||
version: link:../utils
|
|
||||||
'@tanstack/react-query':
|
|
||||||
specifier: ^5.28.4
|
|
||||||
version: 5.28.4(react@18.2.0)
|
|
||||||
react:
|
|
||||||
specifier: ^18.2.0
|
|
||||||
version: 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.66
|
|
||||||
version: 18.2.66
|
|
||||||
tailwindcss:
|
|
||||||
specifier: ^3.4.1
|
|
||||||
version: 3.4.1
|
|
||||||
typescript:
|
|
||||||
specifier: ^5.4.2
|
|
||||||
version: 5.4.2
|
|
||||||
|
|
||||||
packages/tailwindcss:
|
packages/tailwindcss:
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@evilmartians/harmony':
|
'@evilmartians/harmony':
|
||||||
@ -909,8 +409,8 @@ importers:
|
|||||||
specifier: ^12.1.0
|
specifier: ^12.1.0
|
||||||
version: 12.1.0
|
version: 12.1.0
|
||||||
media-chrome:
|
media-chrome:
|
||||||
specifier: ^2.2.5
|
specifier: ^3.0.2
|
||||||
version: 2.2.5
|
version: 3.0.2
|
||||||
minidenticons:
|
minidenticons:
|
||||||
specifier: ^4.2.1
|
specifier: ^4.2.1
|
||||||
version: 4.2.1
|
version: 4.2.1
|
||||||
@ -945,11 +445,11 @@ importers:
|
|||||||
specifier: ^1.1.1
|
specifier: ^1.1.1
|
||||||
version: 1.1.1
|
version: 1.1.1
|
||||||
slate:
|
slate:
|
||||||
specifier: ^0.101.5
|
specifier: ^0.102.0
|
||||||
version: 0.101.5
|
version: 0.102.0
|
||||||
slate-react:
|
slate-react:
|
||||||
specifier: ^0.101.6
|
specifier: ^0.102.0
|
||||||
version: 0.101.6(react-dom@18.2.0)(react@18.2.0)(slate@0.101.5)
|
version: 0.102.0(react-dom@18.2.0)(react@18.2.0)(slate@0.102.0)
|
||||||
sonner:
|
sonner:
|
||||||
specifier: ^1.4.3
|
specifier: ^1.4.3
|
||||||
version: 1.4.3(react-dom@18.2.0)(react@18.2.0)
|
version: 1.4.3(react-dom@18.2.0)(react@18.2.0)
|
||||||
@ -963,8 +463,8 @@ importers:
|
|||||||
specifier: ^10.0.0
|
specifier: ^10.0.0
|
||||||
version: 10.0.0(react@18.2.0)
|
version: 10.0.0(react@18.2.0)
|
||||||
virtua:
|
virtua:
|
||||||
specifier: ^0.27.5
|
specifier: ^0.29.0
|
||||||
version: 0.27.5(react-dom@18.2.0)(react@18.2.0)
|
version: 0.29.0(react-dom@18.2.0)(react@18.2.0)
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@lume/tailwindcss':
|
'@lume/tailwindcss':
|
||||||
specifier: workspace:^
|
specifier: workspace:^
|
||||||
@ -1009,11 +509,11 @@ importers:
|
|||||||
specifier: ^18.2.0
|
specifier: ^18.2.0
|
||||||
version: 18.2.0(react@18.2.0)
|
version: 18.2.0(react@18.2.0)
|
||||||
slate:
|
slate:
|
||||||
specifier: ^0.101.5
|
specifier: ^0.102.0
|
||||||
version: 0.101.5
|
version: 0.102.0
|
||||||
slate-react:
|
slate-react:
|
||||||
specifier: ^0.101.6
|
specifier: ^0.102.0
|
||||||
version: 0.101.6(react-dom@18.2.0)(react@18.2.0)(slate@0.101.5)
|
version: 0.102.0(react-dom@18.2.0)(react@18.2.0)(slate@0.102.0)
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@lume/tsconfig':
|
'@lume/tsconfig':
|
||||||
specifier: workspace:^
|
specifier: workspace:^
|
||||||
@ -1048,8 +548,8 @@ packages:
|
|||||||
'@jridgewell/trace-mapping': 0.3.25
|
'@jridgewell/trace-mapping': 0.3.25
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@astrojs/check@0.4.1(typescript@5.4.2):
|
/@astrojs/check@0.5.9(typescript@5.4.2):
|
||||||
resolution: {integrity: sha512-XEsuU4TlWkgcsvdeessq5mXLXV1fejtxIioCPv/FfhTzb1bDYe2BtLiSBK+rFTyD9Hl686YOas9AGNMJcpoRsw==}
|
resolution: {integrity: sha512-+QsQMtYq4oso+gmilJC9HLmdi0glZ+04V/VyyTTPry7n21jqjX9SfgDpLGxMk5cwPC/vwZMkn6ORGPnkZS/L5w==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
typescript: ^5.0.0
|
typescript: ^5.0.0
|
||||||
@ -5058,8 +4558,8 @@ packages:
|
|||||||
'@types/mdast': 4.0.3
|
'@types/mdast': 4.0.3
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/media-chrome@2.2.5:
|
/media-chrome@3.0.2:
|
||||||
resolution: {integrity: sha512-59peAYFlL9ZlFVkKJmIgIDNMkQr4nauYTwIQhLg3khmGfO6/25VNEI8Yn0aUMLb5IFB2gzjcPmfu1ktfOhQ8Ag==}
|
resolution: {integrity: sha512-PdTKNmQ3JDTfd6MQl53qKANx5iSgu3PMNVWDHd2yzjlnvdJwUUuVr1z0vdutig2vYP0DGhC/QRzotfb6m0FfCw==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/merge-stream@2.0.0:
|
/merge-stream@2.0.0:
|
||||||
@ -6331,8 +5831,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
|
resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/slate-react@0.101.6(react-dom@18.2.0)(react@18.2.0)(slate@0.101.5):
|
/slate-react@0.102.0(react-dom@18.2.0)(react@18.2.0)(slate@0.102.0):
|
||||||
resolution: {integrity: sha512-aMtp9FY127hKWTkCcTBonfKIwKJC2ESPqFdw2o/RuOk3RMQRwsWay8XTOHx8OBGOHanI2fsKaTAPF5zxOLA1Qg==}
|
resolution: {integrity: sha512-SAcFsK5qaOxXjm0hr/t2pvIxfRv6HJGzmWkG58TdH4LdJCsgKS1n6hQOakHPlRVCwPgwvngB6R+t3pPjv8MqwA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: '>=18.2.0'
|
react: '>=18.2.0'
|
||||||
react-dom: '>=18.2.0'
|
react-dom: '>=18.2.0'
|
||||||
@ -6348,12 +5848,12 @@ packages:
|
|||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
react-dom: 18.2.0(react@18.2.0)
|
react-dom: 18.2.0(react@18.2.0)
|
||||||
scroll-into-view-if-needed: 3.1.0
|
scroll-into-view-if-needed: 3.1.0
|
||||||
slate: 0.101.5
|
slate: 0.102.0
|
||||||
tiny-invariant: 1.3.1
|
tiny-invariant: 1.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/slate@0.101.5:
|
/slate@0.102.0:
|
||||||
resolution: {integrity: sha512-ZZt1ia8ayRqxtpILRMi2a4MfdvwdTu64CorxTVq9vNSd0GQ/t3YDkze6wKjdeUtENmBlq5wNIDInZbx38Hfu5Q==}
|
resolution: {integrity: sha512-RT+tHgqOyZVB1oFV9Pv99ajwh4OUCN9p28QWdnDTIzaN/kZxMsHeQN39UNAgtkZTVVVygFqeg7/R2jiptCvfyA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
immer: 10.0.4
|
immer: 10.0.4
|
||||||
is-plain-object: 5.0.0
|
is-plain-object: 5.0.0
|
||||||
@ -7065,8 +6565,8 @@ packages:
|
|||||||
vfile-message: 4.0.2
|
vfile-message: 4.0.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/virtua@0.27.5(react-dom@18.2.0)(react@18.2.0):
|
/virtua@0.29.0(react-dom@18.2.0)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-VeiK2eMCHDmNJvP1GO+DB8rX5ACAxrzFRMGIcqoZK+eqnS25C6lSnuZO4XXLK+RmFkPAoHApMZZTf5ngrpcSMw==}
|
resolution: {integrity: sha512-V7gxQDUGgxe32cLyYhZ+hOxtZLuqKV9icqomE8qAN5HILf1TMuisCEZJVbr+k7GI7K+oOkYdKFiBX23cdXSXmg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: '>=16.14.0'
|
react: '>=16.14.0'
|
||||||
react-dom: '>=16.14.0'
|
react-dom: '>=16.14.0'
|
||||||
|
Loading…
Reference in New Issue
Block a user