diff options
| -rw-r--r-- | gui/bun.lock | 21 | ||||
| -rw-r--r-- | gui/package.json | 1 | ||||
| -rw-r--r-- | gui/src/components/feed/PostList.tsx | 17 | ||||
| -rw-r--r-- | gui/src/components/nostr/Feed.tsx | 115 | ||||
| -rw-r--r-- | gui/src/components/post/Footer.tsx | 1 | ||||
| -rw-r--r-- | gui/src/components/post/Header.tsx | 10 | ||||
| -rw-r--r-- | gui/src/components/post/Post.tsx | 4 | ||||
| -rw-r--r-- | gui/src/components/post/wrappers/NostrIcon.tsx | 15 | ||||
| -rw-r--r-- | gui/src/logic/nostr.ts | 33 | ||||
| -rw-r--r-- | gui/src/logic/nostrill.ts | 3 | ||||
| -rw-r--r-- | gui/src/pages/Feed.tsx | 91 | ||||
| -rw-r--r-- | gui/src/state/state.ts | 3 | ||||
| -rw-r--r-- | gui/src/styles/styles.css | 12 | ||||
| -rw-r--r-- | gui/src/styles/trill.css | 12 | ||||
| -rw-r--r-- | gui/src/types/trill.ts | 2 |
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; |
