This commit is contained in:
@ -1,39 +0,0 @@
|
||||
.markdown a {
|
||||
@apply underline;
|
||||
}
|
||||
|
||||
.markdown blockquote {
|
||||
margin: 0;
|
||||
padding-left: 12px;
|
||||
@apply border-l-neutral-800 border-2 text-neutral-400;
|
||||
}
|
||||
|
||||
.markdown hr {
|
||||
border: 0;
|
||||
height: 1px;
|
||||
margin: 20px;
|
||||
@apply bg-neutral-800;
|
||||
}
|
||||
|
||||
.markdown img:not(.custom-emoji),
|
||||
.markdown video,
|
||||
.markdown iframe,
|
||||
.markdown audio {
|
||||
max-width: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.markdown iframe,
|
||||
.markdown video {
|
||||
width: -webkit-fill-available;
|
||||
aspect-ratio: 16 / 9;
|
||||
}
|
||||
|
||||
.markdown h1,
|
||||
.markdown h2,
|
||||
.markdown h3,
|
||||
.markdown h4,
|
||||
.markdown h5,
|
||||
.markdown h6 {
|
||||
margin: 0.5em 0;
|
||||
}
|
@ -1,5 +1,3 @@
|
||||
import "./markdown.css";
|
||||
|
||||
import { ReactNode, forwardRef, useMemo } from "react";
|
||||
import { Token, Tokens, marked } from "marked";
|
||||
import { Link } from "react-router-dom";
|
||||
@ -16,9 +14,9 @@ const Markdown = forwardRef<HTMLDivElement, MarkdownProps>(
|
||||
switch (t.type) {
|
||||
case "paragraph": {
|
||||
return (
|
||||
<div key={ctr++}>
|
||||
<p key={ctr++} className="py-2">
|
||||
{t.tokens ? t.tokens.map(renderToken) : t.raw}
|
||||
</div>
|
||||
</p>
|
||||
);
|
||||
}
|
||||
case "image": {
|
||||
@ -28,37 +26,37 @@ const Markdown = forwardRef<HTMLDivElement, MarkdownProps>(
|
||||
switch (t.depth) {
|
||||
case 1:
|
||||
return (
|
||||
<h1 key={ctr++}>
|
||||
<h1 key={ctr++} className="my-6 text-2xl">
|
||||
{t.tokens ? t.tokens.map(renderToken) : t.raw}
|
||||
</h1>
|
||||
);
|
||||
case 2:
|
||||
return (
|
||||
<h2 key={ctr++}>
|
||||
<h2 key={ctr++} className="my-5 text-xl">
|
||||
{t.tokens ? t.tokens.map(renderToken) : t.raw}
|
||||
</h2>
|
||||
);
|
||||
case 3:
|
||||
return (
|
||||
<h3 key={ctr++}>
|
||||
<h3 key={ctr++} className="my-4 text-lg">
|
||||
{t.tokens ? t.tokens.map(renderToken) : t.raw}
|
||||
</h3>
|
||||
);
|
||||
case 4:
|
||||
return (
|
||||
<h4 key={ctr++}>
|
||||
<h4 key={ctr++} className="my-3 text-md">
|
||||
{t.tokens ? t.tokens.map(renderToken) : t.raw}
|
||||
</h4>
|
||||
);
|
||||
case 5:
|
||||
return (
|
||||
<h5 key={ctr++}>
|
||||
<h5 key={ctr++} className="my-2">
|
||||
{t.tokens ? t.tokens.map(renderToken) : t.raw}
|
||||
</h5>
|
||||
);
|
||||
case 6:
|
||||
return (
|
||||
<h6 key={ctr++}>
|
||||
<h6 key={ctr++} className="my-2">
|
||||
{t.tokens ? t.tokens.map(renderToken) : t.raw}
|
||||
</h6>
|
||||
);
|
||||
@ -66,7 +64,11 @@ const Markdown = forwardRef<HTMLDivElement, MarkdownProps>(
|
||||
throw new Error("Invalid heading");
|
||||
}
|
||||
case "codespan": {
|
||||
return <code key={ctr++}>{t.raw}</code>;
|
||||
return (
|
||||
<code key={ctr++} className="bg-neutral-900 px-2">
|
||||
{t.raw.substring(1, t.raw.length - 1)}
|
||||
</code>
|
||||
);
|
||||
}
|
||||
case "code": {
|
||||
return <pre key={ctr++}>{t.raw}</pre>;
|
||||
@ -84,23 +86,34 @@ const Markdown = forwardRef<HTMLDivElement, MarkdownProps>(
|
||||
}
|
||||
case "blockquote": {
|
||||
return (
|
||||
<blockquote key={ctr++}>
|
||||
<blockquote
|
||||
key={ctr++}
|
||||
className="outline-l-neutral-900 outline text-neutral-300 p-3"
|
||||
>
|
||||
{t.tokens ? t.tokens.map(renderToken) : t.raw}
|
||||
</blockquote>
|
||||
);
|
||||
}
|
||||
case "link": {
|
||||
return (
|
||||
<Link to={t.href} key={ctr++}>
|
||||
<Link to={t.href} key={ctr++} className="underline">
|
||||
{t.tokens ? t.tokens.map(renderToken) : t.raw}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
case "list": {
|
||||
if (t.ordered) {
|
||||
return <ol key={ctr++}>{t.items.map(renderToken)}</ol>;
|
||||
return (
|
||||
<ol key={ctr++} className="list-decimal list-outside">
|
||||
{t.items.map(renderToken)}
|
||||
</ol>
|
||||
);
|
||||
} else {
|
||||
return <ul key={ctr++}>{t.items.map(renderToken)}</ul>;
|
||||
return (
|
||||
<ul key={ctr++} className="list-disc list-outside">
|
||||
{t.items.map(renderToken)}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
}
|
||||
case "list_item": {
|
||||
@ -170,7 +183,7 @@ const Markdown = forwardRef<HTMLDivElement, MarkdownProps>(
|
||||
return marked.lexer(props.content);
|
||||
}, [props.content]);
|
||||
return (
|
||||
<div className="markdown" ref={ref}>
|
||||
<div className="leading-8 text-pretty break-words" ref={ref}>
|
||||
{parsed
|
||||
.filter((a) => a.type !== "footnote" && a.type !== "footnotes")
|
||||
.map((a) => renderToken(a))}
|
||||
|
10
src/main.tsx
10
src/main.tsx
@ -15,6 +15,8 @@ import { StatusPage } from "./pages/status.tsx";
|
||||
import { AccountSettings } from "./pages/account-settings.tsx";
|
||||
import { VmBillingPage } from "./pages/vm-billing.tsx";
|
||||
import { VmGraphsPage } from "./pages/vm-graphs.tsx";
|
||||
import { NewsPage } from "./pages/news.tsx";
|
||||
import { NewsPost } from "./pages/news-post.tsx";
|
||||
|
||||
const system = new NostrSystem({
|
||||
automaticOutboxModel: false,
|
||||
@ -71,6 +73,14 @@ const router = createBrowserRouter([
|
||||
path: "/status",
|
||||
element: <StatusPage />,
|
||||
},
|
||||
{
|
||||
path: "/news",
|
||||
element: <NewsPage />,
|
||||
},
|
||||
{
|
||||
path: "/news/:id",
|
||||
element: <NewsPost />,
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
@ -40,6 +40,8 @@ export default function HomePage() {
|
||||
{" | "}
|
||||
<Link to="/tos">Terms</Link>
|
||||
{" | "}
|
||||
<Link to="/news">News</Link>
|
||||
{" | "}
|
||||
<a
|
||||
href={`https://snort.social/${NostrProfile.encode()}`}
|
||||
target="_blank"
|
||||
|
25
src/pages/news-post.tsx
Normal file
25
src/pages/news-post.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import { NostrLink, TaggedNostrEvent } from "@snort/system";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import Markdown from "../components/markdown";
|
||||
import Profile from "../components/profile";
|
||||
|
||||
export function NewsPost() {
|
||||
const { state } = useLocation() as { state?: TaggedNostrEvent };
|
||||
|
||||
if (!state) return;
|
||||
const title = state.tags.find((a) => a[0] == "title")?.[1];
|
||||
const posted = Number(
|
||||
state.tags.find((a) => a[0] == "published_at")?.[1] ?? state.created_at,
|
||||
);
|
||||
return (
|
||||
<div>
|
||||
<div className="text-2xl">{title}</div>
|
||||
<div className="flex items-center justify-between py-8">
|
||||
<Profile link={NostrLink.profile(state.pubkey, state.relays)} />
|
||||
<div>{new Date(posted * 1000).toLocaleString()}</div>
|
||||
</div>
|
||||
|
||||
<Markdown content={state.content} />
|
||||
</div>
|
||||
);
|
||||
}
|
55
src/pages/news.tsx
Normal file
55
src/pages/news.tsx
Normal file
@ -0,0 +1,55 @@
|
||||
import { EventKind, NostrLink, RequestBuilder } from "@snort/system";
|
||||
import { NostrProfile } from "../const";
|
||||
import { useRequestBuilder } from "@snort/system-react";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
export function NewsPage() {
|
||||
const req = new RequestBuilder("news");
|
||||
req
|
||||
.withFilter()
|
||||
.kinds([EventKind.LongFormTextNote])
|
||||
.authors([NostrProfile.id])
|
||||
.limit(10);
|
||||
|
||||
const posts = useRequestBuilder(req);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="text-2xl">News</div>
|
||||
{posts
|
||||
.sort((a, b) => {
|
||||
const a_posted = Number(
|
||||
a.tags.find((a) => a[0] == "published_at")?.[1] ?? a.created_at,
|
||||
);
|
||||
const b_posted = Number(
|
||||
b.tags.find((z) => z[0] == "published_at")?.[1] ?? b.created_at,
|
||||
);
|
||||
return b_posted - a_posted;
|
||||
})
|
||||
.map((a) => {
|
||||
const link = NostrLink.fromEvent(a);
|
||||
const title = a.tags.find((a) => a[0] == "title")?.[1];
|
||||
const posted = Number(
|
||||
a.tags.find((a) => a[0] == "published_at")?.[1] ?? a.created_at,
|
||||
);
|
||||
const slug = title
|
||||
?.toLocaleLowerCase()
|
||||
.replace(/[:/]/g, "")
|
||||
.trimStart()
|
||||
.trimEnd()
|
||||
.replace(/ /g, "-");
|
||||
return (
|
||||
<Link to={`/news/${slug}`} state={a} key={link.tagKey}>
|
||||
<div className="flex flex-col rounded-xl bg-neutral-900 px-3 py-4">
|
||||
<div className="text-xl flex items-center justify-between">
|
||||
<div>{title}</div>
|
||||
<div>{new Date(posted * 1000).toDateString()}</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
{posts.length === 0 && <div>No posts yet..</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
Reference in New Issue
Block a user