@@ -6,7 +6,6 @@ import {
66 SingleColumn,
77 Section,
88 IntraNav,
9− Markdown,
109 getAllPosts,
1110 generateDisplayDate,
1211 formatDate,
@@ -14,12 +13,15 @@ import {
1413import Header from "../../components/Header";
1514import Footer from "../../components/Footer";
1615import Sidebar from "../../components/Sidebar";
16+import EcosystemSidebar from "../../components/ecosystem/EcosystemSidebar";
17+import SpotlightArchive from "../../components/ecosystem/SpotlightArchive";
1718import Post from "../../components/ecosystem/Post";
1819import classnames from "classnames";
1920import Link from "next/link";
2021import fs from "fs";
2122import path from "path";
2223import Contact from "../../components/Contact";
24+import { matchEcosystemPost } from "../../lib/lib";
2325
2426export default function Ecosystem({
2527 search,
@@ -36,16 +38,24 @@ export default function Ecosystem({
3638
3739 const { type } = router.query;
3840
41+ // Route ?type=archive to below component;
42+ // otherwise, show directory of ecosystem type
43+ // If no param, we show the spotlight for the month
3944 if (type === "archive") {
4045 return <SpotlightArchive posts={posts} search={search} />;
4146 }
4247
48+ const spotlight = type === undefined;
49+
4350 let title;
4451 if (type) {
4552 switch (type) {
4653 case "applications":
4754 title = "Applications • Ecosystem";
4855 break;
56+ case "applications":
57+ title = "Groups • Ecosystem";
58+ break;
4959 case "marketplaces":
5060 title = "Marketplaces • Ecosystem";
5161 break;
@@ -88,21 +98,19 @@ export default function Ecosystem({
8898 </Sidebar>
8999 <div
90100 className={classnames("w-full", {
91− "max-w-prose": type === undefined,
101+ "max-w-prose": spotlight,
92102 })}
93103 >
94− {type === undefined && (
95− <h3>{`Ecosystem Spotlight - ${post.title}`}</h3>
96− )}
97− {type === undefined && (
104+ {/* Default layout */}
105+ {spotlight && (
98106 <>
107+ <h3>{`Ecosystem Spotlight - ${post.title}`}</h3>
99108 <Post post={post} />
100109 <Link href="/ecosystem/?type=archive" passHref>
101110 <a className="button-lg bg-wall-600 text-white w-fit">
102111 Spotlight Archive
103112 </a>
104113 </Link>
105−
106114 <div className="pt-36">
107115 <h3 className="pb-2">Urbit Monthly</h3>
108116 <p className="pb-8">
@@ -113,126 +121,36 @@ export default function Ecosystem({
113121 </div>
114122 </>
115123 )}
116−
124+ {/* Ecosystem type layouts */}
117125 <div
118126 className={classnames("grid gap-12 w-full", {
119127 "grid-cols-2 md:grid-cols-3": type == "organizations" || type == "marketplaces",
120128 "grid-cols-1": type === "podcasts" || type === "articles",
121129 "grid-cols-1 md:grid-cols-1 lg:grid-cols-1 xl:grid-cols-2":
122130 type === "applications" || type === "groups",
123− hidden: type === undefined,
131+ hidden: spotlight,
124132 })}
125133 >
126134 {type === "organizations" &&
127− organizations.map((org) => (
128− <Link href={`/organizations/${org.slug}`}>
129− <div className="flex flex-col space-y-4 items-center cursor-pointer hover:opacity-90">
130− <img className="w-36 rounded-xl" src={org.image} />
131− <p className="text-center font-bold">{org.title}</p>
132− </div>
133− </Link>
134− ))}
135+ <Organizations dir={organizations} />
136+ }
135137
136138 {type === "applications" &&
137− applications.map((app) => (
138− <Link href={`/applications/${app.ship}/${app.slug}`}>
139− <div className="flex space-x-4 items-center cursor-pointer hover:opacity-90">
140− <div
141− className="h-36 w-36 rounded-xl items-center justify-center shrink-0 overflow-hidden"
142− style={{
143− backgroundColor: app?.bgColor || "rgba(0,0,0,0)",
144− }}
145− >
146− {app?.image && (
147− <img
148− className="items-center h-36 w-36"
149− src={app.image}
150− />
151− )}
152− </div>
153− <div className="">
154− <p className="text-left font-bold">{app.title}</p>
155− <p className="text-left text-wall-400 font-light">
156− {app.description}
157− </p>
158− </div>
159− </div>
160− </Link>
161− ))}
139+ <Applications dir={applications} />
140+ }
162141 {type === "groups" &&
163− groups.map((group) => (
164− <Link href={`/groups/${group.ship}/${group.slug}`}>
165− <div className="flex space-x-4 items-center cursor-pointer hover:opacity-90">
166− <div
167− className="h-36 w-36 rounded-xl items-center justify-center shrink-0 overflow-hidden"
168− style={{
169− backgroundColor: group?.tile?.startsWith("#")
170− ? group?.tile
171− : "rgba(0,0,0,0)",
172− }}
173− >
174− {!group?.tile?.startsWith("#") && (
175− <img
176− className="items-center h-36 w-36"
177− src={group.tile}
178− />
179− )}
180− </div>
181− <div className="">
182− <p className="text-left font-bold">{group.title}</p>
183− <p className="text-left text-wall-400 font-light">
184− {group.description}
185− </p>
186− </div>
187− </div>
188− </Link>
189− ))}
142+ <Groups dir={groups} />
143+ }
190144 {type === "marketplaces" &&
191− marketplaces.map((market) => (
192− <Link href={`/marketplaces/${market.slug}`}>
193− <div className="flex flex-col space-y-4 justify-center items-center cursor-pointer hover:opacity-90">
194− <img className="w-36 rounded-xl" src={market.image} />
195− <p className="text-center font-bold">{market.title}</p>
196− </div>
197− </Link>
198− ))}
145+ <Marketplaces dir={marketplaces} />
146+ }
199147 {type === "podcasts" &&
200− podcasts.map((pod) => (
201− <Link href={`/podcasts/${pod.slug}`}>
202− <div className="flex cursor-pointer space-x-4 items-center hover:opacity-90">
203− <img className="w-28" src={pod.image} />
204− <div className="flex flex-col space-y-2 min-w-0">
205− <p className="font-bold">{pod.podcast}</p>
206− <p className="min-w-0 min-h-0 leading-5 ">
207− {pod.title}
208− </p>
209− <p className="text-wall-400">
210− {formatDate(generateDisplayDate(pod.date))}
211− </p>
212− </div>
213− </div>
214− </Link>
215− ))}
148+ <Podcasts dir={podcasts} />
149+ }
216150
217151 {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−
152+ <Articles dir={articles} />
153+ }
236154 </div>
237155 </div>
238156 </div>
@@ -243,153 +161,136 @@ export default function Ecosystem({
243161 );
244162}
245163
246−function ActiveLink({ children, href, className, currentPath }) {
247− // special casing this behaviour which overloads one nav
248− const archive = Boolean(
249− currentPath === "/ecosystem?type=archive" && href === "/ecosystem"
250− );
251− const activeClassName = classnames({
252− "text-wall-800": currentPath === href || archive,
253− "text-wall-400": currentPath !== href && !archive,
254− });
255−
256− return (
257− <Link href={href} passHref>
258− <a className={`${className} ${activeClassName}`}>{children}</a>
164+const Organizations = ({ dir }) => {
165+ return dir.map((org) => (
166+ <Link href={`/organizations/${org.slug}`}>
167+ <div className="flex flex-col space-y-4 items-center cursor-pointer hover:opacity-90">
168+ <img className="w-36 rounded-xl" src={org.image} />
169+ <p className="text-center font-bold">{org.title}</p>
170+ </div>
259171 </Link>
260− );
172+ ))
261173}
262174
263−function EcosystemSidebar() {
264− const router = useRouter();
265− let currentPath = router.asPath;
266− return (
267− <div className="flex flex-col space-y-4 pr-24">
268− <ActiveLink
269− currentPath={currentPath}
270− className="type-ui"
271− href="/ecosystem"
272− >
273− Spotlight
274− </ActiveLink>
275− <ActiveLink
276− currentPath={currentPath}
277− className="type-ui"
278− href="/ecosystem?type=organizations"
279− >
280− Organizations
281− </ActiveLink>
282− <ActiveLink
283− currentPath={currentPath}
284− className="type-ui"
285− href="/ecosystem?type=applications"
286− >
287− Applications
288− </ActiveLink>
289− <ActiveLink
290− currentPath={currentPath}
291− className="type-ui"
292− href="/ecosystem?type=groups"
293− >
294− Groups
295− </ActiveLink>
296− <ActiveLink
297− currentPath={currentPath}
298− className="type-ui"
299− href="/ecosystem?type=marketplaces"
300− >
301− Marketplaces
302− </ActiveLink>
303− <ActiveLink
304− currentPath={currentPath}
305− className="mr-5 type-ui"
306− href="/ecosystem?type=podcasts"
307− >
308− Podcasts
309− </ActiveLink>
175+const Applications = ({ dir }) => {
176+ return dir.map((app) => (
177+ <Link href={`/applications/${app.ship}/${app.slug}`}>
178+ <div className="flex space-x-4 items-center cursor-pointer hover:opacity-90">
179+ <div
180+ className="h-36 w-36 rounded-xl items-center justify-center shrink-0 overflow-hidden"
181+ style={{
182+ backgroundColor: app?.bgColor || "rgba(0,0,0,0)",
183+ }}
184+ >
185+ {app?.image && (
186+ <img
187+ className="items-center h-36 w-36"
188+ src={app.image}
189+ />
190+ )}
191+ </div>
192+ <div className="">
193+ <p className="text-left font-bold">{app.title}</p>
194+ <p className="text-left text-wall-400 font-light">
195+ {app.description}
196+ </p>
197+ </div>
198+ </div>
199+ </Link>
200+ ))
201+}
310202
311− <ActiveLink
312− currentPath={currentPath}
313− className="mr-5 type-ui"
314− href="/ecosystem?type=articles"
315− >
316− Articles
317− </ActiveLink>
203+const Groups = ({ dir }) => {
204+ return dir.map((group) => (
205+ <Link href={`/groups/${group.ship}/${group.slug}`}>
206+ <div className="flex space-x-4 items-center cursor-pointer hover:opacity-90">
207+ <div
208+ className="h-36 w-36 rounded-xl items-center justify-center shrink-0 overflow-hidden"
209+ style={{
210+ backgroundColor: group?.tile?.startsWith("#")
211+ ? group?.tile
212+ : "rgba(0,0,0,0)",
213+ }}
214+ >
215+ {!group?.tile?.startsWith("#") && (
216+ <img
217+ className="items-center h-36 w-36"
218+ src={group.tile}
219+ />
220+ )}
221+ </div>
222+ <div className="">
223+ <p className="text-left font-bold">{group.title}</p>
224+ <p className="text-left text-wall-400 font-light">
225+ {group.description}
226+ </p>
227+ </div>
228+ </div>
229+ </Link>
230+ ))
231+}
318232
233+const Marketplaces = ({ dir }) => {
234+ return dir.map((market) => (
235+ <Link href={`/marketplaces/${market.slug}`}>
236+ <div className="flex flex-col space-y-4 justify-center items-center cursor-pointer hover:opacity-90">
237+ <img className="w-36 rounded-xl" src={market.image} />
238+ <p className="text-center font-bold">{market.title}</p>
239+ </div>
240+ </Link>
241+ ))
242+}
319243
320− </div>
321− );
244+const Podcasts = ({ dir }) => {
245+ return dir.map((pod) => (
246+ <Link href={`/podcasts/${pod.slug}`}>
247+ <div className="flex cursor-pointer space-x-4 items-center hover:opacity-90">
248+ <img className="w-28" src={pod.image} />
249+ <div className="flex flex-col space-y-2 min-w-0">
250+ <p className="font-bold">{pod.podcast}</p>
251+ <p className="min-w-0 min-h-0 leading-5 ">
252+ {pod.title}
253+ </p>
254+ <p className="text-wall-400">
255+ {formatDate(generateDisplayDate(pod.date))}
256+ </p>
257+ </div>
258+ </div>
259+ </Link>
260+ ))
322261}
323262
324−function SpotlightArchive({ posts, search }) {
325− const post = {
326− title: "Spotlight Archive",
327− description: "The archive of ecosystem spotlights.",
328− };
329− return (
330− <Container>
331− <Head>
332− <title>Archive • Spotlight • Urbit</title>
333− {Meta(post)}
334− </Head>
335− <IntraNav ourSite="https://urbit.org" search={search} />
336− <SingleColumn>
337− <Header search={search} />
338− <Section>
339− <div className="measure">
340− <h1>Ecosystem</h1>
341− </div>
342− </Section>
343− <Section className="flex">
344− <Sidebar>
345− <EcosystemSidebar />
346− </Sidebar>
347− <div>
348− <h3 className="mb-12">Spotlight Archive</h3>
349− {posts.map((post) => {
350− return (
351− <div key={post.slug} className="mb-24 cursor-pointer">
352− <Link href={`/ecosystem/spotlight/${post.slug}`}>
353− <div>
354− <h3 className="text-green-400">{post.title}</h3>
355− <div className="type-ui mt-2">
356− {["featured-1", "featured-2", "featured-3"].map(
357− (feat, i) => {
358− if (post?.[feat]) {
359− return (
360− <>
361− {i ? ", " : ""}
362− <span>{post[feat].title}</span>
363− </>
364− );
365− }
366− }
367− )}
368− </div>
369− </div>
370− </Link>
371− </div>
372− );
373− })}
374− </div>
375− </Section>
376− </SingleColumn>
377− <Footer />
378− </Container>
379− );
263+const Articles = ({ dir }) => {
264+ return dir.map((article) => (
265+ <Link href={`/articles/${article.slug}`}>
266+ <div className="flex cursor-pointer space-x-4 items-center hover:opacity-90">
267+ <img className="w-28" src={article.image} />
268+ <div className="flex flex-col space-y-2 min-w-0">
269+ <p className="font-bold">{article.publication}</p>
270+ <p className="min-w-0 min-h-0 leading-5 ">
271+ {article.title}
272+ </p>
273+ <p className="text-wall-400">
274+ {formatDate(generateDisplayDate(article.date))}
275+ </p>
276+ </div>
277+ </div>
278+ </Link>
279+ ))
380280}
381281
382−export async function getStaticProps({}) {
282+
283+export async function getStaticProps() {
383284 const posts = getAllPosts(
384285 ["title", "date", "slug", "featured-1", "featured-2", "featured-3"],
385286 "ecosystem/spotlight",
386287 "date"
387288 );
388− let post = getAllPosts(
289+ const post = matchEcosystemPost(getAllPosts(
389290 ["title", "date", "featured-1", "featured-2", "featured-3"],
390291 "ecosystem/spotlight",
391292 "date"
392− )[0];
293+ )[0]);
393294 const marketplaces = getAllPosts(["title", "image", "slug"], "marketplaces");
394295 const podcasts = getAllPosts(
395296 ["title", "image", "date", "podcast", "slug"],
@@ -444,26 +345,6 @@ export async function getStaticProps({}) {
444345 return nameA < nameB ? -1 : 1;
445346 });
446347
447− ["featured-1", "featured-2", "featured-3"].forEach((feat) => {
448− if (post?.[feat]) {
449− const matchedPost = [
450− ...applications.map((e) => ({ ...e, type: "Application" })),
451− ...organizations.map((e) => ({ ...e, type: "Organization" })),
452− ...podcasts.map((e) => ({ ...e, type: "Podcast" })),
453− ...articles.map((e) => ({ ...e, type: "Article" })),
454− ...marketplaces.map((e) => ({ ...e, type: "Marketplace" })),
455− ].filter((e) => e.title === post[feat].title)?.[0];
456− post[feat].image = post[feat]?.image || matchedPost?.image || null;
457− post[feat].type = matchedPost?.type || "Podcast";
458− post[feat].matchedPost = matchedPost || null;
459− post[feat].content = JSON.stringify(
460− Markdown.parse({
461− post: { content: post[feat].content },
462− })
463− );
464− }
465− });
466−
467348 return {
468349 props: {
469350 posts,