on master help sign in
Merge pull request #1827 from urbit/gc/octspot
744e7c3Gordon Cieplak
components/ecosystem/BasicPage.js+25−2
@@ -28,6 +28,7 @@ export default function BasicPage({
28 metaPost.title = `${post.title} - ${section}`;
28 metaPost.title = `${post.title} - ${section}`;
29 metaPost.description =
29 metaPost.description =
30 section === "Podcast" ? `${post.podcast} - ${post.date}` : post.description;
30 section === "Podcast" ? `${post.podcast} - ${post.date}` : post.description;
31+ section === "Article" ? `${post.publication} - ${post.date}` : post.description;
31 return (
32 return (
32 <Container>
33 <Container>
33 <Head>
34 <Head>
@@ -43,7 +44,7 @@ export default function BasicPage({
43 <div className="flex items-center space-x-4">
44 <div className="flex items-center space-x-4">
44 <img src={post.image} className="w-36" />
45 <img src={post.image} className="w-36" />
45 <div className="flex flex-col pl-2">
46 <div className="flex flex-col pl-2">
46− <h1 className={classnames({ "text-3xl": section === "Podcast" })}>
47+ <h1 className={classnames({ "text-3xl": section === "Podcast" },{ "text-3xl": section === "Article" })}>
47 {post.title}
48 {post.title}
48 </h1>
49 </h1>
49 <p>{section}</p>
50 <p>{section}</p>
@@ -58,13 +59,26 @@ export default function BasicPage({
58 <p>{post.podcast}</p>
59 <p>{post.podcast}</p>
59 </div>
60 </div>
60 )}
61 )}
62+ {post?.publication && (
63+ <div className="flex flex-col">
64+ <p className="font-bold text-wall-400">Publication</p>
65+ <p>{post.publication}</p>
66+ </div>
67+ )}
68+ {post?.author && (
69+ <div className="flex flex-col">
70+ <p className="font-bold text-wall-400">Author</p>
71+ <p>{post.author}</p>
72+ </div>
73+ )}
61 {post?.date && (
74 {post?.date && (
62 <div className="flex flex-col">
75 <div className="flex flex-col">
63 <p className="font-bold text-wall-400">Date</p>
76 <p className="font-bold text-wall-400">Date</p>
64 <p className="shrink-0">{post.date}</p>
77 <p className="shrink-0">{post.date}</p>
65 </div>
78 </div>
66 )}
79 )}
80+
67− {post?.URL && section !== "Podcast" && (
81+ {post?.URL && section !== "Podcast" && section !== "Article" && (
68 <div className="flex flex-col">
82 <div className="flex flex-col">
69 <p className="font-bold text-wall-400">Website</p>
83 <p className="font-bold text-wall-400">Website</p>
70 <a
84 <a
@@ -75,6 +89,7 @@ export default function BasicPage({
75 </a>
89 </a>
76 </div>
90 </div>
77 )}
91 )}
92+
78 {post?.URL && section === "Urbit Marketplace" && (
93 {post?.URL && section === "Urbit Marketplace" && (
79 <div className="flex flex-col">
94 <div className="flex flex-col">
80 <p className="font-bold text-wall-400">Accepts</p>
95 <p className="font-bold text-wall-400">Accepts</p>
@@ -93,6 +108,14 @@ export default function BasicPage({
93 &nbsp;Listen
108 &nbsp;Listen
94 </a>
109 </a>
95 )}
110 )}
111+ {post?.URL && section === "Article" && (
112+ <a
113+ href={post.URL}
114+ className="button-lg bg-green-400 text-white cursor-pointer flex space-x-2"
115+ >
116+ Read
117+ </a>
118+ )}
96 </div>
119 </div>
97 <div className="flex">
120 <div className="flex">
98 <div className="markdown w-full">
121 <div className="markdown w-full">
components/ecosystem/IndexCard.js+1
@@ -43,6 +43,7 @@ const badge = (type) => {
43 "bg-[#83b2d4]": type === "Application",
43 "bg-[#83b2d4]": type === "Application",
44 "bg-wall-600": type === "Podcast",
44 "bg-wall-600": type === "Podcast",
45 "bg-[#8dd5b1]": type === "Marketplace",
45 "bg-[#8dd5b1]": type === "Marketplace",
46+ "bg-[#B5966D]": type === "Article"
46 }
47 }
47 )}
48 )}
48 >
49 >
components/ecosystem/Post.js+9
@@ -49,6 +49,14 @@ export default function Post({ post }) {
49 </Link>
49 </Link>
50 )}
50 )}
51
51
52+ {post[feat].type === "Article" && (
53+ <Link href={href || "#"} passHref>
54+ <a class="button-sm text-white flex-col bg-green-400 cursor-pointer pr-4 w-fit">
55+ Read {post[feat].type}
56+ </a>
57+ </Link>
58+ )}
59+
52 {post[feat].type === "Application" && (
60 {post[feat].type === "Application" && (
53 <Link href={href || "#"} passHref>
61 <Link href={href || "#"} passHref>
54 <a class="button-sm text-white flex-col bg-green-400 cursor-pointer pr-4 w-fit">
62 <a class="button-sm text-white flex-col bg-green-400 cursor-pointer pr-4 w-fit">
@@ -87,6 +95,7 @@ const badge = (type) => {
87 "bg-[#83b2d4]": type === "Application",
95 "bg-[#83b2d4]": type === "Application",
88 "bg-wall-600": type === "Podcast",
96 "bg-wall-600": type === "Podcast",
89 "bg-[#8dd5b1]": type === "Marketplace",
97 "bg-[#8dd5b1]": type === "Marketplace",
98+ "bg-[#B5966D]": type === "Article",
90 }
99 }
91 )}
100 )}
92 >
101 >
content/articles/2022-09-29_the-dream-of-digital-homesteading.mdnew file+10
@@ -0,0 +1,10 @@
1++++
2+title = "The Dream of Digital Homesteading"
3+publication = "Compact"
4+author = "Geoff Schullenberger"
5+date = "2022-09-29"
6+URL = "https://compactmag.com/article/the-dream-of-digital-homesteading"
7+image = "https://storage.googleapis.com/media.urbit.org/site/ecosystem/articles/compactmag.png"
8++++
9+
10+Geoff Schullenberger attends Assembly 2022 and chronicles his impressions and thoughts.
content/ecosystem/spotlight/2022-october.mdnew file+41
@@ -0,0 +1,41 @@
1++++
2+title = "October 2022"
3+date = "2022-10-03"
4+
5+[featured-1]
6+title = "Uqbar Development Suite"
7+description = "Write smart contracts and blockchain applications in an integrated development suite."
8+image=""
9+content = """
10+At Assembly 2022 Uqbar debuted their brand new developer toolkit: Ziggurat. Ziggurat provides a unified development interface where programmers can write contracts, receive compiler feedback, and test on a fake chain. This promises a huge improvement on the developer experience for smart contracts and blockchain application development alike. Note: As Ziggurat is in active development and undergoing frequent updates, Uqbar recommends installing Ziggurat on a moon rather than your primary ship.
11+
12+
13+![Screenshot](https://storage.googleapis.com/media.urbit.org/site/ecosystem/spotlight/uqbar-code.png)
14+
15+"""
16+
17+[featured-2]
18+title = "Tlon"
19+description = "The organization that created Urbit is now focused on building the best possible foundational user experiences on Urbit."
20+image="https://storage.googleapis.com/media.urbit.org/site/ecosystem/spotlight/tlonlogos.png"
21+content = """
22+
23+Tlon is the organization responsible for the genesis of Urbit and now focused on creating lasting and effective tools for Urbit. Formerly rather quiet about its development activity, Tlon has recently launched a [beautiful public website](https://tlon.io) and is sharing more of its process in the new [Tlon public group](/groups/~nibset-napwyn/tlon-public).
24+
25+![Screenshot](https://storage.googleapis.com/media.urbit.org/site/ecosystem/spotlight/tlonio.png)
26+
27+
28+"""
29+
30+[featured-3]
31+
32+title = "The Dream of Digital Homesteading"
33+image="https://storage.googleapis.com/media.urbit.org/site/ecosystem/spotlight/compact-thumb2.png"
34+description = "Compact Magazine's managing editor Geoff Shullenberger attended Assembly 2022 and tuned into both the event vibes and Urbit's technical aspirations."
35+content = """
36+
37+Compact Magazine provides a clear-eyed view of both the social and technical visions of the Urbit community as well as the literary milieu around it.
38+![](https://storage.googleapis.com/media.urbit.org/site/ecosystem/spotlight/thedreamofdigitalhomesteading.png)
39+
40+"""
41++++
content/groups/~nibset-napwyn/tlon-public.mdnew file+12
@@ -0,0 +1,12 @@
1++++
2+title = "Tlon Public"
3+shortcode = "~nibset-napwyn/tlon-public"
4+description = "Updates, announcements, and broadcasts from Tlon."
5+type = "Public"
6+tile = "https://storage.googleapis.com/media.urbit.org/site/ecosystem/organizations/tlon.png"
7+participant_range = "200+"
8++++
9+
10+Download the latest alphas of Tlon products, check out Tlon's visual inspiration and design work, and chat with Tlon team about their software.
11+
12+![](https://storage.googleapis.com/media.urbit.org/site/ecosystem/groups/tlon-public.png)
pages/articles/[slug].jsnew file+84
@@ -0,0 +1,84 @@
1+import BasicPage from "../../components/ecosystem/BasicPage";
2+import {
3+ Markdown,
4+ getAllPosts,
5+ getPostBySlug,
6+ getPostSlugs,
7+} from "@urbit/foundation-design-system";
8+import Link from "next/link";
9+
10+export default function Article({
11+ post,
12+ markdown,
13+ search,
14+ index,
15+ spotlights = [],
16+}) {
17+ return (
18+ <BasicPage
19+ section="Article"
20+ post={post}
21+ markdown={markdown}
22+ search={search}
23+ index={index}
24+ >
25+ {spotlights.length > 0 && (
26+ <p>
27+ Featured in{" "}
28+ {spotlights.map((e, i) => (
29+ <>
30+ {i ? ", " : ""}
31+ <Link href={`/ecosystem/spotlight/${e.slug}`}>{e.title}</Link>
32+ </>
33+ ))}
34+ </p>
35+ )}
36+ </BasicPage>
37+ );
38+}
39+
40+export async function getStaticProps({ params }) {
41+ const post = getPostBySlug(
42+ params.slug,
43+ ["title", "slug", "URL", "publication", "author", "date", "image", "content"],
44+ "articles"
45+ );
46+
47+ const spotlights = getAllPosts(
48+ ["title", "slug", "featured-1", "featured-2", "featured-3"],
49+ "ecosystem/spotlight"
50+ ).filter(
51+ (e) =>
52+ e?.["featured-1"]?.title === post.title ||
53+ e?.["featured-2"]?.title === post.title ||
54+ e?.["featured-3"]?.title === post.title
55+ );
56+
57+ let { index } = post?.extra || { index: null };
58+
59+ if (index === undefined) {
60+ index = null;
61+ }
62+
63+ const markdown = JSON.stringify(Markdown.parse({ post }));
64+ return {
65+ props: { post, markdown, spotlights, index },
66+ };
67+}
68+
69+export async function getStaticPaths() {
70+ const posts = getPostSlugs("articles");
71+
72+ const slugs = posts.map((e) => e.slice(0, -3));
73+
74+ return {
75+ paths: slugs.map((post) => {
76+ return {
77+ params: {
78+ slug: post,
79+ },
80+ };
81+ }),
82+ fallback: false,
83+ };
84+}
pages/ecosystem/index.js+43−2
@@ -30,6 +30,7 @@ export default function Ecosystem({
30 marketplaces,
30 marketplaces,
31 organizations,
31 organizations,
32 podcasts,
32 podcasts,
33+ articles
33}) {
34}) {
34 const router = useRouter();
35 const router = useRouter();
35
36
@@ -54,6 +55,9 @@ export default function Ecosystem({
54 case "organizations":
55 case "organizations":
55 title = "Organizations • Ecosystem";
56 title = "Organizations • Ecosystem";
56 break;
57 break;
58+ case "articles":
59+ title = "Articles • Ecosystem";
60+ break;
57 default:
61 default:
58 title = "Ecosystem Spotlight";
62 title = "Ecosystem Spotlight";
59 }
63 }
@@ -112,8 +116,8 @@ export default function Ecosystem({
112
116
113 <div
117 <div
114 className={classnames("grid gap-12 w-full", {
118 className={classnames("grid gap-12 w-full", {
115− "grid-cols-2 md:grid-cols-3": type !== "podcasts",
119+ "grid-cols-2 md:grid-cols-3": type == "organizations" || type == "marketplaces",
116− "grid-cols-1": type === "podcasts",
120+ "grid-cols-1": type === "podcasts" || type === "articles",
117 "grid-cols-1 md:grid-cols-1 lg:grid-cols-1 xl:grid-cols-2":
121 "grid-cols-1 md:grid-cols-1 lg:grid-cols-1 xl:grid-cols-2":
118 type === "applications" || type === "groups",
122 type === "applications" || type === "groups",
119 hidden: type === undefined,
123 hidden: type === undefined,
@@ -209,6 +213,26 @@ export default function Ecosystem({
209 </div>
213 </div>
210 </Link>
214 </Link>
211 ))}
215 ))}
216+
217+ {type === "articles" &&
218+ articles.map((article) => (
219+ <Link href={`/articles/${article.slug}`}>
220+ <div className="flex cursor-pointer space-x-4 items-center hover:opacity-90">
221+ <img className="w-28" src={article.image} />
222+ <div className="flex flex-col space-y-2 min-w-0">
223+ <p className="font-bold">{article.publication}</p>
224+ <p className="min-w-0 min-h-0 leading-5 ">
225+ {article.title}
226+ </p>
227+ <p className="text-wall-400">
228+ {formatDate(generateDisplayDate(article.date))}
229+ </p>
230+ </div>
231+ </div>
232+ </Link>
233+ ))}
234+
235+
212 </div>
236 </div>
213 </div>
237 </div>
214 </div>
238 </div>
@@ -283,6 +307,16 @@ function EcosystemSidebar() {
283 >
307 >
284 Podcasts
308 Podcasts
285 </ActiveLink>
309 </ActiveLink>
310+
311+ <ActiveLink
312+ currentPath={currentPath}
313+ className="mr-5 type-ui"
314+ href="/ecosystem?type=articles"
315+ >
316+ Articles
317+ </ActiveLink>
318+
319+
286 </div>
320 </div>
287 );
321 );
288}
322}
@@ -362,6 +396,11 @@ export async function getStaticProps({}) {
362 "podcasts",
396 "podcasts",
363 "date"
397 "date"
364 );
398 );
399+ const articles = getAllPosts(
400+ ["title", "image", "date", "publication", "author", "slug"],
401+ "articles",
402+ "date"
403+ );
365 const organizations = getAllPosts(
404 const organizations = getAllPosts(
366 ["title", "image", "slug"],
405 ["title", "image", "slug"],
367 "organizations"
406 "organizations"
@@ -411,6 +450,7 @@ export async function getStaticProps({}) {
411 ...applications.map((e) => ({ ...e, type: "Application" })),
450 ...applications.map((e) => ({ ...e, type: "Application" })),
412 ...organizations.map((e) => ({ ...e, type: "Organization" })),
451 ...organizations.map((e) => ({ ...e, type: "Organization" })),
413 ...podcasts.map((e) => ({ ...e, type: "Podcast" })),
452 ...podcasts.map((e) => ({ ...e, type: "Podcast" })),
453+ ...articles.map((e) => ({ ...e, type: "Article" })),
414 ...marketplaces.map((e) => ({ ...e, type: "Marketplace" })),
454 ...marketplaces.map((e) => ({ ...e, type: "Marketplace" })),
415 ].filter((e) => e.title === post[feat].title)?.[0];
455 ].filter((e) => e.title === post[feat].title)?.[0];
416 post[feat].image = post[feat]?.image || matchedPost?.image || null;
456 post[feat].image = post[feat]?.image || matchedPost?.image || null;
@@ -433,6 +473,7 @@ export async function getStaticProps({}) {
433 marketplaces,
473 marketplaces,
434 podcasts,
474 podcasts,
435 organizations,
475 organizations,
476+ articles
436 },
477 },
437 };
478 };
438}
479}
pages/ecosystem/spotlight/[slug].js+6
@@ -55,6 +55,11 @@ export async function getStaticProps({ params }) {
55 "podcasts",
55 "podcasts",
56 "date"
56 "date"
57 );
57 );
58+ const articles = getAllPosts(
59+ ["title", "image", "date", "publication", "author", "slug"],
60+ "articles",
61+ "date"
62+ );
58 const organizations = getAllPosts(
63 const organizations = getAllPosts(
59 ["title", "image", "slug"],
64 ["title", "image", "slug"],
60 "organizations"
65 "organizations"
@@ -86,6 +91,7 @@ export async function getStaticProps({ params }) {
86 ...organizations.map((e) => ({ ...e, type: "Organization" })),
91 ...organizations.map((e) => ({ ...e, type: "Organization" })),
87 ...podcasts.map((e) => ({ ...e, type: "Podcast" })),
92 ...podcasts.map((e) => ({ ...e, type: "Podcast" })),
88 ...marketplaces.map((e) => ({ ...e, type: "Marketplace" })),
93 ...marketplaces.map((e) => ({ ...e, type: "Marketplace" })),
94+ ...articles.map((e) => ({ ...e, type: "Article" })),
89 ].filter((e) => e.title === post[feat].title)?.[0];
95 ].filter((e) => e.title === post[feat].title)?.[0];
90 post[feat].image = post[feat]?.image || matchedPost?.image || null;
96 post[feat].image = post[feat]?.image || matchedPost?.image || null;
91 post[feat].type = matchedPost?.type || "Podcast";
97 post[feat].type = matchedPost?.type || "Podcast";
pages/index.js+21−14
@@ -68,20 +68,20 @@ export default function Home({
68 </h2>
68 </h2>
69
69
70 <div className="space-y-4">
70 <div className="space-y-4">
71− <p className="max-w-prose">
71+ <p className="max-w-prose">
72− We realized that in order to fix the internet, we had to build a new
72+ We realized that in order to fix the internet, we had to build a new
73− computer from scratch. Good thing we started over a decade ago.
73+ computer from scratch. Good thing we started over a decade ago.
74− </p>
74+ </p>
75− <p className="max-w-prose pb-4">
75+ <p className="max-w-prose pb-4">
76− Today, Urbit is a real system with thousands of users that are
76+ Today, Urbit is a real system with thousands of users that are
77− building all kinds of communities, software, DAOs, and more. And
77+ building all kinds of communities, software, DAOs, and more. And
78− it’s getting better every day.{" "}
78+ it’s getting better every day.{" "}
79− </p>
79+ </p>
80− <Link href="/overview" passHref>
80+ <Link href="/overview" passHref>
81− <a className="button-lg bg-green-400 text-white type-ui max-w-fit">
81+ <a className="button-lg bg-green-400 text-white type-ui max-w-fit">
82− Read the Overview
82+ Read the Overview
83− </a>
83+ </a>
84− </Link>
84+ </Link>
85 </div>
85 </div>
86
86
87 </div>
87 </div>
@@ -302,6 +302,12 @@ export async function getStaticProps() {
302 "date"
302 "date"
303 )[0];
303 )[0];
304
304
305+ const articles = getAllPosts(
306+ ["title", "image", "date", "podcast", "slug"],
307+ "articles",
308+ "date"
309+ );
310+
305 const marketplaces = getAllPosts(["title", "image", "slug"], "marketplaces");
311 const marketplaces = getAllPosts(["title", "image", "slug"], "marketplaces");
306 const podcasts = getAllPosts(
312 const podcasts = getAllPosts(
307 ["title", "image", "date", "podcast", "slug"],
313 ["title", "image", "date", "podcast", "slug"],
@@ -335,6 +341,7 @@ export async function getStaticProps() {
335 ["featured-1", "featured-2", "featured-3"].forEach((feat) => {
341 ["featured-1", "featured-2", "featured-3"].forEach((feat) => {
336 if (ecosystem?.[feat]) {
342 if (ecosystem?.[feat]) {
337 const matchedPost = [
343 const matchedPost = [
344+ ...articles.map((e) => ({ ...e, type: "Article" })),
338 ...applications.map((e) => ({ ...e, type: "Application" })),
345 ...applications.map((e) => ({ ...e, type: "Application" })),
339 ...organizations.map((e) => ({ ...e, type: "Organization" })),
346 ...organizations.map((e) => ({ ...e, type: "Organization" })),
340 ...podcasts.map((e) => ({ ...e, type: "Podcast" })),
347 ...podcasts.map((e) => ({ ...e, type: "Podcast" })),