summaryrefslogtreecommitdiff
path: root/front/src/components/feed
diff options
context:
space:
mode:
Diffstat (limited to 'front/src/components/feed')
-rw-r--r--front/src/components/feed/Body.tsx174
-rw-r--r--front/src/components/feed/Card.tsx9
-rw-r--r--front/src/components/feed/Composer.tsx52
-rw-r--r--front/src/components/feed/External.tsx41
-rw-r--r--front/src/components/feed/Footer.tsx237
-rw-r--r--front/src/components/feed/Header.tsx33
-rw-r--r--front/src/components/feed/Media.tsx35
-rw-r--r--front/src/components/feed/NostrIcon.tsx22
-rw-r--r--front/src/components/feed/Post.tsx79
-rw-r--r--front/src/components/feed/PostData.tsx160
-rw-r--r--front/src/components/feed/PostList.tsx3
-rw-r--r--front/src/components/feed/Quote.tsx37
-rw-r--r--front/src/components/feed/RP.tsx47
-rw-r--r--front/src/components/feed/Reactions.tsx118
-rw-r--r--front/src/components/feed/StatsModal.tsx106
15 files changed, 2 insertions, 1151 deletions
diff --git a/front/src/components/feed/Body.tsx b/front/src/components/feed/Body.tsx
deleted file mode 100644
index 2f11962..0000000
--- a/front/src/components/feed/Body.tsx
+++ /dev/null
@@ -1,174 +0,0 @@
-import type {
- // TODO ref backend fetching!!
- Reference,
- Block,
- Inline,
- Media as MediaType,
- ExternalContent,
-} from "@/types/trill";
-import crow from "@/assets/icons/crow.svg";
-import type { PostProps } from "./Post";
-import Media from "./Media";
-import JSONContent, { YoutubeSnippet } from "./External";
-import { useLocation } from "wouter";
-import Quote from "./Quote";
-import PostData from "./PostData";
-import Card from "./Card.tsx";
-import type { Ship } from "@/types/urbit.ts";
-
-function Body(props: PostProps) {
- const text = props.poast.contents.filter((c) => {
- return (
- "paragraph" in c ||
- "blockquote" in c ||
- "heading" in c ||
- "codeblock" in c ||
- "list" in c
- );
- });
-
- const media: MediaType[] = props.poast.contents.filter(
- (c): c is MediaType => "media" in c,
- );
-
- const refs = props.poast.contents.filter((c): c is Reference => "ref" in c);
- const json = props.poast.contents.filter(
- (c): c is ExternalContent => "json" in c,
- );
-
- return (
- <div className="trill-post-body body">
- <div className="body-text">
- {text.map((b, i) => (
- <TextBlock key={JSON.stringify(b) + i} block={b} />
- ))}
- </div>
- {media.length > 0 && <Media media={media} />}
- {refs.map((r, i) => (
- <Ref r={r} nest={props.nest || 0} key={JSON.stringify(r) + i} />
- ))}
- <JSONContent content={json} />
- </div>
- );
-}
-export default Body;
-
-function TextBlock({ block }: { block: Block }) {
- const key = JSON.stringify(block);
- return "paragraph" in block ? (
- <div className="trill-post-paragraph">
- {block.paragraph.map((i, ind) => (
- <Inlin key={key + ind} i={i} />
- ))}
- </div>
- ) : "blockquote" in block ? (
- <blockquote>
- {block.blockquote.map((i, ind) => (
- <Inlin key={key + ind} i={i} />
- ))}
- </blockquote>
- ) : "heading" in block ? (
- <Heading string={block.heading.text} num={block.heading.num} />
- ) : "codeblock" in block ? (
- <pre>
- <code className={`language-${block.codeblock.lang}`}>
- {block.codeblock.code}
- </code>
- </pre>
- ) : "list" in block ? (
- block.list.ordered ? (
- <ol>
- {block.list.text.map((i, ind) => (
- <li key={JSON.stringify(i) + ind}>
- <Inlin key={key + ind} i={i} />
- </li>
- ))}
- </ol>
- ) : (
- <ul>
- {block.list.text.map((i, ind) => (
- <li key={JSON.stringify(i) + ind}>
- <Inlin key={JSON.stringify(i) + ind} i={i} />
- </li>
- ))}
- </ul>
- )
- ) : null;
-}
-function Inlin({ i }: { i: Inline }) {
- const [_, navigate] = useLocation();
- function gotoShip(e: React.MouseEvent, ship: Ship) {
- e.stopPropagation();
- navigate(`/feed/${ship}`);
- }
- return "text" in i ? (
- <span>{i.text}</span>
- ) : "italic" in i ? (
- <i>{i.italic}</i>
- ) : "bold" in i ? (
- <strong>{i.bold}</strong>
- ) : "strike" in i ? (
- <span>{i.strike}</span>
- ) : "underline" in i ? (
- <span>{i.underline}</span>
- ) : "sup" in i ? (
- <sup>{i.sup}</sup>
- ) : "sub" in i ? (
- <sub>{i.sub}</sub>
- ) : "ship" in i ? (
- <span
- className="mention"
- role="link"
- onMouseUp={(e) => gotoShip(e, i.ship)}
- >
- {i.ship}
- </span>
- ) : "codespan" in i ? (
- <code>{i.codespan}</code>
- ) : "link" in i ? (
- <LinkParser {...i.link} />
- ) : "break" in i ? (
- <br />
- ) : null;
-}
-
-function LinkParser({ href, show }: { href: string; show: string }) {
- const YOUTUBE_REGEX_1 = /(youtube\.com\/watch\?v=)(\w+)/;
- const YOUTUBE_REGEX_2 = /(youtu\.be\/)([a-zA-Z0-9-_]+)/;
- const m1 = href.match(YOUTUBE_REGEX_1);
- const m2 = href.match(YOUTUBE_REGEX_2);
- const ytb = m1 && m1[2] ? m1[2] : m2 && m2[2] ? m2[2] : "";
- return ytb ? (
- <YoutubeSnippet href={href} id={ytb} />
- ) : (
- <a href={href}>{show}</a>
- );
-}
-function Heading({ string, num }: { string: string; num: number }) {
- return num === 1 ? (
- <h1>{string}</h1>
- ) : num === 2 ? (
- <h2>{string}</h2>
- ) : num === 3 ? (
- <h3>{string}</h3>
- ) : num === 4 ? (
- <h4>{string}</h4>
- ) : num === 5 ? (
- <h5>{string}</h5>
- ) : num === 6 ? (
- <h6>{string}</h6>
- ) : null;
-}
-
-function Ref({ r, nest }: { r: Reference; nest: number }) {
- if (r.ref.type === "nostril") {
- const comp = PostData({
- host: r.ref.ship,
- id: r.ref.path.slice(1),
- nest: nest + 1,
- className: "quote-in-post",
- })(Quote);
- return <Card logo={crow}>{comp}</Card>;
- }
- return <></>;
-}
diff --git a/front/src/components/feed/Card.tsx b/front/src/components/feed/Card.tsx
deleted file mode 100644
index 37f4911..0000000
--- a/front/src/components/feed/Card.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-export default function ({ children, logo, cn}: { cn?: string; logo: string; children: any }) {
- const className = "trill-post-card" + (cn ? ` ${cn}`: "")
- return (
- <div className={className}>
- <img src={logo} alt="" className="trill-post-card-logo" />
- {children}
- </div>
- );
-}
diff --git a/front/src/components/feed/Composer.tsx b/front/src/components/feed/Composer.tsx
deleted file mode 100644
index 27da392..0000000
--- a/front/src/components/feed/Composer.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-import { openLock } from "@/logic/bunts";
-import { HASHTAGS_REGEX } from "@/logic/constants";
-import useLocalState from "@/state/state";
-import type { Poast, SentPoast } from "@/types/trill";
-import Sigil from "@/components/Sigil";
-import { useState } from "react";
-
-function Composer({
- isAnon,
- replying,
-}: {
- isAnon?: boolean;
- replying?: Poast;
-}) {
- const { api, keys } = useLocalState();
- const our = api!.airlock.our!;
- const [input, setInput] = useState(replying ? `${replying}: ` : "");
- async function poast() {
- // TODO
- // const parent = replying ? replying : null;
- // const tokens = tokenize(input);
- // const post: SentPoast = {
- // host: parent ? parent.host : our,
- // author: our,
- // thread: parent ? parent.thread : null,
- // parent: parent ? parent.id : null,
- // contents: input,
- // read: openLock,
- // write: openLock,
- // tags: input.match(HASHTAGS_REGEX) || [],
- // };
- // TODO make it user choosable
- const pubkey = keys[0]!;
- await api!.addPost(pubkey, input);
- }
- const placeHolder = isAnon ? "> be me" : "What's going on in Urbit";
- return (
- <div id="composer">
- <div className="sigil">
- <Sigil patp={our} size={48} />
- </div>
- <input
- value={input}
- onInput={(e) => setInput(e.currentTarget.value)}
- placeholder={placeHolder}
- />
- <button onClick={poast}>Post</button>
- </div>
- );
-}
-
-export default Composer;
diff --git a/front/src/components/feed/External.tsx b/front/src/components/feed/External.tsx
deleted file mode 100644
index 0ea1500..0000000
--- a/front/src/components/feed/External.tsx
+++ /dev/null
@@ -1,41 +0,0 @@
-import type { ExternalContent } from "@/types/trill";
-import youtube from "@/assets/icons/youtube.svg";
-import Card from "./Card";
-
-interface JSONProps {
- content: ExternalContent[];
-}
-
-function JSONContent({ content }: JSONProps) {
- return (
- <>
- {content.map((c, i) => {
- if (!JSON.parse(c.json.content)) return <p key={i}>Error</p>;
- else
- return (
- <p
- key={JSON.stringify(c.json)}
- className="external-content-warning"
- >
- External content from "{c.json.origin}", use
- <a href="https://urbit.org/applications/~sortug/ufa">UFA</a>
- to display.
- </p>
- );
- })}
- </>
- );
-}
-export default JSONContent;
-
-export function YoutubeSnippet({ href, id }: { href: string; id: string }) {
- const thumbnail = `https://i.ytimg.com/vi/${id}/hqdefault.jpg`;
- // todo styiling
- return (
- <Card logo={youtube} cn="youtube-thumbnail">
- <a href={href}>
- <img src={thumbnail} alt="" />
- </a>
- </Card>
- );
-}
diff --git a/front/src/components/feed/Footer.tsx b/front/src/components/feed/Footer.tsx
deleted file mode 100644
index 938a8c7..0000000
--- a/front/src/components/feed/Footer.tsx
+++ /dev/null
@@ -1,237 +0,0 @@
-import type { PostProps } from "./Post";
-import reply from "@/assets/icons/reply.svg";
-import quote from "@/assets/icons/quote.svg";
-import repost from "@/assets/icons/rt.svg";
-import { useState } from "react";
-import useLocalState from "@/state/state";
-import { useLocation } from "wouter";
-import { displayCount } from "@/logic/utils";
-import { TrillReactModal, stringToReact } from "./Reactions";
-import toast from "react-hot-toast";
-import NostrIcon from "./NostrIcon";
-function Footer({ poast, refetch }: PostProps) {
- const [_showMenu, setShowMenu] = useState(false);
- const [location, navigate] = useLocation();
- const [reposting, _setReposting] = useState(false);
- const { api, setComposerData, setModal } = useLocalState();
- const our = api!.airlock.our!;
- function doReply(e: React.MouseEvent) {
- e.stopPropagation();
- setComposerData({ type: "reply", post: { service: "trill", post: poast } });
- navigate("/composer");
- }
- function doQuote(e: React.MouseEvent) {
- e.stopPropagation();
- setComposerData({
- type: "quote",
- post: { service: "trill", post: poast },
- });
- navigate("/composer");
- }
- const childrenCount = poast.children
- ? poast.children.length
- ? poast.children.length
- : Object.keys(poast.children).length
- : 0;
- const myRP = poast.engagement.shared.find((r) => r.pid.ship === our);
- async function cancelRP(e: React.MouseEvent) {
- e.stopPropagation();
- const r = await api!.deletePost(our);
- if (r) toast.success("Repost deleted");
- refetch();
- if (location.includes(poast.id)) navigate("/");
- }
- async function sendRP(e: React.MouseEvent) {
- // TODO update backend because contents are only markdown now
- e.stopPropagation();
- // const c = [
- // {
- // ref: {
- // type: "trill",
- // ship: poast.host,
- // path: `/${poast.id}`,
- // },
- // },
- // ];
- // const post: SentPoast = {
- // host: our,
- // author: our,
- // thread: null,
- // parent: null,
- // contents: input,
- // read: openLock,
- // write: openLock,
- // tags: [], // TODO
- // };
- // const r = await api!.addPost(post, false);
- // setReposting(true);
- // if (r) {
- // setReposting(false);
- // toast.success("Your post was published");
- // }
- }
- function doReact(e: React.MouseEvent) {
- e.stopPropagation();
- const modal = <TrillReactModal poast={poast} />;
- setModal(modal);
- }
- function showReplyCount() {
- if (poast.children[0]) fetchAndShow(); // Flatpoast
- // else {
- // const authors = Object.keys(poast.children).map(
- // (i) => poast.children[i].post.author
- // );
- // setEngagement({ type: "replies", ships: authors }, poast);
- // }
- }
- async function fetchAndShow() {
- // let authors = [];
- // for (let i of poast.children as string[]) {
- // const res = await scrypoastFull(poast.host, i);
- // if (res)
- // authors.push(res.post.author || "deleter");
- // }
- // setEngagement({ type: "replies", ships: authors }, poast);
- }
- function showRepostCount() {
- // const ships = poast.engagement.shared.map((entry) => entry.host);
- // setEngagement({ type: "reposts", ships: ships }, poast);
- }
- function showQuoteCount() {
- // setEngagement({ type: "quotes", quotes: poast.engagement.quoted }, poast);
- }
- function showReactCount() {
- // setEngagement({ type: "reacts", reacts: poast.engagement.reacts }, poast);
- }
-
- const mostCommonReact = Object.values(poast.engagement.reacts).reduce(
- (acc: any, item) => {
- if (!acc.counts[item]) acc.counts[item] = 0;
- acc.counts[item] += 1;
- if (!acc.winner || acc.counts[item] > acc.counts[acc.winner])
- acc.winner = item;
- return acc;
- },
- { counts: {}, winner: "" },
- ).winner;
- const reactIcon = stringToReact(mostCommonReact);
-
- // TODO round up all helpers
-
- return (
- <div className="footer-wrapper post-footer">
- <footer>
- <div className="icon">
- <span role="link" onMouseUp={showReplyCount} className="reply-count">
- {displayCount(childrenCount)}
- </span>
- <img role="link" onMouseUp={doReply} src={reply} alt="" />
- </div>
- <div className="icon">
- <span role="link" onMouseUp={showQuoteCount} className="quote-count">
- {displayCount(poast.engagement.quoted.length)}
- </span>
- <img role="link" onMouseUp={doQuote} src={quote} alt="" />
- </div>
- <div className="icon">
- <span
- role="link"
- onMouseUp={showRepostCount}
- className="repost-count"
- >
- {displayCount(poast.engagement.shared.length)}
- </span>
- {reposting ? (
- <p>...</p>
- ) : myRP ? (
- <img
- role="link"
- className="my-rp"
- onMouseUp={cancelRP}
- src={repost}
- title="cancel repost"
- />
- ) : (
- <img role="link" onMouseUp={sendRP} src={repost} title="repost" />
- )}
- </div>
- <div className="icon" role="link" onMouseUp={doReact}>
- <span
- role="link"
- onMouseUp={showReactCount}
- className="reaction-count"
- >
- {displayCount(Object.keys(poast.engagement.reacts).length)}
- </span>
- {reactIcon}
- </div>
- <NostrIcon poast={poast} />
- </footer>
- </div>
- );
-}
-export default Footer;
-
-// function Menu({
-// poast,
-// setShowMenu,
-// refetch,
-// }: {
-// poast: Poast;
-// setShowMenu: Function;
-// refetch: Function;
-// }) {
-// const ref = useRef<HTMLDivElement>(null);
-// const [location, navigate] = useLocation();
-// // TODO this is a mess and the event still propagates
-// useEffect(() => {
-// const checkIfClickedOutside = (e: any) => {
-// e.stopPropagation();
-// if (ref && ref.current && !ref.current.contains(e.target))
-// setShowMenu(false);
-// };
-// document.addEventListener("mousedown", checkIfClickedOutside);
-// return () => {
-// document.removeEventListener("mousedown", checkIfClickedOutside);
-// };
-// }, []);
-// const { our, setModal, setAlert } = useLocalState();
-// const mine = our === poast.host || our === poast.author;
-// async function doDelete(e: React.MouseEvent) {
-// e.stopPropagation();
-// deletePost(poast.host, poast.id);
-// setAlert("Post deleted");
-// setShowMenu(false);
-// refetch();
-// if (location.includes(poast.id)) navigate("/");
-// }
-// async function copyLink(e: React.MouseEvent) {
-// e.stopPropagation();
-// const link = trillPermalink(poast);
-// await navigator.clipboard.writeText(link);
-// // some alert
-// setShowMenu(false);
-// }
-// function openStats(e: React.MouseEvent) {
-// e.stopPropagation();
-// e.preventDefault();
-// const m = <StatsModal poast={poast} close={() => setModal(null)} />;
-// setModal(m);
-// }
-// return (
-// <div ref={ref} id="post-menu">
-// {/* <p onClick={openShare}>Share to Groups</p> */}
-// <p role="link" onMouseUp={openStats}>
-// See Stats
-// </p>
-// <p role="link" onMouseUp={copyLink}>
-// Permalink
-// </p>
-// {mine && (
-// <p role="link" onMouseUp={doDelete}>
-// Delete Post
-// </p>
-// )}
-// </div>
-// );
-// }
diff --git a/front/src/components/feed/Header.tsx b/front/src/components/feed/Header.tsx
deleted file mode 100644
index 7658bfb..0000000
--- a/front/src/components/feed/Header.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import { date_diff } from "@/logic/utils";
-import type { PostProps } from "./Post";
-import { useLocation } from "wouter";
-function Header(props: PostProps) {
- const [_, navigate] = useLocation();
- function go(e: React.MouseEvent) {
- e.stopPropagation();
- }
- function openThread(e: React.MouseEvent) {
- e.stopPropagation();
- const sel = window.getSelection()?.toString();
- if (!sel) navigate(`/feed/${poast.host}/${poast.id}`);
- }
- const { poast } = props;
- const name = (
- <div className="name cp">
- <p className="p-only">{poast.author}</p>
- </div>
- );
- return (
- <header>
- <div className="author flex-align" role="link" onMouseUp={go}>
- {name}
- </div>
- <div role="link" onMouseUp={openThread} className="date">
- <p title={new Date(poast.time).toLocaleString()}>
- {date_diff(poast.time, "short")}
- </p>
- </div>
- </header>
- );
-}
-export default Header;
diff --git a/front/src/components/feed/Media.tsx b/front/src/components/feed/Media.tsx
deleted file mode 100644
index 04ea156..0000000
--- a/front/src/components/feed/Media.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import type { Media } from "@/types/trill";
-interface Props {
- media: Media[];
-}
-function M({ media }: Props) {
- return (
- <div className="body-media">
- {media.map((m, i) => {
- return "video" in m.media ? (
- <video key={JSON.stringify(m) + i} src={m.media.video} controls />
- ) : "audio" in m.media ? (
- <audio key={JSON.stringify(m) + i} src={m.media.audio} controls />
- ) : "images" in m.media ? (
- <Images key={JSON.stringify(m) + i} urls={m.media.images} />
- ) : null;
- })}
- </div>
- );
-}
-export default M;
-
-function Images({ urls }: { urls: string[] }) {
- return (
- <>
- {urls.map((u, i) => (
- <img
- key={u + i}
- className={`body-img body-img-1-of-${urls.length}`}
- src={u}
- alt=""
- />
- ))}
- </>
- );
-}
diff --git a/front/src/components/feed/NostrIcon.tsx b/front/src/components/feed/NostrIcon.tsx
deleted file mode 100644
index 0c368fb..0000000
--- a/front/src/components/feed/NostrIcon.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import nostrIcon from "@/assets/icons/nostr.svg";
-import useLocalState from "@/state/state";
-import toast from "react-hot-toast";
-import type { Poast } from "@/types/trill";
-export default function ({ poast }: { poast: Poast }) {
- const { relays, api, keys } = useLocalState();
-
- async function sendToRelay(e: React.MouseEvent) {
- e.stopPropagation();
- //
- const urls = Object.keys(relays);
- await api!.relayPost(poast.host, poast.id, urls);
- toast.success("Post relayed");
- }
- // TODO round up all helpers
-
- return (
- <div className="icon" role="link" onMouseUp={sendToRelay}>
- <img role="link" src={nostrIcon} title="repost" />
- </div>
- );
-}
diff --git a/front/src/components/feed/Post.tsx b/front/src/components/feed/Post.tsx
deleted file mode 100644
index 1211a97..0000000
--- a/front/src/components/feed/Post.tsx
+++ /dev/null
@@ -1,79 +0,0 @@
-import type { PostID, Poast, Reference } from "@/types/trill";
-
-import Header from "./Header";
-import Body from "./Body";
-import Footer from "./Footer";
-import { useLocation } from "wouter";
-import useLocalState from "@/state/state";
-import RP from "./RP";
-import ShipModal from "../modals/ShipModal";
-import type { Ship } from "@/types/urbit";
-import Sigil from "../Sigil";
-
-export interface PostProps {
- poast: Poast;
- fake?: boolean;
- rter?: Ship;
- rtat?: number;
- rtid?: PostID;
- nest?: number;
- refetch: Function;
-}
-function Post(props: PostProps) {
- const { poast } = props;
- console.log({ poast });
- if (!poast || poast.contents === null) {
- return null;
- }
- const isRP =
- poast.contents.length === 1 &&
- "ref" in poast.contents[0] &&
- poast.contents[0].ref.type === "trill";
- if (isRP) {
- const ref = (poast.contents[0] as Reference).ref;
- return (
- <RP
- host={ref.ship}
- id={ref.path.slice(1)}
- rter={poast.author}
- rtat={poast.time}
- rtid={poast.id}
- />
- );
- } else return <TrillPost {...props} />;
-}
-export default Post;
-
-function TrillPost(props: PostProps) {
- const { poast, fake } = props;
- const { setModal } = useLocalState();
- const [_, navigate] = useLocation();
- function openThread(_e: React.MouseEvent) {
- const sel = window.getSelection()?.toString();
- if (!sel) navigate(`/feed/${poast.host}/${poast.id}`);
- }
-
- function openModal(e: React.MouseEvent) {
- e.stopPropagation();
- setModal(<ShipModal ship={poast.author} />);
- }
- const avatar = (
- <div className="avatar-w sigil cp" role="link" onMouseUp={openModal}>
- <Sigil patp={poast.author} size={42} />
- </div>
- );
- return (
- <div
- className={`timeline-post trill-post cp`}
- role="link"
- onMouseUp={openThread}
- >
- <div className="left">{avatar}</div>
- <div className="right">
- <Header {...props} />
- <Body {...props} />
- {!fake && <Footer {...props} />}
- </div>
- </div>
- );
-}
diff --git a/front/src/components/feed/PostData.tsx b/front/src/components/feed/PostData.tsx
deleted file mode 100644
index f3c4715..0000000
--- a/front/src/components/feed/PostData.tsx
+++ /dev/null
@@ -1,160 +0,0 @@
-import { useQuery, useQueryClient } from "@tanstack/react-query";
-import spinner from "@/assets/triangles.svg";
-import { useEffect, useRef, useState } from "react";
-import useLocalState from "@/state/state";
-import type { PostID } from "@/types/trill";
-import type { Ship } from "@/types/urbit";
-
-function PostData(props: {
- host: Ship;
- id: PostID;
- rter?: Ship;
- rtat?: number;
- rtid?: PostID;
- nest?: number; // nested quotes
- className?: string;
-}) {
- const { api } = useLocalState();
- const { host, id, nest } = props;
- const [enest, setEnest] = useState(nest);
- useEffect(() => {
- setEnest(nest);
- }, [nest]);
-
- return function (Component: React.ElementType) {
- // const [showNested, setShowNested] = useState(nest <= 3);
- const handleShowNested = (e: React.MouseEvent) => {
- e.stopPropagation();
- setEnest(enest! - 3);
- };
- const [dead, setDead] = useState(false);
- const [denied, setDenied] = useState(false);
- const { isLoading, isError, data, refetch } = useQuery({
- queryKey: ["trill-thread", host, id],
- queryFn: fetchNode,
- });
- const queryClient = useQueryClient();
- const dataRef = useRef(data);
- useEffect(() => {
- dataRef.current = data;
- }, [data]);
-
- async function fetchNode(): Promise<any> {
- const res = await api!.scryPost(host, id, null, null);
- if ("fpost" in res) return res;
- else {
- const existing = queryClient.getQueryData(["trill-thread", host, id]);
- const existingData = existing || data;
- if ("bugen" in res) {
- // we peek for the actual node
- peekTheNode();
- // if we have a cache we don't invalidate it
- if (existingData && "fpost" in existingData) return existingData;
- // if we don't have a cache then we show the loading screen
- else return res;
- }
- if ("no-node" in res) {
- if (existingData && "fpost" in existingData) return existingData;
- else return res;
- }
- }
- }
- function peekTheNode() {
- let timer;
- peekNode({ ship: host, id });
- timer = setTimeout(() => {
- const gotPost = dataRef.current && "fpost" in dataRef.current;
- setDead(!gotPost);
- // clearTimeout(timer);
- }, 10_000);
- }
-
- useEffect(() => {
- const path = `${host}/${id}`;
- if (path in peekedPosts) {
- queryClient.setQueryData(["trill-thread", host, id], {
- fpost: peekedPosts[path],
- });
- } else if (path in deniedPosts) {
- setDenied(true);
- }
- }, [peekedPosts]);
- useEffect(() => {
- const path = `${host}/${id}`;
- if (path in deniedPosts) setDenied(true);
- }, [deniedPosts]);
-
- useEffect(() => {
- const l = lastThread;
- if (l && l.thread == id) {
- queryClient.setQueryData(["trill-thread", host, id], { fpost: l });
- }
- }, [lastThread]);
- function retryPeek(e: React.MouseEvent) {
- e.stopPropagation();
- setDead(false);
- peekTheNode();
- }
- if (enest > 3)
- return (
- <div className={props.className}>
- <div className="lazy x-center not-found">
- <button className="x-center" onMouseUp={handleShowNested}>
- Load more
- </button>
- </div>
- </div>
- );
- else
- return data ? (
- dead ? (
- <div className={props.className}>
- <div className="no-response x-center not-found">
- <p>{host} did not respond</p>
- <button className="x-center" onMouseUp={retryPeek}>
- Try again
- </button>
- </div>
- </div>
- ) : denied ? (
- <div className={props.className}>
- <p className="x-center not-found">
- {host} denied you access to this post
- </p>
- </div>
- ) : "no-node" in data || "bucun" in data ? (
- <div className={props.className}>
- <p className="x-center not-found">Post not found</p>
- </div>
- ) : "bugen" in data ? (
- <div className={props.className}>
- <div className="x-center not-found">
- <p className="x-center">Post not found, requesting...</p>
- <img src={spinner} className="x-center s-100" alt="" />
- </div>
- </div>
- ) : "fpost" in data && data.fpost.contents === null ? (
- <div className={props.className}>
- <p className="x-center not-found">Post deleted</p>
- </div>
- ) : (
- <Component
- data={data.fpost}
- refetch={refetch}
- {...props}
- nest={enest}
- />
- )
- ) : // no data
- isLoading || isError ? (
- <div className={props.className}>
- <img className="x-center post-spinner" src={spinner} alt="" />
- </div>
- ) : (
- <div className={props.className}>
- <p>...</p>
- </div>
- );
- };
-}
-export default PostData;
diff --git a/front/src/components/feed/PostList.tsx b/front/src/components/feed/PostList.tsx
index 3d41ff8..b09a0e9 100644
--- a/front/src/components/feed/PostList.tsx
+++ b/front/src/components/feed/PostList.tsx
@@ -1,4 +1,4 @@
-import TrillPost from "./Post";
+import TrillPost from "@/components/post/Post";
import type { FC } from "@/types/trill";
// import { useEffect } from "react";
// import { useQueryClient } from "@tanstack/react-query";
@@ -22,6 +22,7 @@ function TrillFeed({ data, refetch }: { data: FC; refetch: Function }) {
{Object.keys(data.feed)
.sort()
.reverse()
+ .slice(0, 50)
.map((i) => (
<TrillPost key={i} poast={data.feed[i]} refetch={refetch} />
))}
diff --git a/front/src/components/feed/Quote.tsx b/front/src/components/feed/Quote.tsx
deleted file mode 100644
index d71be40..0000000
--- a/front/src/components/feed/Quote.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import type { FullNode } from "@/types/trill";
-import { date_diff } from "@/logic/utils";
-import { useLocation } from "wouter";
-import Body from "./Body";
-import Sigil from "../Sigil";
-import { toFlat } from "./RP";
-
-function Quote({
- data,
- refetch,
- nest,
-}: {
- data: FullNode;
- refetch?: Function;
- nest: number;
-}) {
- const [_, navigate] = useLocation();
- function gotoQuote(e: React.MouseEvent) {
- e.stopPropagation();
- navigate(`/feed/${data.host}/${data.id}`);
- }
- return (
- <div onMouseUp={gotoQuote} className="quote-in-post">
- <header className="btw">
- (
- <div className="quote-author flex">
- <Sigil patp={data.author} size={20} />
- {data.author}
- </div>
- )<span>{date_diff(data.time, "short")}</span>
- </header>
- <Body poast={toFlat(data)} nest={nest} refetch={refetch!} />
- </div>
- );
-}
-
-export default Quote;
diff --git a/front/src/components/feed/RP.tsx b/front/src/components/feed/RP.tsx
deleted file mode 100644
index dc733cc..0000000
--- a/front/src/components/feed/RP.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-import Post from "./Post";
-import type { Ship } from "@/types/urbit";
-import type { Poast, FullNode, ID } from "@/types/trill";
-import PostData from "./PostData";
-export default function (props: {
- host: string;
- id: string;
- rter: Ship;
- rtat: number;
- rtid: ID;
- refetch?: Function;
-}) {
- return PostData(props)(RP);
-}
-
-function RP({
- data,
- refetch,
- rter,
- rtat,
- rtid,
-}: {
- data: FullNode;
- refetch: Function;
- rter: Ship;
- rtat: number;
- rtid: ID;
-}) {
- return (
- <Post
- poast={toFlat(data)}
- rter={rter}
- rtat={rtat}
- rtid={rtid}
- refetch={refetch}
- />
- );
-}
-
-export function toFlat(n: FullNode): Poast {
- return {
- ...n,
- children: !n.children
- ? []
- : Object.keys(n.children).map((c) => n.children[c].id),
- };
-}
diff --git a/front/src/components/feed/Reactions.tsx b/front/src/components/feed/Reactions.tsx
deleted file mode 100644
index 58662cd..0000000
--- a/front/src/components/feed/Reactions.tsx
+++ /dev/null
@@ -1,118 +0,0 @@
-import type { Poast } from "@/types/trill";
-import yeschad from "@/assets/reacts/yeschad.png";
-import cringe from "@/assets/reacts/cringe.png";
-import cry from "@/assets/reacts/cry.png";
-import doom from "@/assets/reacts/doom.png";
-import galaxy from "@/assets/reacts/galaxy.png";
-import gigachad from "@/assets/reacts/gigachad.png";
-import pepechin from "@/assets/reacts/pepechin.png";
-import pepeeyes from "@/assets/reacts/pepeeyes.png";
-import pepegmi from "@/assets/reacts/pepegmi.png";
-import pepesad from "@/assets/reacts/pepesad.png";
-import pink from "@/assets/reacts/pink.png";
-import soy from "@/assets/reacts/soy.png";
-import chad from "@/assets/reacts/chad.png";
-import pika from "@/assets/reacts/pika.png";
-import facepalm from "@/assets/reacts/facepalm.png";
-import emoji from "@/assets/icons/emoji.svg";
-import emojis from "@/logic/emojis.json";
-import Modal from "../modals/Modal";
-import useLocalState from "@/state/state";
-
-export function ReactModal({ send }: { send: (s: string) => Promise<number> }) {
- const { setModal } = useLocalState();
- async function sendReact(e: React.MouseEvent, s: string) {
- e.stopPropagation();
- const res = await send(s);
- if (res) setModal(null);
- }
- // todo one more meme
- return (
- <Modal>
- <div id="react-list">
- <span onMouseUp={(e) => sendReact(e, "❤️")}>️️❤️</span>
- <span onMouseUp={(e) => sendReact(e, "🤔")}>🤔</span>
- <span onMouseUp={(e) => sendReact(e, "😅")}>😅</span>
- <span onMouseUp={(e) => sendReact(e, "🤬")}>🤬</span>
- <span onMouseUp={(e) => sendReact(e, "😂")}>😂️</span>
- <span onMouseUp={(e) => sendReact(e, "🫡")}>🫡️</span>
- <span onMouseUp={(e) => sendReact(e, "🤢")}>🤢</span>
- <span onMouseUp={(e) => sendReact(e, "😭")}>😭</span>
- <span onMouseUp={(e) => sendReact(e, "😱")}>😱</span>
- <img
- onMouseUp={(e) => sendReact(e, "facepalm")}
- src={facepalm}
- alt=""
- />
- <span onMouseUp={(e) => sendReact(e, "👍")}>👍️</span>
- <span onMouseUp={(e) => sendReact(e, "👎")}>👎️</span>
- <span onMouseUp={(e) => sendReact(e, "☝")}>☝️</span>
- <span onMouseUp={(e) => sendReact(e, "🤝")}>🤝</span>️
- <span onMouseUp={(e) => sendReact(e, "🙏")}>🙏</span>
- <span onMouseUp={(e) => sendReact(e, "🤡")}>🤡</span>
- <span onMouseUp={(e) => sendReact(e, "👀")}>👀</span>
- <span onMouseUp={(e) => sendReact(e, "🎤")}>🎤</span>
- <span onMouseUp={(e) => sendReact(e, "💯")}>💯</span>
- <span onMouseUp={(e) => sendReact(e, "🔥")}>🔥</span>
- <img onMouseUp={(e) => sendReact(e, "yeschad")} src={yeschad} alt="" />
- <img
- onMouseUp={(e) => sendReact(e, "gigachad")}
- src={gigachad}
- alt=""
- />
- <img onMouseUp={(e) => sendReact(e, "pika")} src={pika} alt="" />
- <img onMouseUp={(e) => sendReact(e, "cringe")} src={cringe} alt="" />
- <img onMouseUp={(e) => sendReact(e, "pepegmi")} src={pepegmi} alt="" />
- <img onMouseUp={(e) => sendReact(e, "pepesad")} src={pepesad} alt="" />
- <img onMouseUp={(e) => sendReact(e, "galaxy")} src={galaxy} alt="" />
- <img onMouseUp={(e) => sendReact(e, "pink")} src={pink} alt="" />
- <img onMouseUp={(e) => sendReact(e, "soy")} src={soy} alt="" />
- <img onMouseUp={(e) => sendReact(e, "cry")} src={cry} alt="" />
- <img onMouseUp={(e) => sendReact(e, "doom")} src={doom} alt="" />
- </div>
- </Modal>
- );
-}
-
-export function stringToReact(s: string) {
- const em = (emojis as Record<string, string>)[s.replace(/\:/g, "")];
- if (s === "yeschad")
- return <img className="react-img" src={yeschad} alt="" />;
- if (s === "facepalm")
- return <img className="react-img" src={facepalm} alt="" />;
- if (s === "yes.jpg")
- return <img className="react-img" src={yeschad} alt="" />;
- if (s === "gigachad")
- return <img className="react-img" src={gigachad} alt="" />;
- if (s === "pepechin")
- return <img className="react-img" src={pepechin} alt="" />;
- if (s === "pepeeyes")
- return <img className="react-img" src={pepeeyes} alt="" />;
- if (s === "pepegmi")
- return <img className="react-img" src={pepegmi} alt="" />;
- if (s === "pepesad")
- return <img className="react-img" src={pepesad} alt="" />;
- if (s === "")
- return <img className="react-img no-react" src={emoji} alt="" />;
- if (s === "cringe") return <img className="react-img" src={cringe} alt="" />;
- if (s === "cry") return <img className="react-img" src={cry} alt="" />;
- if (s === "crywojak") return <img className="react-img" src={cry} alt="" />;
- if (s === "doom") return <img className="react-img" src={doom} alt="" />;
- if (s === "galaxy") return <img className="react-img" src={galaxy} alt="" />;
- if (s === "pink") return <img className="react-img" src={pink} alt="" />;
- if (s === "pinkwojak") return <img className="react-img" src={pink} alt="" />;
- if (s === "soy") return <img className="react-img" src={soy} alt="" />;
- if (s === "chad") return <img className="react-img" src={chad} alt="" />;
- if (s === "pika") return <img className="react-img" src={pika} alt="" />;
- if (em) return <span className="react-icon">{em}</span>;
- else if (s.length > 2) return <span className="react-icon"></span>;
- else return <span className="react-icon">{s}</span>;
-}
-
-export function TrillReactModal({ poast }: { poast: Poast }) {
- const { api } = useLocalState();
- async function sendReact(s: string) {
- return await api!.addReact(poast.host, poast.id, s);
- }
- return <ReactModal send={sendReact} />;
-}
diff --git a/front/src/components/feed/StatsModal.tsx b/front/src/components/feed/StatsModal.tsx
deleted file mode 100644
index 4720b2a..0000000
--- a/front/src/components/feed/StatsModal.tsx
+++ /dev/null
@@ -1,106 +0,0 @@
-import type { Poast } from "@/types/trill";
-import Modal from "../modals/Modal";
-import { useState } from "react";
-import Post from "./Post";
-import RP from "./RP";
-import Avatar from "../Avatar";
-import { stringToReact } from "./Reactions";
-
-function StatsModal({ poast, close }: { close: any; poast: Poast }) {
- const [tab, setTab] = useState("replies");
- const replies = poast.children || [];
- const quotes = poast.engagement.quoted;
- const reposts = poast.engagement.shared;
- const reacts = poast.engagement.reacts;
- function set(e: React.MouseEvent, s: string) {
- e.stopPropagation();
- setTab(s);
- }
- // TODO revise the global thingy here
- return (
- <Modal close={close}>
- <div id="stats-modal">
- <Post poast={poast} refetch={() => {}} />
- <div id="tabs">
- <div
- role="link"
- className={"tab" + (tab === "replies" ? " active-tab" : "")}
- onClick={(e) => set(e, "replies")}
- >
- <h4>Replies</h4>
- </div>
- <div
- role="link"
- className={"tab" + (tab === "quotes" ? " active-tab" : "")}
- onClick={(e) => set(e, "quotes")}
- >
- <h4>Quotes</h4>
- </div>
- <div
- role="link"
- className={"tab" + (tab === "reposts" ? " active-tab" : "")}
- onClick={(e) => set(e, "reposts")}
- >
- <h4>Reposts</h4>
- </div>
- <div
- role="link"
- className={"tab" + (tab === "reacts" ? " active-tab" : "")}
- onClick={(e) => set(e, "reacts")}
- >
- <h4>Reacts</h4>
- </div>
- </div>
- <div id="engagement">
- {tab === "replies" ? (
- <div id="replies">
- {replies.map((p) => (
- <div key={p} className="reply-stat">
- <RP
- host={poast.host}
- id={p}
- rter={undefined}
- rtat={undefined}
- rtid={undefined}
- />
- </div>
- ))}
- </div>
- ) : tab === "quotes" ? (
- <div id="quotes">
- {quotes.map((p) => (
- <div key={p.pid.id} className="quote-stat">
- <RP
- host={p.pid.ship}
- id={p.pid.id}
- rter={undefined}
- rtat={undefined}
- rtid={undefined}
- />
- </div>
- ))}
- </div>
- ) : tab === "reposts" ? (
- <div id="reposts">
- {reposts.map((p) => (
- <div key={p.pid.id} className="repost-stat">
- <Avatar p={p.pid.ship} size={40} />
- </div>
- ))}
- </div>
- ) : tab === "reacts" ? (
- <div id="reacts">
- {Object.keys(reacts).map((p) => (
- <div key={p} className="react-stat btw">
- <Avatar p={p} size={32} />
- {stringToReact(reacts[p])}
- </div>
- ))}
- </div>
- ) : null}
- </div>
- </div>
- </Modal>
- );
-}
-export default StatsModal;