diff options
Diffstat (limited to 'front/src/components/feed')
-rw-r--r-- | front/src/components/feed/Body.tsx | 174 | ||||
-rw-r--r-- | front/src/components/feed/Card.tsx | 9 | ||||
-rw-r--r-- | front/src/components/feed/Composer.tsx | 52 | ||||
-rw-r--r-- | front/src/components/feed/External.tsx | 41 | ||||
-rw-r--r-- | front/src/components/feed/Footer.tsx | 237 | ||||
-rw-r--r-- | front/src/components/feed/Header.tsx | 33 | ||||
-rw-r--r-- | front/src/components/feed/Media.tsx | 35 | ||||
-rw-r--r-- | front/src/components/feed/NostrIcon.tsx | 22 | ||||
-rw-r--r-- | front/src/components/feed/Post.tsx | 79 | ||||
-rw-r--r-- | front/src/components/feed/PostData.tsx | 160 | ||||
-rw-r--r-- | front/src/components/feed/PostList.tsx | 3 | ||||
-rw-r--r-- | front/src/components/feed/Quote.tsx | 37 | ||||
-rw-r--r-- | front/src/components/feed/RP.tsx | 47 | ||||
-rw-r--r-- | front/src/components/feed/Reactions.tsx | 118 | ||||
-rw-r--r-- | front/src/components/feed/StatsModal.tsx | 106 |
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; |