mirror of
https://github.com/lumehq/lume.git
synced 2025-03-18 05:41:53 +01: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-dom": "^18.2.0",
|
||||
"react-i18next": "^14.1.0",
|
||||
"slate": "^0.101.5",
|
||||
"slate-react": "^0.101.6",
|
||||
"slate": "^0.102.0",
|
||||
"slate-react": "^0.102.0",
|
||||
"sonner": "^1.4.3",
|
||||
"virtua": "^0.27.5"
|
||||
"virtua": "^0.29.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@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"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/check": "^0.4.1",
|
||||
"@astrojs/check": "^0.5.9",
|
||||
"@astrojs/tailwind": "^5.1.0",
|
||||
"@fontsource/geist-mono": "^5.0.2",
|
||||
"astro": "^4.5.5",
|
||||
|
@ -17,7 +17,7 @@
|
||||
"@tanstack/react-query": "^5.28.4",
|
||||
"@tanstack/react-router": "^1.20.0",
|
||||
"get-urls": "^12.1.0",
|
||||
"media-chrome": "^2.2.5",
|
||||
"media-chrome": "^3.0.2",
|
||||
"minidenticons": "^4.2.1",
|
||||
"nanoid": "^5.0.6",
|
||||
"qrcode.react": "^3.1.0",
|
||||
@ -28,7 +28,7 @@
|
||||
"react-string-replace": "^1.1.1",
|
||||
"sonner": "^1.4.3",
|
||||
"string-strip-html": "^13.4.6",
|
||||
"virtua": "^0.27.5"
|
||||
"virtua": "^0.29.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@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 {
|
||||
autoupdate: boolean;
|
||||
nsecbunker: boolean;
|
||||
@ -88,11 +86,6 @@ export interface LumeColumn {
|
||||
description?: string;
|
||||
author?: string;
|
||||
logo?: string;
|
||||
x?: number;
|
||||
y?: number;
|
||||
width?: number;
|
||||
height?: number;
|
||||
window?: Webview;
|
||||
}
|
||||
|
||||
export interface Opengraph {
|
||||
|
@ -22,7 +22,7 @@
|
||||
"@tanstack/react-router": "^1.20.0",
|
||||
"framer-motion": "^11.0.14",
|
||||
"get-urls": "^12.1.0",
|
||||
"media-chrome": "^2.2.5",
|
||||
"media-chrome": "^3.0.2",
|
||||
"minidenticons": "^4.2.1",
|
||||
"nanoid": "^5.0.6",
|
||||
"qrcode.react": "^3.1.0",
|
||||
@ -34,13 +34,13 @@
|
||||
"react-hotkeys-hook": "^4.5.0",
|
||||
"react-i18next": "^14.1.0",
|
||||
"react-string-replace": "^1.1.1",
|
||||
"slate": "^0.101.5",
|
||||
"slate-react": "^0.101.6",
|
||||
"slate": "^0.102.0",
|
||||
"slate-react": "^0.102.0",
|
||||
"sonner": "^1.4.3",
|
||||
"string-strip-html": "^13.4.6",
|
||||
"uqr": "^0.1.2",
|
||||
"use-debounce": "^10.0.0",
|
||||
"virtua": "^0.27.5"
|
||||
"virtua": "^0.29.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lume/tailwindcss": "workspace:^",
|
||||
|
@ -7,6 +7,7 @@ import {
|
||||
import { Webview } from "@tauri-apps/api/webview";
|
||||
import { LumeColumn } from "@lume/types";
|
||||
import { useDebouncedCallback } from "use-debounce";
|
||||
import { type UnlistenFn } from "@tauri-apps/api/event";
|
||||
|
||||
export function Column({
|
||||
column,
|
||||
@ -19,6 +20,7 @@ export function Column({
|
||||
const childWindow = useRef<Webview>(null);
|
||||
const divRef = useRef<HTMLDivElement>(null);
|
||||
const initialRect = useRef<DOMRect>(null);
|
||||
const unlisten = useRef<UnlistenFn>(null);
|
||||
const handleResize = useDebouncedCallback(() => {
|
||||
const newRect = divRef.current.getBoundingClientRect();
|
||||
if (initialRect.current.height !== newRect.height) {
|
||||
@ -26,16 +28,12 @@ export function Column({
|
||||
new LogicalSize(newRect.width, newRect.height),
|
||||
);
|
||||
}
|
||||
}, 800);
|
||||
}, 500);
|
||||
|
||||
const trackResize = useCallback(async () => {
|
||||
const unlisten = await mainWindow.onResized(() => {
|
||||
unlisten.current = await mainWindow.onResized(() => {
|
||||
handleResize();
|
||||
});
|
||||
|
||||
return () => {
|
||||
if (unlisten) unlisten();
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
@ -52,27 +50,27 @@ export function Column({
|
||||
if (!divRef.current) return;
|
||||
if (childWindow.current) return;
|
||||
|
||||
// get element dimension
|
||||
const rect = divRef.current.getBoundingClientRect();
|
||||
const name = column.name.toLowerCase().replace(/\W/g, "");
|
||||
|
||||
// create new webview
|
||||
initialRect.current = rect;
|
||||
childWindow.current = new Webview(
|
||||
mainWindow,
|
||||
column.name.toLowerCase().replace(/\W/g, ""),
|
||||
{
|
||||
url: column.content,
|
||||
x: rect.x,
|
||||
y: rect.y,
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
transparent: true,
|
||||
userAgent: "Lume/4.0",
|
||||
},
|
||||
);
|
||||
childWindow.current = new Webview(mainWindow, name, {
|
||||
url: column.content,
|
||||
x: rect.x,
|
||||
y: rect.y,
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
transparent: true,
|
||||
userAgent: "Lume/4.0",
|
||||
});
|
||||
|
||||
// track window resize event
|
||||
trackResize();
|
||||
|
||||
return () => {
|
||||
if (unlisten.current) unlisten.current();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
@ -15,8 +15,8 @@
|
||||
"nostr-tools": "^2.3.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"slate": "^0.101.5",
|
||||
"slate-react": "^0.101.6"
|
||||
"slate": "^0.102.0",
|
||||
"slate-react": "^0.102.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lume/tsconfig": "workspace:^",
|
||||
|
570
pnpm-lock.yaml
generated
570
pnpm-lock.yaml
generated
@ -121,17 +121,17 @@ importers:
|
||||
specifier: ^14.1.0
|
||||
version: 14.1.0(i18next@23.10.1)(react-dom@18.2.0)(react@18.2.0)
|
||||
slate:
|
||||
specifier: ^0.101.5
|
||||
version: 0.101.5
|
||||
specifier: ^0.102.0
|
||||
version: 0.102.0
|
||||
slate-react:
|
||||
specifier: ^0.101.6
|
||||
version: 0.101.6(react-dom@18.2.0)(react@18.2.0)(slate@0.101.5)
|
||||
specifier: ^0.102.0
|
||||
version: 0.102.0(react-dom@18.2.0)(react@18.2.0)(slate@0.102.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)
|
||||
specifier: ^0.29.0
|
||||
version: 0.29.0(react-dom@18.2.0)(react@18.2.0)
|
||||
devDependencies:
|
||||
'@lume/tailwindcss':
|
||||
specifier: workspace:^
|
||||
@ -182,8 +182,8 @@ importers:
|
||||
apps/web:
|
||||
dependencies:
|
||||
'@astrojs/check':
|
||||
specifier: ^0.4.1
|
||||
version: 0.4.1(typescript@5.4.2)
|
||||
specifier: ^0.5.9
|
||||
version: 0.5.9(typescript@5.4.2)
|
||||
'@astrojs/tailwind':
|
||||
specifier: ^5.1.0
|
||||
version: 5.1.0(astro@4.5.5)(tailwindcss@3.4.1)
|
||||
@ -255,8 +255,8 @@ importers:
|
||||
specifier: ^12.1.0
|
||||
version: 12.1.0
|
||||
media-chrome:
|
||||
specifier: ^2.2.5
|
||||
version: 2.2.5
|
||||
specifier: ^3.0.2
|
||||
version: 3.0.2
|
||||
minidenticons:
|
||||
specifier: ^4.2.1
|
||||
version: 4.2.1
|
||||
@ -288,8 +288,8 @@ importers:
|
||||
specifier: ^13.4.6
|
||||
version: 13.4.6
|
||||
virtua:
|
||||
specifier: ^0.27.5
|
||||
version: 0.27.5(react-dom@18.2.0)(react@18.2.0)
|
||||
specifier: ^0.29.0
|
||||
version: 0.29.0(react-dom@18.2.0)(react@18.2.0)
|
||||
devDependencies:
|
||||
'@lume/tailwindcss':
|
||||
specifier: workspace:^
|
||||
@ -326,506 +326,6 @@ importers:
|
||||
specifier: ^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:
|
||||
devDependencies:
|
||||
'@evilmartians/harmony':
|
||||
@ -909,8 +409,8 @@ importers:
|
||||
specifier: ^12.1.0
|
||||
version: 12.1.0
|
||||
media-chrome:
|
||||
specifier: ^2.2.5
|
||||
version: 2.2.5
|
||||
specifier: ^3.0.2
|
||||
version: 3.0.2
|
||||
minidenticons:
|
||||
specifier: ^4.2.1
|
||||
version: 4.2.1
|
||||
@ -945,11 +445,11 @@ importers:
|
||||
specifier: ^1.1.1
|
||||
version: 1.1.1
|
||||
slate:
|
||||
specifier: ^0.101.5
|
||||
version: 0.101.5
|
||||
specifier: ^0.102.0
|
||||
version: 0.102.0
|
||||
slate-react:
|
||||
specifier: ^0.101.6
|
||||
version: 0.101.6(react-dom@18.2.0)(react@18.2.0)(slate@0.101.5)
|
||||
specifier: ^0.102.0
|
||||
version: 0.102.0(react-dom@18.2.0)(react@18.2.0)(slate@0.102.0)
|
||||
sonner:
|
||||
specifier: ^1.4.3
|
||||
version: 1.4.3(react-dom@18.2.0)(react@18.2.0)
|
||||
@ -963,8 +463,8 @@ importers:
|
||||
specifier: ^10.0.0
|
||||
version: 10.0.0(react@18.2.0)
|
||||
virtua:
|
||||
specifier: ^0.27.5
|
||||
version: 0.27.5(react-dom@18.2.0)(react@18.2.0)
|
||||
specifier: ^0.29.0
|
||||
version: 0.29.0(react-dom@18.2.0)(react@18.2.0)
|
||||
devDependencies:
|
||||
'@lume/tailwindcss':
|
||||
specifier: workspace:^
|
||||
@ -1009,11 +509,11 @@ importers:
|
||||
specifier: ^18.2.0
|
||||
version: 18.2.0(react@18.2.0)
|
||||
slate:
|
||||
specifier: ^0.101.5
|
||||
version: 0.101.5
|
||||
specifier: ^0.102.0
|
||||
version: 0.102.0
|
||||
slate-react:
|
||||
specifier: ^0.101.6
|
||||
version: 0.101.6(react-dom@18.2.0)(react@18.2.0)(slate@0.101.5)
|
||||
specifier: ^0.102.0
|
||||
version: 0.102.0(react-dom@18.2.0)(react@18.2.0)(slate@0.102.0)
|
||||
devDependencies:
|
||||
'@lume/tsconfig':
|
||||
specifier: workspace:^
|
||||
@ -1048,8 +548,8 @@ packages:
|
||||
'@jridgewell/trace-mapping': 0.3.25
|
||||
dev: false
|
||||
|
||||
/@astrojs/check@0.4.1(typescript@5.4.2):
|
||||
resolution: {integrity: sha512-XEsuU4TlWkgcsvdeessq5mXLXV1fejtxIioCPv/FfhTzb1bDYe2BtLiSBK+rFTyD9Hl686YOas9AGNMJcpoRsw==}
|
||||
/@astrojs/check@0.5.9(typescript@5.4.2):
|
||||
resolution: {integrity: sha512-+QsQMtYq4oso+gmilJC9HLmdi0glZ+04V/VyyTTPry7n21jqjX9SfgDpLGxMk5cwPC/vwZMkn6ORGPnkZS/L5w==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
typescript: ^5.0.0
|
||||
@ -5058,8 +4558,8 @@ packages:
|
||||
'@types/mdast': 4.0.3
|
||||
dev: false
|
||||
|
||||
/media-chrome@2.2.5:
|
||||
resolution: {integrity: sha512-59peAYFlL9ZlFVkKJmIgIDNMkQr4nauYTwIQhLg3khmGfO6/25VNEI8Yn0aUMLb5IFB2gzjcPmfu1ktfOhQ8Ag==}
|
||||
/media-chrome@3.0.2:
|
||||
resolution: {integrity: sha512-PdTKNmQ3JDTfd6MQl53qKANx5iSgu3PMNVWDHd2yzjlnvdJwUUuVr1z0vdutig2vYP0DGhC/QRzotfb6m0FfCw==}
|
||||
dev: false
|
||||
|
||||
/merge-stream@2.0.0:
|
||||
@ -6331,8 +5831,8 @@ packages:
|
||||
resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
|
||||
dev: false
|
||||
|
||||
/slate-react@0.101.6(react-dom@18.2.0)(react@18.2.0)(slate@0.101.5):
|
||||
resolution: {integrity: sha512-aMtp9FY127hKWTkCcTBonfKIwKJC2ESPqFdw2o/RuOk3RMQRwsWay8XTOHx8OBGOHanI2fsKaTAPF5zxOLA1Qg==}
|
||||
/slate-react@0.102.0(react-dom@18.2.0)(react@18.2.0)(slate@0.102.0):
|
||||
resolution: {integrity: sha512-SAcFsK5qaOxXjm0hr/t2pvIxfRv6HJGzmWkG58TdH4LdJCsgKS1n6hQOakHPlRVCwPgwvngB6R+t3pPjv8MqwA==}
|
||||
peerDependencies:
|
||||
react: '>=18.2.0'
|
||||
react-dom: '>=18.2.0'
|
||||
@ -6348,12 +5848,12 @@ packages:
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
scroll-into-view-if-needed: 3.1.0
|
||||
slate: 0.101.5
|
||||
slate: 0.102.0
|
||||
tiny-invariant: 1.3.1
|
||||
dev: false
|
||||
|
||||
/slate@0.101.5:
|
||||
resolution: {integrity: sha512-ZZt1ia8ayRqxtpILRMi2a4MfdvwdTu64CorxTVq9vNSd0GQ/t3YDkze6wKjdeUtENmBlq5wNIDInZbx38Hfu5Q==}
|
||||
/slate@0.102.0:
|
||||
resolution: {integrity: sha512-RT+tHgqOyZVB1oFV9Pv99ajwh4OUCN9p28QWdnDTIzaN/kZxMsHeQN39UNAgtkZTVVVygFqeg7/R2jiptCvfyA==}
|
||||
dependencies:
|
||||
immer: 10.0.4
|
||||
is-plain-object: 5.0.0
|
||||
@ -7065,8 +6565,8 @@ packages:
|
||||
vfile-message: 4.0.2
|
||||
dev: false
|
||||
|
||||
/virtua@0.27.5(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-VeiK2eMCHDmNJvP1GO+DB8rX5ACAxrzFRMGIcqoZK+eqnS25C6lSnuZO4XXLK+RmFkPAoHApMZZTf5ngrpcSMw==}
|
||||
/virtua@0.29.0(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-V7gxQDUGgxe32cLyYhZ+hOxtZLuqKV9icqomE8qAN5HILf1TMuisCEZJVbr+k7GI7K+oOkYdKFiBX23cdXSXmg==}
|
||||
peerDependencies:
|
||||
react: '>=16.14.0'
|
||||
react-dom: '>=16.14.0'
|
||||
|
Loading…
x
Reference in New Issue
Block a user