summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gui/bun.lock21
-rw-r--r--gui/package.json1
-rw-r--r--gui/src/components/feed/PostList.tsx17
-rw-r--r--gui/src/components/nostr/Feed.tsx115
-rw-r--r--gui/src/components/post/Footer.tsx1
-rw-r--r--gui/src/components/post/Header.tsx10
-rw-r--r--gui/src/components/post/Post.tsx4
-rw-r--r--gui/src/components/post/wrappers/NostrIcon.tsx15
-rw-r--r--gui/src/logic/nostr.ts33
-rw-r--r--gui/src/logic/nostrill.ts3
-rw-r--r--gui/src/pages/Feed.tsx91
-rw-r--r--gui/src/state/state.ts3
-rw-r--r--gui/src/styles/styles.css12
-rw-r--r--gui/src/styles/trill.css12
-rw-r--r--gui/src/types/trill.ts2
15 files changed, 223 insertions, 117 deletions
diff --git a/gui/bun.lock b/gui/bun.lock
index 77fd532..33af6d1 100644
--- a/gui/bun.lock
+++ b/gui/bun.lock
@@ -7,6 +7,7 @@
"@tailwindcss/vite": "^4.1.14",
"@tanstack/react-query": "^5.85.9",
"any-ascii": "^0.3.3",
+ "nostr-tools": "^2.17.2",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-hot-toast": "^2.6.0",
@@ -161,6 +162,12 @@
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
+ "@noble/ciphers": ["@noble/ciphers@0.5.3", "", {}, "sha512-B0+6IIHiqEs3BPMT0hcRmHvEj2QHOLu+uwt+tqDDeVd0oyVzh7BPrDcPjRnV1PV/5LaknXJJQvOuRGR0zQJz+w=="],
+
+ "@noble/curves": ["@noble/curves@1.2.0", "", { "dependencies": { "@noble/hashes": "1.3.2" } }, "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw=="],
+
+ "@noble/hashes": ["@noble/hashes@1.3.1", "", {}, "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA=="],
+
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
"@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="],
@@ -213,6 +220,12 @@
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.52.4", "", { "os": "win32", "cpu": "x64" }, "sha512-bf9PtUa0u8IXDVxzRToFQKsNCRz9qLYfR/MpECxl4mRoWYjAeFjgxj1XdZr2M/GNVpT05p+LgQOHopYDlUu6/w=="],
+ "@scure/base": ["@scure/base@1.1.1", "", {}, "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA=="],
+
+ "@scure/bip32": ["@scure/bip32@1.3.1", "", { "dependencies": { "@noble/curves": "~1.1.0", "@noble/hashes": "~1.3.1", "@scure/base": "~1.1.0" } }, "sha512-osvveYtyzdEVbt3OfwwXFr4P2iVBL5u1Q3q4ONBfDY/UpOuXmOlbgwc1xECEboY8wIays8Yt6onaWMUdUbfl0A=="],
+
+ "@scure/bip39": ["@scure/bip39@1.2.1", "", { "dependencies": { "@noble/hashes": "~1.3.0", "@scure/base": "~1.1.0" } }, "sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg=="],
+
"@tailwindcss/node": ["@tailwindcss/node@4.1.14", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.6.0", "lightningcss": "1.30.1", "magic-string": "^0.30.19", "source-map-js": "^1.2.1", "tailwindcss": "4.1.14" } }, "sha512-hpz+8vFk3Ic2xssIA3e01R6jkmsAhvkQdXlEbRTk6S10xDAtiQiM3FyvZVGsucefq764euO/b8WUW9ysLdThHw=="],
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.14", "", { "dependencies": { "detect-libc": "^2.0.4", "tar": "^7.5.1" }, "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.14", "@tailwindcss/oxide-darwin-arm64": "4.1.14", "@tailwindcss/oxide-darwin-x64": "4.1.14", "@tailwindcss/oxide-freebsd-x64": "4.1.14", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.14", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.14", "@tailwindcss/oxide-linux-arm64-musl": "4.1.14", "@tailwindcss/oxide-linux-x64-gnu": "4.1.14", "@tailwindcss/oxide-linux-x64-musl": "4.1.14", "@tailwindcss/oxide-wasm32-wasi": "4.1.14", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.14", "@tailwindcss/oxide-win32-x64-msvc": "4.1.14" } }, "sha512-23yx+VUbBwCg2x5XWdB8+1lkPajzLmALEfMb51zZUBYaYVPDQvBSD/WYDqiVyBIo2BZFa3yw1Rpy3G2Jp+K0dw=="],
@@ -505,6 +518,10 @@
"node-releases": ["node-releases@2.0.23", "", {}, "sha512-cCmFDMSm26S6tQSDpBCg/NR8NENrVPhAJSf+XbxBG4rPFaaonlEoE9wHQmun+cls499TQGSb7ZyPBRlzgKfpeg=="],
+ "nostr-tools": ["nostr-tools@2.17.2", "", { "dependencies": { "@noble/ciphers": "^0.5.1", "@noble/curves": "1.2.0", "@noble/hashes": "1.3.1", "@scure/base": "1.1.1", "@scure/bip32": "1.3.1", "@scure/bip39": "1.2.1", "nostr-wasm": "0.1.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-B/6rxJ4hyrwEdxsGYUiw2E/+/ZcKuFzQcrSYDHJpYAV0xae344UU2QdL6VHPlbHsBhMufUDU4+h2emZLpIlwLA=="],
+
+ "nostr-wasm": ["nostr-wasm@0.1.0", "", {}, "sha512-78BTryCLcLYv96ONU8Ws3Q1JzjlAt+43pWQhIl86xZmWeegYCNLPml7yQ+gG3vR6V5h4XGj+TxO+SS5dsThQIA=="],
+
"optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
"p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
@@ -621,6 +638,10 @@
"@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="],
+ "@noble/curves/@noble/hashes": ["@noble/hashes@1.3.2", "", {}, "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ=="],
+
+ "@scure/bip32/@noble/curves": ["@noble/curves@1.1.0", "", { "dependencies": { "@noble/hashes": "1.3.1" } }, "sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA=="],
+
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.5.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.5.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ=="],
diff --git a/gui/package.json b/gui/package.json
index 4ddb8b3..9ab8472 100644
--- a/gui/package.json
+++ b/gui/package.json
@@ -13,6 +13,7 @@
"@tailwindcss/vite": "^4.1.14",
"@tanstack/react-query": "^5.85.9",
"any-ascii": "^0.3.3",
+ "nostr-tools": "^2.17.2",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-hot-toast": "^2.6.0",
diff --git a/gui/src/components/feed/PostList.tsx b/gui/src/components/feed/PostList.tsx
index 0d01bd2..12b58b4 100644
--- a/gui/src/components/feed/PostList.tsx
+++ b/gui/src/components/feed/PostList.tsx
@@ -1,10 +1,12 @@
import TrillPost from "@/components/post/Post";
import type { FC } from "@/types/trill";
+import useLocalState from "@/state/state";
// import { useEffect } from "react";
// import { useQueryClient } from "@tanstack/react-query";
// import { toFull } from "../thread/helpers";
function TrillFeed({ data, refetch }: { data: FC; refetch: Function }) {
+ const { profiles } = useLocalState((s) => ({ profiles: s.profiles }));
// const qc = useQueryClient();
// useEffect(() => {
// Object.values(data.feed).forEach((poast) => {
@@ -25,9 +27,18 @@ function TrillFeed({ data, refetch }: { data: FC; refetch: Function }) {
.sort()
.reverse()
.slice(0, 50)
- .map((i) => (
- <TrillPost key={i} poast={data.feed[i]} refetch={refetch} />
- ))}
+ .map((i) => {
+ const poast = data.feed[i];
+ const profile = profiles.get(poast.author);
+ return (
+ <TrillPost
+ key={i}
+ poast={poast}
+ profile={profile}
+ refetch={refetch}
+ />
+ );
+ })}
</>
);
}
diff --git a/gui/src/components/nostr/Feed.tsx b/gui/src/components/nostr/Feed.tsx
new file mode 100644
index 0000000..0e74cea
--- /dev/null
+++ b/gui/src/components/nostr/Feed.tsx
@@ -0,0 +1,115 @@
+import PostList from "@/components/feed/PostList";
+import useLocalState from "@/state/state";
+import spinner from "@/assets/triangles.svg";
+import { useState } from "react";
+import { eventsToFc } from "@/logic/nostrill";
+import Icon from "@/components/Icon";
+import toast from "react-hot-toast";
+
+export default function Nostr() {
+ const { nostrFeed, api, relays } = useLocalState((s) => ({
+ nostrFeed: s.nostrFeed,
+ api: s.api,
+ relays: s.relays,
+ }));
+ console.log({ relays });
+ const [isSyncing, setIsSyncing] = useState(false);
+ const feed = eventsToFc(nostrFeed);
+ console.log({ feed });
+ const refetch = () => feed;
+
+ const handleResync = async () => {
+ if (!api) return;
+
+ setIsSyncing(true);
+ try {
+ await api.syncRelays();
+ toast.success("Nostr feed sync initiated");
+ } catch (error) {
+ toast.error("Failed to sync Nostr feed");
+ console.error("Sync error:", error);
+ } finally {
+ setIsSyncing(false);
+ }
+ };
+
+ if (Object.keys(relays).length === 0)
+ return (
+ <div className="nostr-empty-state">
+ <div className="empty-content">
+ <Icon name="nostr" size={48} color="textMuted" />
+ <h3>No Nostr Relays Set Up</h3>
+ <p>
+ You haven't set any Nostr Relays to sync data from. You can do so in
+ the Settings page.
+ </p>
+ <p>
+ If you don't know of any, we recommend the following public relays:
+ </p>
+ <ul>
+ <li>wss://nos.lol</li>
+ <li>wss://relay.damus.io</li>
+ </ul>
+ </div>
+ </div>
+ );
+ // Show empty state with resync option when no feed data
+ if (!feed || !feed.feed || Object.keys(feed.feed).length === 0) {
+ return (
+ <div className="nostr-empty-state">
+ <div className="empty-content">
+ <Icon name="nostr" size={48} color="textMuted" />
+ <h3>No Nostr Posts</h3>
+ <p>
+ Your Nostr feed appears to be empty. Try syncing with your relays to
+ fetch the latest posts.
+ </p>
+ <button
+ onClick={handleResync}
+ disabled={isSyncing}
+ className="resync-btn"
+ >
+ {isSyncing ? (
+ <>
+ <img src={spinner} alt="Loading" className="btn-spinner" />
+ Syncing...
+ </>
+ ) : (
+ <>
+ <Icon name="settings" size={16} />
+ Sync Relays
+ </>
+ )}
+ </button>
+ </div>
+ </div>
+ );
+ }
+
+ // Show feed with resync button in header
+ return (
+ <div className="nostr-feed">
+ <div className="nostr-header">
+ <div className="feed-info">
+ <h4>Nostr Feed</h4>
+ <span className="post-count">
+ {Object.keys(feed.feed).length} posts
+ </span>
+ </div>
+ <button
+ onClick={handleResync}
+ disabled={isSyncing}
+ className="resync-btn-small"
+ title="Sync with Nostr relays"
+ >
+ {isSyncing ? (
+ <img src={spinner} alt="Loading" className="btn-spinner-small" />
+ ) : (
+ <Icon name="settings" size={16} />
+ )}
+ </button>
+ </div>
+ <PostList data={feed} refetch={refetch} />
+ </div>
+ );
+}
diff --git a/gui/src/components/post/Footer.tsx b/gui/src/components/post/Footer.tsx
index 87f45f3..41752fc 100644
--- a/gui/src/components/post/Footer.tsx
+++ b/gui/src/components/post/Footer.tsx
@@ -41,7 +41,6 @@ function Footer({ poast, refetch }: PostProps) {
// Scroll to top where composer is located
window.scrollTo({ top: 0, behavior: "smooth" });
}
- console.log({ poast });
const childrenCount = poast.children
? poast.children.length
? poast.children.length
diff --git a/gui/src/components/post/Header.tsx b/gui/src/components/post/Header.tsx
index 0dfd5e4..b0822b4 100644
--- a/gui/src/components/post/Header.tsx
+++ b/gui/src/components/post/Header.tsx
@@ -1,11 +1,9 @@
import { date_diff } from "@/logic/utils";
import type { PostProps } from "./Post";
import { useLocation } from "wouter";
-import useLocalState from "@/state/state";
function Header(props: PostProps) {
const [_, navigate] = useLocation();
- const profiles = useLocalState((s) => s.profiles);
- const profile = profiles.get(props.poast.author);
+ const { profile } = props;
// console.log("profile", profile);
// console.log(props.poast.author.length, "length");
function go(e: React.MouseEvent) {
@@ -21,13 +19,11 @@ function Header(props: PostProps) {
const name = profile ? (
profile.name
) : (
- <div className="name cp">
- <p className="p-only">{poast.author}</p>
- </div>
+ <p className="p-only">{poast.author}</p>
);
return (
<header>
- <div className="author flex-align" role="link" onMouseUp={go}>
+ <div className="cp author flex-align name" role="link" onMouseUp={go}>
{name}
</div>
<div role="link" onMouseUp={openThread} className="date">
diff --git a/gui/src/components/post/Post.tsx b/gui/src/components/post/Post.tsx
index 2965040..2d9a09a 100644
--- a/gui/src/components/post/Post.tsx
+++ b/gui/src/components/post/Post.tsx
@@ -22,7 +22,7 @@ export interface PostProps {
profile?: UserProfile;
}
function Post(props: PostProps) {
- console.log("post", props);
+ // console.log("post", props);
const { poast } = props;
if (!poast || poast.contents === null) {
return null;
@@ -60,7 +60,7 @@ function TrillPost(props: PostProps) {
setModal(<ShipModal ship={poast.author} />);
}
const avatar = profile ? (
- <div className="avatar cp" role="link" onMouseUp={openModal}>
+ <div className="avatar sigil cp" role="link" onMouseUp={openModal}>
<img src={profile.picture} />
</div>
) : (
diff --git a/gui/src/components/post/wrappers/NostrIcon.tsx b/gui/src/components/post/wrappers/NostrIcon.tsx
index 30fbfe9..f39d689 100644
--- a/gui/src/components/post/wrappers/NostrIcon.tsx
+++ b/gui/src/components/post/wrappers/NostrIcon.tsx
@@ -2,14 +2,23 @@ import Icon from "@/components/Icon";
import useLocalState from "@/state/state";
import toast from "react-hot-toast";
import type { Poast } from "@/types/trill";
+import { generateNevent } from "@/logic/nostr";
export default function ({ poast }: { poast: Poast }) {
const { relays, api } = useLocalState((s) => ({
relays: s.relays,
api: s.api,
}));
- async function sendToRelay(e: React.MouseEvent) {
+ async function handleClick(e: React.MouseEvent) {
e.stopPropagation();
+ if (poast.event) {
+ const nevent = generateNevent(poast.event);
+ console.log({ nevent });
+ const href = `https://primal.net/e/${nevent}`;
+ window.open(href, "_blank");
+ } else sendToRelay(e);
+ }
+ async function sendToRelay(e: React.MouseEvent) {
//
const urls = Object.keys(relays);
await api!.relayPost(poast.host, poast.id, urls);
@@ -18,8 +27,10 @@ export default function ({ poast }: { poast: Poast }) {
// TODO round up all helpers
return (
- <div className="icon" role="link" onMouseUp={sendToRelay}>
+ <div className="icon" role="link" onMouseUp={handleClick}>
<Icon name="nostr" size={20} title="relay to nostr" />
</div>
);
}
+
+// npub1w8k2hk9kkv653cr4luqmx9tglldpn59vy7yqvlvex2xxmeygt96s4dlh8p
diff --git a/gui/src/logic/nostr.ts b/gui/src/logic/nostr.ts
new file mode 100644
index 0000000..b85047f
--- /dev/null
+++ b/gui/src/logic/nostr.ts
@@ -0,0 +1,33 @@
+// import { generateSecretKey, getPublicKey } from "nostr-tools/pure";
+import * as nip19 from "nostr-tools/nip19";
+import type { Event } from "@/types/nostr";
+
+export function generateNevent(event: Event) {
+ const evp: nip19.EventPointer = {
+ id: event.id,
+ author: event.pubkey,
+ kind: event.kind,
+ };
+ const nev = nip19.neventEncode(evp);
+ return nev;
+}
+
+// let sk = generateSecretKey()
+// let nsec = nip19.nsecEncode(sk)
+// let { type, data } = nip19.decode(nsec)
+// assert(type === 'nsec')
+// assert(data === sk)
+
+// let pk = getPublicKey(generateSecretKey())
+// let npub = nip19.npubEncode(pk)
+// let { type, data } = nip19.decode(npub)
+// assert(type === 'npub')
+// assert(data === pk)
+
+// let pk = getPublicKey(generateSecretKey())
+// let relays = ['wss://relay.nostr.example.mydomain.example.com', 'wss://nostr.banana.com']
+// let nprofile = nip19.nprofileEncode({ pubkey: pk, relays })
+// let { type, data } = nip19.decode(nprofile)
+// assert(type === 'nprofile')
+// assert(data.pubkey === pk)
+// assert(data.relays.length === 2)
diff --git a/gui/src/logic/nostrill.ts b/gui/src/logic/nostrill.ts
index bd5fc9c..97d2156 100644
--- a/gui/src/logic/nostrill.ts
+++ b/gui/src/logic/nostrill.ts
@@ -36,12 +36,13 @@ export function eventToPoast(event: Event): Poast | null {
time: ts,
engagement: engagementBunt,
children: [],
+ event,
};
for (const tag of event.tags) {
const f = tag[0];
if (!f) continue;
const ff = f.toLowerCase();
- console.log("tag", ff);
+ // console.log("tag", ff);
if (ff === "e") {
const [, eventId, _relayURL, marker, _pubkey, ..._] = tag;
// TODO
diff --git a/gui/src/pages/Feed.tsx b/gui/src/pages/Feed.tsx
index ac596dd..02f7b1a 100644
--- a/gui/src/pages/Feed.tsx
+++ b/gui/src/pages/Feed.tsx
@@ -8,10 +8,8 @@ import { useParams } from "wouter";
import spinner from "@/assets/triangles.svg";
import { useState } from "react";
import Composer from "@/components/composer/Composer";
-import Icon from "@/components/Icon";
-import toast from "react-hot-toast";
-import { eventsToFc } from "@/logic/nostrill";
import { ErrorPage } from "@/Router";
+import NostrFeed from "@/components/nostr/Feed";
type FeedType = "global" | "following" | "nostr";
function Loader() {
@@ -59,7 +57,7 @@ function FeedPage({ t }: { t: FeedType }) {
) : active === "following" ? (
<Following />
) : active === "nostr" ? (
- <Nostr />
+ <NostrFeed />
) : null}
</div>
</main>
@@ -102,91 +100,6 @@ function Following() {
</div>
);
}
-function Nostr() {
- const { nostrFeed, api } = useLocalState((s) => ({
- nostrFeed: s.nostrFeed,
- api: s.api,
- }));
- const [isSyncing, setIsSyncing] = useState(false);
- const feed = eventsToFc(nostrFeed);
- console.log({ feed });
- const refetch = () => feed;
-
- const handleResync = async () => {
- if (!api) return;
-
- setIsSyncing(true);
- try {
- await api.syncRelays();
- toast.success("Nostr feed sync initiated");
- } catch (error) {
- toast.error("Failed to sync Nostr feed");
- console.error("Sync error:", error);
- } finally {
- setIsSyncing(false);
- }
- };
-
- // Show empty state with resync option when no feed data
- if (!feed || !feed.feed || Object.keys(feed.feed).length === 0) {
- return (
- <div className="nostr-empty-state">
- <div className="empty-content">
- <Icon name="nostr" size={48} color="textMuted" />
- <h3>No Nostr Posts</h3>
- <p>
- Your Nostr feed appears to be empty. Try syncing with your relays to
- fetch the latest posts.
- </p>
- <button
- onClick={handleResync}
- disabled={isSyncing}
- className="resync-btn"
- >
- {isSyncing ? (
- <>
- <img src={spinner} alt="Loading" className="btn-spinner" />
- Syncing...
- </>
- ) : (
- <>
- <Icon name="settings" size={16} />
- Sync Relays
- </>
- )}
- </button>
- </div>
- </div>
- );
- }
-
- // Show feed with resync button in header
- return (
- <div className="nostr-feed">
- <div className="nostr-header">
- <div className="feed-info">
- <h4>Nostr Feed</h4>
- <span className="post-count">
- {Object.keys(feed.feed).length} posts
- </span>
- </div>
- <button
- onClick={handleResync}
- disabled={isSyncing}
- className="resync-btn-small"
- title="Sync with Nostr relays"
- >
- {isSyncing ? (
- <img src={spinner} alt="Loading" className="btn-spinner-small" />
- ) : (
- <Icon name="settings" size={16} />
- )}
- </button>
- </div>
- <PostList data={feed} refetch={refetch} />
- </div>
- );
-}
export default Loader;
// TODO
diff --git a/gui/src/state/state.ts b/gui/src/state/state.ts
index 9bd5e0e..7d433f4 100644
--- a/gui/src/state/state.ts
+++ b/gui/src/state/state.ts
@@ -90,6 +90,9 @@ export const useStore = creator((set, get) => ({
set({ following });
}
}
+ if ("nostr" in data.fact) {
+ set({ nostrFeed: data.fact.nostr });
+ }
}
});
set({ api });
diff --git a/gui/src/styles/styles.css b/gui/src/styles/styles.css
index c105656..ac3c71b 100644
--- a/gui/src/styles/styles.css
+++ b/gui/src/styles/styles.css
@@ -555,6 +555,13 @@ h6 {
& .author {
flex: unset;
gap: 0;
+ margin: 0.7rem 0.3rem;
+ font-weight: 700;
+ flex-grow: 1;
+
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
& .name {
display: flex;
@@ -566,6 +573,11 @@ h6 {
}
}
+ post-body {
+ max-height: 300px;
+ overflow-y: auto;
+ }
+
& .date {
color: grey;
}
diff --git a/gui/src/styles/trill.css b/gui/src/styles/trill.css
index 0a21ed5..0b1650c 100644
--- a/gui/src/styles/trill.css
+++ b/gui/src/styles/trill.css
@@ -25,10 +25,6 @@
/* min-height: 150px; */
}
-.trill-post .author {
- flex: 1 0 auto;
-}
-
.trill-reply-thread {
border-top: 1px solid var(--text-color);
}
@@ -74,14 +70,6 @@
font-size: 1rem;
}
-.trill-post .p-only {
- margin: 0.7rem 0.3rem;
- font-weight: 700;
-}
-
-.trill-post .p {
- /* margin-top: -5px; */
-}
.trill-post a {
text-decoration: 0;
diff --git a/gui/src/types/trill.ts b/gui/src/types/trill.ts
index 984b1f3..a2fccc2 100644
--- a/gui/src/types/trill.ts
+++ b/gui/src/types/trill.ts
@@ -1,3 +1,4 @@
+import type { Event } from "./nostr";
import type { Ship } from "./urbit";
export type SortugRef = {
@@ -48,6 +49,7 @@ export type Poast = {
engagement: Engagement;
tlonRumor?: boolean;
json?: { origin: ExternalApp; content: string }; // for rumor quoting
+ event?: Event; // for Nostr posts
};
export type FullNode = Omit<Poast, "children"> & {
children: FullFeed;