diff --git a/apps/desktop/src/router.tsx b/apps/desktop/src/router.tsx
index 86dbde34..e2f4c144 100644
--- a/apps/desktop/src/router.tsx
+++ b/apps/desktop/src/router.tsx
@@ -137,6 +137,24 @@ export default function Router() {
},
],
},
+ {
+ path: "activity",
+ async lazy() {
+ const { ActivityScreen } = await import("./routes/activty");
+ return { Component: ActivityScreen };
+ },
+ children: [
+ {
+ path: ":id",
+ async lazy() {
+ const { ActivityIdScreen } = await import(
+ "./routes/activty/id"
+ );
+ return { Component: ActivityIdScreen };
+ },
+ },
+ ],
+ },
{
path: "depot",
children: [
diff --git a/apps/desktop/src/routes/activty/components/activityRepost.tsx b/apps/desktop/src/routes/activty/components/activityRepost.tsx
new file mode 100644
index 00000000..3386ba6d
--- /dev/null
+++ b/apps/desktop/src/routes/activty/components/activityRepost.tsx
@@ -0,0 +1,28 @@
+import { User } from "@lume/ark";
+import { NDKEvent } from "@nostr-dev-kit/ndk";
+import { Link } from "react-router-dom";
+
+export function ActivityRepost({ event }: { event: NDKEvent }) {
+ return (
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/desktop/src/routes/activty/components/activityText.tsx b/apps/desktop/src/routes/activty/components/activityText.tsx
new file mode 100644
index 00000000..8967353c
--- /dev/null
+++ b/apps/desktop/src/routes/activty/components/activityText.tsx
@@ -0,0 +1,28 @@
+import { User } from "@lume/ark";
+import { NDKEvent } from "@nostr-dev-kit/ndk";
+import { Link } from "react-router-dom";
+
+export function ActivityText({ event }: { event: NDKEvent }) {
+ return (
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/desktop/src/routes/activty/components/activityZap.tsx b/apps/desktop/src/routes/activty/components/activityZap.tsx
new file mode 100644
index 00000000..85c9571c
--- /dev/null
+++ b/apps/desktop/src/routes/activty/components/activityZap.tsx
@@ -0,0 +1,33 @@
+import { User } from "@lume/ark";
+import { compactNumber } from "@lume/utils";
+import { NDKEvent, zapInvoiceFromEvent } from "@nostr-dev-kit/ndk";
+import { Link } from "react-router-dom";
+
+export function ActivityZap({ event }: { event: NDKEvent }) {
+ const invoice = zapInvoiceFromEvent(event);
+
+ return (
+
+
+
+
+
+
+
+
+ zapped {compactNumber.format(invoice.amount)} sats
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/desktop/src/routes/activty/components/list.tsx b/apps/desktop/src/routes/activty/components/list.tsx
new file mode 100644
index 00000000..8640264d
--- /dev/null
+++ b/apps/desktop/src/routes/activty/components/list.tsx
@@ -0,0 +1,95 @@
+import { useArk } from "@lume/ark";
+import { LoaderIcon } from "@lume/icons";
+import { FETCH_LIMIT } from "@lume/utils";
+import { NDKEvent, NDKKind } from "@nostr-dev-kit/ndk";
+import { useInfiniteQuery, useQueryClient } from "@tanstack/react-query";
+import { useCallback, useMemo } from "react";
+import { ActivityRepost } from "./activityRepost";
+import { ActivityText } from "./activityText";
+import { ActivityZap } from "./activityZap";
+
+export function ActivityList() {
+ const ark = useArk();
+ const queryClient = useQueryClient();
+
+ const { data, hasNextPage, isLoading, isFetchingNextPage, fetchNextPage } =
+ useInfiniteQuery({
+ queryKey: ["activity"],
+ initialPageParam: 0,
+ queryFn: async ({
+ signal,
+ pageParam,
+ }: {
+ signal: AbortSignal;
+ pageParam: number;
+ }) => {
+ const events = await ark.getInfiniteEvents({
+ filter: {
+ kinds: [NDKKind.Zap],
+ "#p": [
+ "126103bfddc8df256b6e0abfd7f3797c80dcc4ea88f7c2f87dd4104220b4d65f",
+ ark.account.pubkey,
+ ],
+ },
+ limit: 200,
+ pageParam,
+ signal,
+ });
+
+ return events;
+ },
+ getNextPageParam: (lastPage) => {
+ const lastEvent = lastPage.at(-1);
+ if (!lastEvent) return;
+ return lastEvent.created_at - 1;
+ },
+ initialData: () => {
+ const queryCacheData = queryClient.getQueryState(["activity"])
+ ?.data as NDKEvent[];
+ if (queryCacheData) {
+ return {
+ pageParams: [undefined, 1],
+ pages: [queryCacheData],
+ };
+ }
+ },
+
+ staleTime: 360 * 1000,
+ refetchOnWindowFocus: false,
+ refetchOnMount: false,
+ });
+
+ const allEvents = useMemo(
+ () => (data ? data.pages.flatMap((page) => page) : []),
+ [data],
+ );
+
+ const renderEvenKind = useCallback(
+ (event: NDKEvent) => {
+ if (event.pubkey === ark.account.pubkey) return null;
+ switch (event.kind) {
+ case NDKKind.Text:
+ return ;
+ case NDKKind.Repost:
+ return ;
+ case NDKKind.Zap:
+ return ;
+ default:
+ return ;
+ }
+ },
+ [data],
+ );
+
+ return (
+
+ {isLoading ? (
+
+
+
+ ) : (
+ allEvents.map((event) => renderEvenKind(event))
+ )}
+
+ );
+}
diff --git a/apps/desktop/src/routes/activty/components/rootNote.tsx b/apps/desktop/src/routes/activty/components/rootNote.tsx
new file mode 100644
index 00000000..5ba7e589
--- /dev/null
+++ b/apps/desktop/src/routes/activty/components/rootNote.tsx
@@ -0,0 +1,40 @@
+import { Note, useEvent } from "@lume/ark";
+
+export function ActivityRootNote({ eventId }: { eventId: string }) {
+ const { isLoading, isError, data } = useEvent(eventId);
+
+ if (isLoading) {
+ return (
+
+ );
+ }
+
+ if (isError) {
+ return (
+
+
+ Failed to fetch event
+
+
+ );
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/desktop/src/routes/activty/components/singleRepost.tsx b/apps/desktop/src/routes/activty/components/singleRepost.tsx
new file mode 100644
index 00000000..f975d3aa
--- /dev/null
+++ b/apps/desktop/src/routes/activty/components/singleRepost.tsx
@@ -0,0 +1,33 @@
+import { User } from "@lume/ark";
+import { NDKEvent } from "@nostr-dev-kit/ndk";
+import { ActivityRootNote } from "./rootNote";
+
+export function ActivitySingleRepost({ event }: { event: NDKEvent }) {
+ const repostId = event.tags.find((el) => el[0] === "e")[1];
+
+ return (
+
+
+
Boost
+
+ @ Someone has reposted to your note
+
+
+
+
+ );
+}
diff --git a/apps/desktop/src/routes/activty/components/singleText.tsx b/apps/desktop/src/routes/activty/components/singleText.tsx
new file mode 100644
index 00000000..328f7670
--- /dev/null
+++ b/apps/desktop/src/routes/activty/components/singleText.tsx
@@ -0,0 +1,53 @@
+import { Note, useArk } from "@lume/ark";
+import { NDKEvent } from "@nostr-dev-kit/ndk";
+import { ActivityRootNote } from "./rootNote";
+
+export function ActivitySingleText({ event }: { event: NDKEvent }) {
+ const ark = useArk();
+ const thread = ark.getEventThread({
+ content: event.content,
+ tags: event.tags,
+ });
+
+ return (
+
+
+
+ Conversation
+
+
+ @ Someone has replied to your note
+
+
+
+
+ {thread ? (
+
+ ) : null}
+
+
+
+
+ );
+}
diff --git a/apps/desktop/src/routes/activty/components/singleZap.tsx b/apps/desktop/src/routes/activty/components/singleZap.tsx
new file mode 100644
index 00000000..59e25c42
--- /dev/null
+++ b/apps/desktop/src/routes/activty/components/singleZap.tsx
@@ -0,0 +1,39 @@
+import { User } from "@lume/ark";
+import { compactNumber } from "@lume/utils";
+import { NDKEvent, zapInvoiceFromEvent } from "@nostr-dev-kit/ndk";
+import { ActivityRootNote } from "./rootNote";
+
+export function ActivitySingleZap({ event }: { event: NDKEvent }) {
+ const zapEventId = event.tags.find((el) => el[0] === "e")[1];
+ const invoice = zapInvoiceFromEvent(event);
+
+ return (
+
+
+
+ Conversation
+
+
+ @ Someone has replied to your note
+
+
+
+
+
+
+
+
+
+
+
+
+ Zap you {compactNumber.format(invoice.amount)} sats for
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/desktop/src/routes/activty/id.tsx b/apps/desktop/src/routes/activty/id.tsx
new file mode 100644
index 00000000..53350b55
--- /dev/null
+++ b/apps/desktop/src/routes/activty/id.tsx
@@ -0,0 +1,27 @@
+import { useEvent } from "@lume/ark";
+import { LoaderIcon } from "@lume/icons";
+import { NDKKind } from "@nostr-dev-kit/ndk";
+import { useParams } from "react-router-dom";
+import { ActivitySingleRepost } from "./components/singleRepost";
+import { ActivitySingleText } from "./components/singleText";
+import { ActivitySingleZap } from "./components/singleZap";
+
+export function ActivityIdScreen() {
+ const { id } = useParams();
+ const { isLoading, data } = useEvent(id);
+
+ if (isLoading || !data) {
+ return (
+
+
+
+ );
+ }
+
+ if (data.kind === NDKKind.Text) return ;
+ if (data.kind === NDKKind.Zap) return ;
+ if (data.kind === NDKKind.Repost)
+ return ;
+
+ return ;
+}
diff --git a/apps/desktop/src/routes/activty/index.tsx b/apps/desktop/src/routes/activty/index.tsx
new file mode 100644
index 00000000..a204cd13
--- /dev/null
+++ b/apps/desktop/src/routes/activty/index.tsx
@@ -0,0 +1,18 @@
+import { Outlet } from "react-router-dom";
+import { ActivityList } from "./components/list";
+
+export function ActivityScreen() {
+ return (
+
+ );
+}
diff --git a/packages/ark/src/components/note/mentions/note.tsx b/packages/ark/src/components/note/mentions/note.tsx
index 49de8d79..72b9bf94 100644
--- a/packages/ark/src/components/note/mentions/note.tsx
+++ b/packages/ark/src/components/note/mentions/note.tsx
@@ -34,7 +34,7 @@ export const MentionNote = memo(function MentionNote({
return (
-
+
diff --git a/packages/lume-column-activity/package.json b/packages/lume-column-activity/package.json
new file mode 100644
index 00000000..f2970ea7
--- /dev/null
+++ b/packages/lume-column-activity/package.json
@@ -0,0 +1,26 @@
+{
+ "name": "@columns/activity",
+ "version": "0.0.0",
+ "private": true,
+ "main": "./src/index.tsx",
+ "dependencies": {
+ "@lume/ark": "workspace:^",
+ "@lume/icons": "workspace:^",
+ "@lume/ui": "workspace:^",
+ "@lume/utils": "workspace:^",
+ "@nostr-dev-kit/ndk": "^2.3.2",
+ "@tanstack/react-query": "^5.17.9",
+ "react": "^18.2.0",
+ "react-router-dom": "^6.21.2",
+ "sonner": "^1.3.1",
+ "virtua": "^0.20.4"
+ },
+ "devDependencies": {
+ "@lume/tailwindcss": "workspace:^",
+ "@lume/tsconfig": "workspace:^",
+ "@lume/types": "workspace:^",
+ "@types/react": "^18.2.47",
+ "tailwind": "^4.0.0",
+ "typescript": "^5.3.3"
+ }
+}
diff --git a/packages/lume-column-activity/src/index.tsx b/packages/lume-column-activity/src/index.tsx
new file mode 100644
index 00000000..e69de29b
diff --git a/packages/lume-column-activity/tailwind.config.js b/packages/lume-column-activity/tailwind.config.js
new file mode 100644
index 00000000..49c48c7a
--- /dev/null
+++ b/packages/lume-column-activity/tailwind.config.js
@@ -0,0 +1,8 @@
+import sharedConfig from "@lume/tailwindcss";
+
+const config = {
+ content: ["./src/**/*.{js,ts,jsx,tsx}"],
+ presets: [sharedConfig],
+};
+
+export default config;
diff --git a/packages/lume-column-activity/tsconfig.json b/packages/lume-column-activity/tsconfig.json
new file mode 100644
index 00000000..34a32891
--- /dev/null
+++ b/packages/lume-column-activity/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "extends": "@lume/tsconfig/base.json",
+ "compilerOptions": {
+ "outDir": "dist"
+ },
+ "include": ["src"],
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/packages/lume-column-timeline/src/home.tsx b/packages/lume-column-timeline/src/home.tsx
index 20d2d1ab..9e10fcf1 100644
--- a/packages/lume-column-timeline/src/home.tsx
+++ b/packages/lume-column-timeline/src/home.tsx
@@ -59,6 +59,7 @@ export function HomeRoute({ colKey }: { colKey: string }) {
},
staleTime: 120 * 1000,
refetchOnWindowFocus: false,
+ refetchOnMount: false,
});
const allEvents = useMemo(
diff --git a/packages/ui/src/layouts/app.tsx b/packages/ui/src/layouts/app.tsx
index cde1ea4f..584101d1 100644
--- a/packages/ui/src/layouts/app.tsx
+++ b/packages/ui/src/layouts/app.tsx
@@ -1,7 +1,6 @@
import { type Platform } from "@tauri-apps/plugin-os";
import { Outlet } from "react-router-dom";
import { twMerge } from "tailwind-merge";
-import { Activity } from "../activity/column";
import { Editor } from "../editor/column";
import { Navigation } from "../navigation";
import { WindowTitleBar } from "../titlebar";
@@ -22,7 +21,6 @@ export function AppLayout({ platform }: { platform: Platform }) {
-
diff --git a/packages/ui/src/navigation.tsx b/packages/ui/src/navigation.tsx
index 9840c34a..28c146ad 100644
--- a/packages/ui/src/navigation.tsx
+++ b/packages/ui/src/navigation.tsx
@@ -3,17 +3,13 @@ import {
BellIcon,
ComposeFilledIcon,
ComposeIcon,
- DepotFilledIcon,
- DepotIcon,
HomeFilledIcon,
HomeIcon,
NwcIcon,
- RelayFilledIcon,
- RelayIcon,
SettingsFilledIcon,
SettingsIcon,
} from "@lume/icons";
-import { activityAtom, cn, editorAtom } from "@lume/utils";
+import { cn, editorAtom } from "@lume/utils";
import { useAtom } from "jotai";
import { useHotkeys } from "react-hotkeys-hook";
import { NavLink } from "react-router-dom";
@@ -21,8 +17,6 @@ import { ActiveAccount } from "./account/active";
export function Navigation() {
const [isEditorOpen, setIsEditorOpen] = useAtom(editorAtom);
- const [isActvityOpen, setIsActvityOpen] = useAtom(activityAtom);
-
useHotkeys("meta+n", () => setIsEditorOpen((state) => !state), []);
return (
@@ -32,10 +26,7 @@ export function Navigation() {
)}
-
-
+ {({ isActive }) => (
+
+ {isActive ? (
+
+ ) : (
+
+ )}
+
+ )}
+