on master help sign in
Merge pull request #1418 from urbit/mp/groups

feat: dynamically rendered pages for groups, urbit ids, applications, and guides

commit8802ea6453f0a474620dc55ef7c8fef6669ba2c8 authorGordon <gordon.cieplak@urbit.org> dateFri 29 Apr 2022, 18:31 (4 years ago) parent37473c67b7a34b
69 files changed+2443−132
.prettierrc+5 components/Contact.js+5−67 components/CopyLink.js+63 components/GuideColumn.js+8 components/Markdown.js+2−2 components/Meta.js+4−2 components/PostPreview.js+5−3 components/ResourceCard.js+13 components/Search.js+58−1 components/Sigil.js+36 components/gateway/Description.js+21 components/gateway/Gateway404.js+27 components/gateway/GatewayHeader.js+29 components/gateway/Image.js+64 components/gateway/MetadataBlock.js+8 components/gateway/MetadataLink.js+14 content/applications/_index.md+3 content/applications/~dister-fabnev-hinmur/escape.md+10 content/applications/~dister-fabnev-hinmur/handshake.md+10 content/applications/~mister-dozzod-dalten/gora.md+9 content/applications/~paldev/face.md+9 content/applications/~paldev/fafa.md+9 content/applications/~paldev/pals.md+9 content/applications/~paldev/picture.md+9 content/applications/~paldev/rumors.md+9 content/applications/~paldev/verse.md+9 content/applications/~pocwet/docs.md+10 content/groups/_index.md+3 content/groups/~bitbet-bolbel/urbit-community.md+14 content/groups/~fabled-faster/new-york.md+10 content/groups/~fabled-faster/structure.md+10 content/groups/~fabled-faster/wind.md+10 content/groups/~hatryx-lastud/other-life.md+10 content/groups/~ladtem-filmyr/urbit-ny-takeover.md+10 content/groups/~matwet/networked-subject.md+11 content/groups/~middev/the-forge.md+10 content/groups/~napmer-rilnut/wet-brain.md+10 content/groups/~natnex-ronret/door-link.md+10 content/guides/getting-an-urbit-id.md+46 content/guides/installing-applications.md+39 content/guides/joining-groups.md+53 content/ids/battus-datsun.md+10 content/ids/bitbet-bolbel.md+4 content/ids/haddef-sigwen.md+10 content/ids/ravmel-ropdyl.md+8 lib/cache.js+1−1 lib/hooks.js+12 lib/lib.js+4−2 package-lock.json+446−21 package.json+4−1 pages/applications/[[...application]].js+166 pages/applications/submit.js+141 pages/blog.js+7−4 pages/blog/[slug].js+7−4 pages/events/[slug].js+1−1 pages/grants.js+2−6 pages/grants/[slug].js+7−4 pages/groups/[[...group]].js+145 pages/groups/submit.js+150 pages/guides/[slug].js+75 pages/guides/installing-applications.js+106 pages/ids/[id].js+242 pages/ids/submit.js+163 pages/index.js+2−1 pages/media.js+2−1 pages/media/[slug].js+7−4 pages/updates.js+7−4 pages/updates/[slug].js+7−3 styles/globals.css+3
.prettierrcnew file+5
@@ -0,0 +1,5 @@
1+{
2+ "tabWidth": 2,
3+ "useTabs": false,
4+ "printWidth": 80
5+}
components/Contact.js+5−67
@@ -1,62 +1,10 @@
11import NewsletterSignup from "./NewletterSignup";
22import { name, contact } from "../lib/constants";
3−import { useCopyToClipboard } from "../lib/hooks";
3+import Link from "next/link";
44
55export default function Contact({ emphasize }) {
6− const [copyStatus, copy] = useCopyToClipboard(contact.urbitCommunity);
7−
86 const linkText = emphasize ? "text-green-400" : "text-wall-600";
97 const border = emphasize ? "border-green-400" : "border-wall-600";
10− const washedBg = emphasize ? "bg-green-100" : "bg-wall-100";
11− const iconColor = emphasize ? "#00B171" : "#24221E";
12−
13− const buttonIcon =
14− copyStatus === "inactive" ? (
15− <svg
16− width="16"
17− height="16"
18− viewBox="0 0 16 16"
19− fill="none"
20− xmlns="http://www.w3.org/2000/svg"
21− >
22− <path
23− fillRule="evenodd"
24− clipRule="evenodd"
25− d="M3.2 1.6C3.2 0.716344 3.91634 0 4.8 0H11.3615L14.9333 2.38124V12.2667C14.9333 13.1503 14.217 13.8667 13.3333 13.8667H12.8V14.4C12.8 15.2837 12.0837 16 11.2 16H2.66667C1.78301 16 1.06667 15.2837 1.06667 14.4V3.73333C1.06667 2.84968 1.78301 2.13333 2.66667 2.13333H3.2V1.6ZM3.2 3.2H2.66667C2.37211 3.2 2.13333 3.43878 2.13333 3.73333V14.4C2.13333 14.6946 2.37211 14.9333 2.66667 14.9333H11.2C11.4946 14.9333 11.7333 14.6946 11.7333 14.4V13.8667H4.8C3.91634 13.8667 3.2 13.1503 3.2 12.2667V3.2Z"
26− fill={iconColor}
27− />
28− </svg>
29− ) : copyStatus === "copied" ? (
30− <svg
31− width="16"
32− height="16"
33− viewBox="0 0 16 16"
34− fill="none"
35− xmlns="http://www.w3.org/2000/svg"
36− >
37− <path
38− fillRule="evenodd"
39− clipRule="evenodd"
40− d="M0 8C0 3.58172 3.58172 0 8 0C12.4183 0 16 3.58172 16 8C16 12.4183 12.4183 16 8 16C3.58172 16 0 12.4183 0 8ZM7.54325 11.4247L12.1498 5.66647L11.3169 5.00013L7.39013 9.90859L4.60812 7.59025L3.92526 8.40969L7.54325 11.4247Z"
41− fill={iconColor}
42− />
43− </svg>
44− ) : (
45− <svg
46− width="16"
47− height="16"
48− viewBox="0 0 16 16"
49− fill="none"
50− xmlns="http://www.w3.org/2000/svg"
51− >
52− <path
53− fillRule="evenodd"
54− clipRule="evenodd"
55− d="M0 8C0 3.58172 3.58172 0 8 0C12.4183 0 16 3.58172 16 8C16 12.4183 12.4183 16 8 16C3.58172 16 0 12.4183 0 8ZM10.8229 11.5772L8.00004 8.75429L5.17716 11.5772L4.42292 10.8229L7.24579 8.00004L4.42292 5.17716L5.17716 4.42292L8.00004 7.24579L10.8229 4.42292L11.5772 5.17716L8.75429 8.00004L11.5772 10.8229L10.8229 11.5772Z"
56− fill={iconColor}
57− />
58− </svg>
59− );
608
619 return (
6210 <div>
@@ -79,21 +27,11 @@ export default function Contact({ emphasize }) {
7927 Github
8028 </a>
8129 </h4>
82− <h4 className="mt-4 text-wall-500 flex items-baseline flex-wrap">
30+ <h4 className="mt-6 text-wall-500 flex-wrap">
8331 Boot Urbit and join{" "}
84− <div className="md:ml-2 rounded-lg flex items-stretch">
85− <code
86− className={`rounded-l-lg px-2 md:px-4 py-2 ${washedBg} flex items-center h-full ${linkText}`}
87− >
88− {contact.urbitCommunity}
89− </code>
90− <button
91− className={`rounded-r-lg px-4 ml-0.5 flex-1 flex justify-center items-center ${washedBg}`}
92− onClick={() => copy()}
93− >
94− {buttonIcon}
95− </button>
96− </div>
32+ <Link href={`/groups/${contact.urbitCommunity}`}>
33+ <a className={linkText}>Urbit Community</a>
34+ </Link>
9735 </h4>
9836 </div>
9937 );
components/CopyLink.jsnew file+63
@@ -0,0 +1,63 @@
1+import { useCopyToClipboard } from "../lib/hooks";
2+
3+export default function CopyLink({ content, className }) {
4+ const [copyStatus, copy] = useCopyToClipboard(content);
5+
6+ const buttonIcon =
7+ copyStatus === "inactive" ? (
8+ <svg
9+ width="16"
10+ height="16"
11+ viewBox="0 0 16 16"
12+ fill="none"
13+ xmlns="http://www.w3.org/2000/svg"
14+ >
15+ <path
16+ fillRule="evenodd"
17+ clipRule="evenodd"
18+ d="M3.2 1.6C3.2 0.716344 3.91634 0 4.8 0H11.3615L14.9333 2.38124V12.2667C14.9333 13.1503 14.217 13.8667 13.3333 13.8667H12.8V14.4C12.8 15.2837 12.0837 16 11.2 16H2.66667C1.78301 16 1.06667 15.2837 1.06667 14.4V3.73333C1.06667 2.84968 1.78301 2.13333 2.66667 2.13333H3.2V1.6ZM3.2 3.2H2.66667C2.37211 3.2 2.13333 3.43878 2.13333 3.73333V14.4C2.13333 14.6946 2.37211 14.9333 2.66667 14.9333H11.2C11.4946 14.9333 11.7333 14.6946 11.7333 14.4V13.8667H4.8C3.91634 13.8667 3.2 13.1503 3.2 12.2667V3.2Z"
19+ fill="#ffffff"
20+ />
21+ </svg>
22+ ) : copyStatus === "copied" ? (
23+ <svg
24+ width="16"
25+ height="16"
26+ viewBox="0 0 16 16"
27+ fill="none"
28+ xmlns="http://www.w3.org/2000/svg"
29+ >
30+ <path
31+ fillRule="evenodd"
32+ clipRule="evenodd"
33+ d="M0 8C0 3.58172 3.58172 0 8 0C12.4183 0 16 3.58172 16 8C16 12.4183 12.4183 16 8 16C3.58172 16 0 12.4183 0 8ZM7.54325 11.4247L12.1498 5.66647L11.3169 5.00013L7.39013 9.90859L4.60812 7.59025L3.92526 8.40969L7.54325 11.4247Z"
34+ fill="#ffffff"
35+ />
36+ </svg>
37+ ) : (
38+ <svg
39+ width="16"
40+ height="16"
41+ viewBox="0 0 16 16"
42+ fill="none"
43+ xmlns="http://www.w3.org/2000/svg"
44+ >
45+ <path
46+ fillRule="evenodd"
47+ clipRule="evenodd"
48+ d="M0 8C0 3.58172 3.58172 0 8 0C12.4183 0 16 3.58172 16 8C16 12.4183 12.4183 16 8 16C3.58172 16 0 12.4183 0 8ZM10.8229 11.5772L8.00004 8.75429L5.17716 11.5772L4.42292 10.8229L7.24579 8.00004L4.42292 5.17716L5.17716 4.42292L8.00004 7.24579L10.8229 4.42292L11.5772 5.17716L8.75429 8.00004L11.5772 10.8229L10.8229 11.5772Z"
49+ fill="#ffffff"
50+ />
51+ </svg>
52+ );
53+
54+ return (
55+ <button
56+ className={`bg-wall-600 p-4 rounded-xl button-lg text-white flex items-center justify-center space-x-4 ${className}`}
57+ onClick={() => copy()}
58+ >
59+ <h4 className="">{copyStatus === "copied" ? "Copied" : "Copy Link"}</h4>
60+ {buttonIcon}
61+ </button>
62+ );
63+}
components/GuideColumn.jsnew file+8
@@ -0,0 +1,8 @@
1+// Guide Column
2+export default function GuideColumn({ children }) {
3+ return (
4+ <div className="flex flex-col w-full items-center py-20 max-w-screen-2xl">
5+ {children}
6+ </div>
7+ );
8+}
components/Markdown.js+2−2
@@ -43,11 +43,11 @@ const options = {
4343};
4444
4545// Converts markdown strings into markdown HTML/React components
46−export default async function Markdown({ post }) {
46+export default async function Markdown({ post }, disablePlugins) {
4747 const result = await remark()
4848 .use(remarkParse, options)
4949 .use(remarkprism, {
50− plugins: ["show-invisibles"],
50+ plugins: !disablePlugins ? ["show-invisibles"] : [],
5151 })
5252 .use(gfm)
5353 .use(slug)
components/Meta.js+4−2
@@ -1,4 +1,4 @@
1−export default function Meta(post) {
1+export default function Meta(post, disableImage) {
22 const author = post?.extra?.author || "Urbit";
33 const title = post?.title ? `${post.title} - ` : "";
44 const description =
@@ -19,7 +19,9 @@ export default function Meta(post) {
1919 <meta name="og:description" content={description} key="description" />
2020 <meta name="description" content={description} />
2121 <meta name="author" content={author} key="author" />
22− <meta name="twitter:image" content={image} key="image" />
22+ {!disableImage && (
23+ <meta name="twitter:image" content={image} key="image" />
24+ )}
2325 <link
2426 rel="alternative"
2527 type="application/rss+xml"
components/PostPreview.js+5−3
@@ -28,9 +28,11 @@ export default function PostPreview(props) {
2828 </div>
2929 ) : null}
3030 {props.post.extra.ship ? (
31− <div className="type-sub-bold text-wall-500 font-mono">
32− {props.post.extra.ship}
33− </div>
31+ <Link href={`/ids/${props.post.extra.ship}`}>
32+ <a className="type-sub-bold text-wall-500 font-mono">
33+ {props.post.extra.ship}
34+ </a>
35+ </Link>
3436 ) : null}
3537 </div>
3638 <div className="text-wall-500 type-sub mt-1">{formatDate(date)}</div>
components/ResourceCard.jsnew file+13
@@ -0,0 +1,13 @@
1+import Link from "next/link";
2+import GatewayImage from "./gateway/Image";
3+
4+const ResourceCard = ({ type, shortcode, title, color, image }) => (
5+ <Link href={`/${type}s/${shortcode}`}>
6+ <div className="cursor-pointer rounded-xl bg-wall-100 p-4 flex items-center space-x-4 mr-2 my-1">
7+ <GatewayImage color={color} image={image || color} size={50} />
8+ <a className="font-semibold">{title}</a>
9+ </div>
10+ </Link>
11+);
12+
13+export default ResourceCard;
components/Search.js+58−1
@@ -3,6 +3,8 @@ import { glossary } from "../lib/glossary";
33import { withRouter } from "next/router";
44import debounce from "lodash.debounce";
55import Downshift from "downshift";
6+import ob from "urbit-ob";
7+import Sigil from "../components/Sigil";
68
79class Search extends Component {
810 constructor(props) {
@@ -14,6 +16,7 @@ class Search extends Component {
1416 this.onInputValueChange = this.onInputValueChange.bind(this);
1517 this.onSelect = this.onSelect.bind(this);
1618 this.glossarySearch = this.glossarySearch.bind(this);
19+ this.patpSearch = this.patpSearch.bind(this);
1720 }
1821
1922 searchEndpoint(query) {
@@ -26,6 +29,14 @@ class Search extends Component {
2629 });
2730 }
2831
32+ patpSearch(query) {
33+ return (
34+ (ob.isValidPatp(`~${deSig(query.toLowerCase())}`) &&
35+ `~${deSig(query.toLowerCase())}`.length < 15) ||
36+ (!isNaN(query) && query <= 4294967295)
37+ );
38+ }
39+
2940 onSelect(item) {
3041 if (item.slug) {
3142 this.props.router.push(item.slug);
@@ -50,12 +61,30 @@ class Search extends Component {
5061 content: item,
5162 }));
5263
64+ const patp = this.patpSearch(query)
65+ ? !isNaN(query)
66+ ? ob.patp(query)
67+ : ob.patp(ob.patp2dec(`~${deSig(query)}`))
68+ : null;
69+
70+ const patpResult = this.patpSearch(query)
71+ ? [
72+ {
73+ type: "PATP",
74+ content: {
75+ patp: patp,
76+ slug: `/ids/${patp}`,
77+ },
78+ },
79+ ]
80+ : [];
81+
5382 const glossaryResults = this.glossarySearch(query).map((item) => ({
5483 type: "GLOSSARY_RESULT",
5584 content: item,
5685 }));
5786
58− const list = [...glossaryResults, ...results];
87+ const list = [...patpResult, ...glossaryResults, ...results];
5988
6089 this.setState({ results: list });
6190 });
@@ -117,6 +146,30 @@ class Search extends Component {
117146 {isOpen
118147 ? state.results.map((item, index) => {
119148 const selected = highlightedIndex === index;
149+ if (item.type === "PATP") {
150+ return (
151+ <li
152+ className={`cursor-pointer p-2 flex space-x-2 items-center text-left w-full ${
153+ selected ? "bg-green-400" : ""
154+ }`}
155+ {...getItemProps({
156+ key: item.content.slug + "-" + index,
157+ index,
158+ item: item.content,
159+ selected: highlightedIndex === index,
160+ })}
161+ >
162+ <div className="rounded-md overflow-hidden">
163+ <Sigil
164+ patp={item.content.patp}
165+ size={25}
166+ icon
167+ />
168+ </div>
169+ <p className="font-mono">{item.content.patp}</p>
170+ </li>
171+ );
172+ }
120173 if (item.type === "GLOSSARY_RESULT") {
121174 return (
122175 <li
@@ -210,4 +263,8 @@ class Search extends Component {
210263 }
211264}
212265
266+function deSig(string) {
267+ return string.startsWith("~") ? string.substring(1) : string;
268+}
269+
213270export default withRouter(Search);
components/Sigil.jsnew file+36
@@ -0,0 +1,36 @@
1+import { sigil, reactRenderer } from "@tlon/sigil-js";
2+
3+export const foregroundFromBackground = (background) => {
4+ const rgb = {
5+ r: parseInt(background.slice(1, 3), 16),
6+ g: parseInt(background.slice(3, 5), 16),
7+ b: parseInt(background.slice(5, 7), 16),
8+ };
9+ const brightness = (299 * rgb.r + 587 * rgb.g + 114 * rgb.b) / 1000;
10+ const whiteBrightness = 255;
11+
12+ return whiteBrightness - brightness < 50 ? "black" : "white";
13+};
14+
15+export const Sigil = ({ patp, size, color = "#24201E", icon }) => {
16+ if (patp.length > 14) {
17+ return <div />;
18+ }
19+ const foreground = foregroundFromBackground(color);
20+ return (
21+ <div
22+ className={icon ? "p-1" : ""}
23+ style={{ backgroundColor: icon ? color || "black" : "transparent" }}
24+ >
25+ {sigil({
26+ patp: patp,
27+ renderer: reactRenderer,
28+ size: icon ? size / 2 : size,
29+ colors: [color, foreground],
30+ icon: icon || false,
31+ })}
32+ </div>
33+ );
34+};
35+
36+export default Sigil;
components/gateway/Description.jsnew file+21
@@ -0,0 +1,21 @@
1+import { decode } from "html-entities";
2+
3+const Description = ({ description, fallback, markdown }) => {
4+ return (
5+ (description !== fallback || markdown) && (
6+ <div className="flex flex-col space-y-4">
7+ <p className="font-bold text-wall-400">Description</p>
8+ {markdown ? (
9+ <div
10+ className="flex flex-col space-y-4"
11+ dangerouslySetInnerHTML={{ __html: decode(markdown) }}
12+ />
13+ ) : (
14+ <p>{description}</p>
15+ )}
16+ </div>
17+ )
18+ );
19+};
20+
21+export default Description;
components/gateway/Gateway404.jsnew file+27
@@ -0,0 +1,27 @@
1+import GatewayHeader from "./GatewayHeader";
2+import Container from "../Container";
3+import SingleColumn from "../SingleColumn";
4+import Section from "../Section";
5+import Link from "next/link";
6+
7+const Gateway404 = ({ type }) => {
8+ return (
9+ <Container>
10+ <SingleColumn>
11+ <Section narrow className="space-y-12">
12+ <GatewayHeader
13+ title="Error 404"
14+ error
15+ item={`No ${type} at this address`}
16+ />
17+ <hr className="text-wall-200" />
18+ <Link href="/">
19+ <a className="text-xl block font-semibold">Urbit.org</a>
20+ </Link>
21+ </Section>
22+ </SingleColumn>
23+ </Container>
24+ );
25+};
26+
27+export default Gateway404;
components/gateway/GatewayHeader.jsnew file+29
@@ -0,0 +1,29 @@
1+import GatewayImage from "./Image";
2+
3+const GatewayHeader = ({
4+ image,
5+ patp,
6+ color,
7+ subtitle,
8+ subtitleMono,
9+ error,
10+ title,
11+ item,
12+}) => {
13+ return (
14+ <div className="flex items-center space-x-4">
15+ <GatewayImage patp={patp} color={color} image={image} error={error} />
16+ <div className="flex flex-col">
17+ <h2>{title}</h2>
18+ {subtitle && (
19+ <p className={(subtitleMono ? "font-mono" : "font-sans") + " pb-1"}>
20+ {subtitle}
21+ </p>
22+ )}
23+ <p>{item}</p>
24+ </div>
25+ </div>
26+ );
27+};
28+
29+export default GatewayHeader;
components/gateway/Image.jsnew file+64
@@ -0,0 +1,64 @@
1+import Sigil from "../Sigil";
2+
3+const GatewayImage = ({
4+ error = false,
5+ patp = false,
6+ color = "black",
7+ image = "",
8+ size = 100,
9+}) => {
10+ if (error) {
11+ return (
12+ <div className="overflow-hidden rounded-xl">
13+ <svg
14+ width={size}
15+ height={size}
16+ viewBox="0 0 100 100"
17+ fill="none"
18+ xmlns="http://www.w3.org/2000/svg"
19+ >
20+ <rect width="100" height="100" rx="10" fill="#24201E" />
21+ <path
22+ d="M28.8906 27.9844C28.5019 28.0215 28.1699 28.2833 28.043 28.6524C27.914 29.0235 28.0137 29.4336 28.2969 29.7032L70.2969 71.7032C70.4844 71.8965 70.7402 72.0059 71.0098 72.0059C71.2773 72.0079 71.5352 71.9004 71.7246 71.709C71.9141 71.5176 72.0176 71.2598 72.0137 70.9903C72.0098 70.7227 71.8984 70.4668 71.7031 70.2813L66.2501 64.8283C69.8204 60.9181 72.0001 55.7073 72.0001 50.0003C72.0001 37.8618 62.1386 28.0003 50.0001 28.0003C44.2931 28.0003 39.0976 30.1956 35.1876 33.7658L29.7031 28.2813C29.4922 28.0645 29.1934 27.9551 28.8906 27.9844L28.8906 27.9844ZM50.0001 30C61.0566 30 70.0001 38.9435 70.0001 50C70.0001 55.17 68.0411 59.8575 64.8281 63.406L36.5936 35.1715C40.1405 31.9606 44.8336 29.9995 49.9996 29.9995L50.0001 30ZM32.1721 37.8125C31.842 37.8321 31.5452 38.0137 31.3752 38.2969C29.2424 41.6856 28.0002 45.7029 28.0002 49.9999C28.0002 62.1384 37.8617 71.9999 50.0002 71.9999C54.2951 71.9999 58.3147 70.7558 61.7032 68.6249C62.1739 68.3319 62.3165 67.7128 62.0235 67.2421C61.7305 66.7714 61.1114 66.6288 60.6407 66.9218C57.5587 68.8593 53.9122 69.9999 50.0002 69.9999C38.9437 69.9999 30.0002 61.0564 30.0002 49.9999C30.0002 46.0878 31.1233 42.4414 33.0627 39.3594C33.2697 39.043 33.2815 38.6387 33.092 38.3125C32.9045 37.9844 32.5491 37.7929 32.1721 37.8125L32.1721 37.8125Z"
23+ fill="white"
24+ />
25+ </svg>
26+ </div>
27+ );
28+ }
29+
30+ if (patp) {
31+ return (
32+ <div className="rounded-xl overflow-hidden">
33+ <Sigil patp={patp} size={size} color={color} />
34+ </div>
35+ );
36+ }
37+
38+ if (image?.startsWith("#")) {
39+ return (
40+ <div
41+ style={{ height: size, width: size, backgroundColor: image }}
42+ className="rounded-xl"
43+ />
44+ );
45+ }
46+
47+ return image ? (
48+ <div className="rounded-xl" style={{ backgroundColor: color }}>
49+ <img
50+ className="rounded-xl"
51+ src={image}
52+ style={{ height: size, width: size }}
53+ />
54+ </div>
55+ ) : (
56+ <img
57+ className="object-contain shadow-sm rounded-xl"
58+ style={{ height: size, width: size }}
59+ src="https://media.urbit.org/public-links/placeholder-image.png"
60+ />
61+ );
62+};
63+
64+export default GatewayImage;
components/gateway/MetadataBlock.jsnew file+8
@@ -0,0 +1,8 @@
1+const MetadataBlock = ({ title, content }) => (
2+ <div className="flex flex-col basis-1/2 mb-4 md:mb-0 flex-1">
3+ <p className="font-bold text-wall-400">{title}</p>
4+ <p className="font-mono">{content}</p>
5+ </div>
6+);
7+
8+export default MetadataBlock;
components/gateway/MetadataLink.jsnew file+14
@@ -0,0 +1,14 @@
1+import Link from "next/link";
2+
3+const MetadataLink = ({ title, href, content }) => (
4+ <div className="flex flex-col basis-1/2 min-w-0 flex-1">
5+ <p className="font-bold text-wall-400">{title}</p>
6+ <Link href={href}>
7+ <div className="flex truncate">
8+ <a className="type-ui truncate font-mono text-green-400">{content}</a>
9+ </div>
10+ </Link>
11+ </div>
12+);
13+
14+export default MetadataLink;
content/applications/_index.mdnew file+3
@@ -0,0 +1,3 @@
1++++
2+title = "Application Directory"
3++++
content/applications/~dister-fabnev-hinmur/escape.mdnew file+10
@@ -0,0 +1,10 @@
1++++
2+title = "EScape"
3+developer = "dister-fabnev-hinmur"
4+shortcode = "~dister-fabnev-hinmur/escape"
5+description = "EScape from Eternal September. The UI from Uqbar."
6+license = "MIT"
7+website = "https://uqbar.network"
8+bgColor = "#f2f3ef"
9+image = "https://escape-app-dist.s3.us-east-2.amazonaws.com/uqbar-logo.png"
10++++
content/applications/~dister-fabnev-hinmur/handshake.mdnew file+10
@@ -0,0 +1,10 @@
1++++
2+title = "Handshake"
3+developer = "~dister-fabnev-hinmur"
4+shortcode = "~dister-fabnev-hinmur/handshake"
5+description = "Confirm Urbit IDs via QR code."
6+license = "MIT"
7+website = "https://uqbar.network"
8+bgColor = "#df828b"
9+image = "https://static.thenounproject.com/png/2002633-200.png"
10++++
content/applications/~mister-dozzod-dalten/gora.mdnew file+9
@@ -0,0 +1,9 @@
1++++
2+title = "Gora - ピタゴラ装置"
3+developer = "Dalten"
4+shortcode = "~mister-dozzod-dalten/gora"
5+description = "A Proof of Attendance Protocol for Urbit"
6+license = "MIT"
7+website = "https://dalten.org"
8+bgColor = "#FCD977"
9++++
content/applications/~paldev/face.mdnew file+9
@@ -0,0 +1,9 @@
1++++
2+title = "face"
3+developer = "~paldev"
4+shortcode = "~paldev/face"
5+description = "see your friends"
6+license = "MIT"
7+website = "https://pal.dev/suite/face"
8+bgColor = "#3b5998"
9++++
content/applications/~paldev/fafa.mdnew file+9
@@ -0,0 +1,9 @@
1++++
2+title = "fafa"
3+developer = "~paldev"
4+shortcode = "~paldev/fafa"
5+description = "2fa otp authenticator app"
6+license = "MIT"
7+website = "https://pal.dev/suite/fafa"
8+bgColor = "#1a1a2a"
9++++
content/applications/~paldev/pals.mdnew file+9
@@ -0,0 +1,9 @@
1++++
2+title = "pals"
3+shortcode = "~paldev/pals"
4+developer = "~paldev"
5+description = "friendlist for peer discovery"
6+license = "MIT"
7+website = "https://pal.dev/suite/pals"
8+bgColor = "#99D3BD"
9++++
content/applications/~paldev/picture.mdnew file+9
@@ -0,0 +1,9 @@
1++++
2+title = "picture"
3+developer = "~paldev"
4+shortcode = "~paldev/picture"
5+description = "a picture frame for the garden"
6+license = "MIT"
7+website = "https://pal.dev/suite/picture"
8+bgColor = "#DDDDDD"
9++++
content/applications/~paldev/rumors.mdnew file+9
@@ -0,0 +1,9 @@
1++++
2+title = "rumors"
3+shortcode = "~paldev/rumors"
4+developer = "~paldev"
5+description = "anonymous gossip from friends of friends"
6+license = "MIT"
7+website = "https://pal.dev/suite/rumors"
8+bgColor = "#bb77dd"
9++++
content/applications/~paldev/verse.mdnew file+9
@@ -0,0 +1,9 @@
1++++
2+title = "verse"
3+developer = "~paldev"
4+shortcode = "~paldev/verse"
5+description = "a random Bible verse, every day"
6+license = "MIT"
7+website = "https://pal.dev/suite/verse"
8+bgColor = "#EEEEDD"
9++++
content/applications/~pocwet/docs.mdnew file+10
@@ -0,0 +1,10 @@
1++++
2+title = "Docs"
3+developer = "~pocwet"
4+shortcode = "~pocwet/docs"
5+description = "User and developer documentation for Urbit apps"
6+license = "MIT"
7+website = "https://github.com/tinnus-napbus/docs-app"
8+bgColor = "#FBCF44"
9+image = "https://m.tinnus-napbus.xyz/pub/books.svg"
10++++
content/groups/_index.mdnew file+3
@@ -0,0 +1,3 @@
1++++
2+title = "Group Directory"
3++++
content/groups/~bitbet-bolbel/urbit-community.mdnew file+14
@@ -0,0 +1,14 @@
1++++
2+title = "Urbit Community"
3+shortcode = "~bitbet-bolbel/urbit-community"
4+description = "World hub, help desk, meet and greet, etc."
5+type = "Public"
6+tile = "https://fabled-faster.nyc3.digitaloceanspaces.com/fabled-faster/2021.4.02..21.52.41-UC.png"
7+participant_range = "5000+"
8++++
9+
10+Urbit Community, or UC, is the “Town Square” of Urbit. This group, unlike most groups on Urbit, is decidedly about Urbit.
11+
12+If you’re new to the network, this is the best place to start. The team at Tlon and the Urbit Foundation hang out here to support newcomers, as well as many of the most active members of our community.
13+
14+UC is where you’ll receive important news about Urbit, get support if you have any issues, and can chat with our community about nearly anything Urbit-related. Every few weeks we host an “Urbit Dialogue,” where a guest spends an hour or so answering questions AMA-style with the community.
content/groups/~fabled-faster/new-york.mdnew file+10
@@ -0,0 +1,10 @@
1++++
2+title = "New York"
3+shortcode = "~fabled-faster/new-york"
4+description = "Martians invade the Big Apple."
5+type = "Public"
6+tile = "#000000"
7+participant_range = "100+"
8++++
9+
10+Martians invade the Big Apple.
content/groups/~fabled-faster/structure.mdnew file+10
@@ -0,0 +1,10 @@
1++++
2+title = "structure"
3+shortcode = "~fabled-faster-/wind"
4+description = "Urbit Structural Design and Engineering Group. Always Thinking About Mechanics."
5+type = "Public"
6+tile = "https://fabled-faster.nyc3.digitaloceanspaces.com/fabled-faster/2022.1.27..17.59.43-image.png"
7+participant_range = "400+"
8++++
9+
10+Urbit Structural Design and Engineering Group. Always Thinking About Mechanics.
content/groups/~fabled-faster/wind.mdnew file+10
@@ -0,0 +1,10 @@
1++++
2+title = "Wind"
3+shortcode = "~fabled-faster-/wind"
4+description = "Wind was the first music. Let's chat, discuss, scheme, and learn everything about new music on Urbit."
5+type = "Public"
6+tile = "https://fabled-faster.nyc3.digitaloceanspaces.com/fabled-faster/2021.4.15..03.52.47-original_001012c5d98277b0f0061b3da18e4f1a.jpg"
7+participant_range = "200+"
8++++
9+
10+Wind was the first music. Let's chat, discuss, scheme, and learn everything about new music on Urbit.
content/groups/~hatryx-lastud/other-life.mdnew file+10
@@ -0,0 +1,10 @@
1++++
2+title = "Other Life"
3+shortcode = "~hatryx-lastud/other-life"
4+description = "The Imperceptible City"
5+type = "Public"
6+tile = "https://dl.airtable.com/.attachmentThumbnails/85973e6c8ac12bef0ce4fbc046a2fb7c/8c21d303"
7+participant_range = "5000+"
8++++
9+
10+The Imperceptible City
content/groups/~ladtem-filmyr/urbit-ny-takeover.mdnew file+10
@@ -0,0 +1,10 @@
1++++
2+title = "Urbit NY Week"
3+shortcode = "~~ladtem-filmyr/urbit-ny-week"
4+description = "Urbit NY Takeover May 19-22"
5+type = "Public"
6+tile = "https://fabled-faster.nyc3.digitaloceanspaces.com/fabled-faster/2022.4.01..21.56.54-Frame%202.png"
7+participant_range = "100+"
8++++
9+
10+Urbit NY Takeover may 19-22
content/groups/~matwet/networked-subject.mdnew file+11
@@ -0,0 +1,11 @@
1++++
2+title = "🌐 networked subject_ 🌲"
3+shortcode = "~matwet/networked-subject"
4+description = "Become illegible. https://subject.network"
5+type = "Public"
6+tile = "https://urbits3.ams3.digitaloceanspaces.com/sitful-hatred/2022.4.28..19.40.19-ns.png"
7+participant_range = "5000+"
8++++
9+
10+Networked Subject is a long-running group with a titular clearnet blog & and on-network notebook with a collection of tutorials and resources for self-hosting Urbit ships, and operating related software. Learn how to self-host your ship on a cloud VPS 24/7 for free, host your own S3 bucket, or read interviews with interesting community members.
11+The NetSub community is a handy nexus for troubleshooting issues with ships or the software you use to run them, offers a public Bitcoin provider node, and has a dedicated space for sharing Urbit memes
content/groups/~middev/the-forge.mdnew file+10
@@ -0,0 +1,10 @@
1++++
2+title = "The Forge"
3+shortcode = "~middev/the-forge"
4+description = "World hub, help desk, meet and greet, etc."
5+type = "Public"
6+tile = "https://nyc3.digitaloceanspaces.com/archiv/littel-wolfur/2021.5.06..21.01.58-the%20forge.png"
7+participant_range = "900+"
8++++
9+
10+pale fire computing
content/groups/~napmer-rilnut/wet-brain.mdnew file+10
@@ -0,0 +1,10 @@
1++++
2+title = "ᴡᴇᴛ ʙʀᴀɪɴ"
3+shortcode = "~napmer-rilnut/wet-brain"
4+description = "wet brain Wet brain wet Brain wet Brain patreon.com/wetbrain"
5+type = "Public"
6+tile = "https://snipboard.io/uhWLiQ.jpg"
7+participant_range = "500+"
8++++
9+
10+wet brain Wet brain wet Brain wet Brain patreon.com/wetbrain
content/groups/~natnex-ronret/door-link.mdnew file+10
@@ -0,0 +1,10 @@
1++++
2+title = "door.link"
3+shortcode = "~natnex-ronret/door-link"
4+description = "music cult"
5+type = "Public"
6+tile = "#000000"
7+participant_range = "300+"
8++++
9+
10+an enthusiastic deep listening community with curation brought to you by Tlon's ~natnex-ronnet
content/guides/getting-an-urbit-id.mdnew file+46
@@ -0,0 +1,46 @@
1++++
2+title = "Getting an Urbit ID"
3++++
4+
5+Urbit IDs are NFTs on Ethereum that give you access to the Urbit Network. There are a few types of Urbit IDs, but what most people will find useful is called a planet.
6+
7+There are a few options to obtain planets.
8+
9+## A) Ask Nicely
10+
11+Some people, such as [imperceptible.computer](https://imperceptible.computer), are generously giving away planets, so if you have patience you can sign up for a free planet.
12+
13+You can also ask around on Twitter or boot up a comet and ask on the Urbit network. If you get a planet this way you’ll get an email with instructions on setting it up.
14+
15+## B) Buy a Planet
16+
17+There are many places to buy a planet using either cryptocurrency or fiat.
18+
19+Layer 1 planets are the most available through marketplaces, however they can be expensive due to Ethereum gas fees. You will need an Ethereum wallet such as Metamask to purchase these planets, and will later need to sign in to Bridge with your wallet to configure your planet.
20+
21+Layer 2 planets do not require any crypto wallet management, but are at present less readily available on marketplaces.
22+
23+Don’t worry, both types work the same on the network.
24+
25+Here are just a few of the places where you can buy planets:
26+
27+**L1 Planets**
28+
29+- [Urbitex](https://urbitex.io)
30+- [Urbit.live](https://urbit.live)
31+- [Urbit.me](https://urbit.me)
32+
33+**L2 Planets**
34+
35+- [Azimuth.shop](https://azimuth.shop)
36+- [\_networked subject\_](https://networked.subject)
37+- [~mocbel house](https://mocbel.house)
38+- [Wexpert.systems](https://wexpert.systems)
39+
40+**Included With Hosting**
41+
42+- [Urbit Host](https://urbithost.com)
43+
44+## Next steps
45+
46+Once you buy your planet you’ll want to log in to Bridge to configure your planet and get your ship running. See our [Getting Started guide](/getting-started) to learn more.
content/guides/installing-applications.mdnew file+39
@@ -0,0 +1,39 @@
1++++
2+title = "Installing applications on Urbit"
3++++
4+
5+## 1. Get on the network
6+
7+First, you’ll need to get on Urbit. You can quickly check out the network by [downloading the Port application](https://github.com/urbit/port) and booting up a comet–a temporary Urbit ID.
8+
9+For long-term use, you’ll want to get a permanent Urbit ID and consider a running your urbit in the cloud or on a hosted service.
10+
11+Read our [Getting Started guide](https://urbit.org/install) for details.
12+
13+## 2. Finding applications
14+
15+Urbit is still in its early stages, so you’ll need to join groups and browse around the network to discover new applications as they are being brought up.
16+
17+However, we can get started a simple app from Urbit developer [`~paldev`](/ids/~paldev).
18+
19+## 3. Search for a Developer
20+
21+Once you’re on the network, you’ll see and large tiled interface with a few applications installed and a search bar on the top of the page.
22+
23+Click and the search bar and search for the software provider’s Urbit ID.
24+
25+For example, to install the picture application, search for `~paldev` and then click on the result for `~paldev` to show his applications.
26+
27+![](https://media.urbit.org/site/application/app-guide1.png)
28+
29+![](https://media.urbit.org/site/application/app-guide2.png)
30+
31+## 4. Get the Application
32+
33+After selecting the “picture” app, a pane will open that shows some information about the application. Go ahead and click the “Get App” button, and confirm the following modal window to begin the installation process.
34+
35+Soon you’ll have a new application tile on your home screen that will let you upload and display a picture to your home screen.
36+
37+![](https://media.urbit.org/site/application/app-guide3.png)
38+
39+![](https://media.urbit.org/site/application/app-guide4.png)
content/guides/joining-groups.mdnew file+53
@@ -0,0 +1,53 @@
1++++
2+title = "Joining groups on Urbit"
3++++
4+
5+## 1. Get on the network
6+
7+First, you’ll need to get on Urbit. You can quickly check out the network by [downloading the Port application](https://github.com/urbit/port) and booting up a comet–a temporary Urbit ID.
8+
9+For long-term use, you’ll want to get a permanent Urbit ID and consider a running your urbit in the cloud or on a hosted service.
10+
11+Read our [Getting Started guide](https://urbit.org/install) for details.
12+
13+## 2. Open the Groups app
14+
15+Once you’re on the network, you’ll see and large tiled interface with a few applications installed: Terminal, Groups, and Bitcoin. Click on Groups.
16+
17+![](https://media.urbit.org/site/group/groups-guide1.png)
18+
19+## 3. Enter the group shortcode
20+
21+We’ll start by joining Urbit Community, a friendly introductory Urbit group.
22+
23+In the Groups app, click the “Join Group” button and enter `~bitbet-bolbel/urbit-community`.
24+
25+Then confirm by clicking with the second “Join Group” button.
26+
27+![](https://media.urbit.org/site/group/groups-guide2.png)
28+![](https://media.urbit.org/site/group/groups-guide3.png)
29+
30+## 4. Confirm join
31+
32+You should see a window that provides a description and group information. If not, check the spelling of the group name and address and try again.
33+
34+Alternatively, the ship that is hosting the group may be offline.
35+
36+Click "Join Urbit Community".
37+
38+![](https://media.urbit.org/site/group/groups-guide4.png)
39+
40+## 5. Join channels
41+
42+Congratulations, if you’re seeing a window like the one that appears below then you’ve joined the group!
43+
44+There two types of channels in the left column: Chats, and Notebooks. The former is self-explanatory, while the latter is like a blog with optional comments.
45+
46+To join a channel within the group, select a channel name from the left column, and click the Join Channel button.
47+
48+![](https://media.urbit.org/site/group/groups-guide5.png)
49+![](https://media.urbit.org/site/group/groups-guide6.png)
50+
51+## 6. Chat and post on the network
52+
53+Congratulations you’re now a part of a peer-to-peer Urbit group. Be friendly, be curious, and have fun.
content/ids/battus-datsun.mdnew file+10
@@ -0,0 +1,10 @@
1++++
2+ship = "~battus-datsun"
3+nickname = "gordon"
4+bgColor = "#fff700"
5+image = ""
6++++
7+
8+Automotive, crypto, and holistic health enthusiast
9+
10+Communications at Urbit Foundation
content/ids/bitbet-bolbel.mdnew file+4
@@ -0,0 +1,4 @@
1++++
2+ship = "~bitbet-bolbel"
3+description = "The host of Urbit Community, run by the Urbit Foundation."
4++++
content/ids/haddef-sigwen.mdnew file+10
@@ -0,0 +1,10 @@
1++++
2+ship = "~haddef-sigwen"
3+nickname = "~am"
4+bgColor = "#dcedce"
5+image = "https://s3.us-east-1.amazonaws.com/haddefsigwen1/haddef-sigwen/2021.2.24..20.22.37-bw400.png"
6++++
7+
8+Tlon Corporation (2019-2021), Urbit Foundation (2022-)
9+
10+Lawful neutral, interactive media artist, mercenary for hire. [Decept.org](https://decept.org) proprietor. More projects at [my website](https://matildepark.ca).
content/ids/ravmel-ropdyl.mdnew file+8
@@ -0,0 +1,8 @@
1++++
2+ship = "~ravmel-ropdyl"
3+nickname = "galen"
4+bgColor = "#ffffff"
5+image = ""
6++++
7+
8+i work on making urbit the only computer that matters
lib/cache.js+1−1
@@ -27,7 +27,7 @@ function buildSearchIndex(dir) {
2727
2828 const children = fs.readdirSync(dir, { withFileTypes: true });
2929 const pages = children.filter((f) => f.isFile() && f.name !== "_index.md");
30− const subdirs = children.filter((f) => f.isDirectory());
30+ const subdirs = children.filter((f) => f.isDirectory() && f.name !== "ids");
3131
3232 index.push(
3333 ...pages.map((page) => {
lib/hooks.js+12
@@ -57,3 +57,15 @@ export const useLocalStorage = (key, defaultValue) => {
5757
5858 return [value, setValue];
5959};
60+
61+export const useInputChange = () => {
62+ const [input, setInput] = useState({});
63+
64+ const handleInputChange = (e) =>
65+ setInput({
66+ ...input,
67+ [e.currentTarget.name]: e.currentTarget.value,
68+ });
69+
70+ return [input, handleInputChange];
71+};
lib/lib.js+4−2
@@ -28,7 +28,9 @@ const directories = (dir) => {
2828};
2929
3030export function getPostSlugs(key) {
31− const dir = fs.readdirSync(directories(key), { withFileTypes: true });
31+ const dir = fs.existsSync(directories(key))
32+ ? fs.readdirSync(directories(key), { withFileTypes: true })
33+ : [];
3234 return dir
3335 .filter((f) => f.isFile() && f.name !== "_index.md")
3436 .map((f) => f.name);
@@ -68,7 +70,7 @@ export function getPostBySlug(slug, fields = [], key) {
6870 return items;
6971}
7072
71−export function getAllPosts(fields = [], key, sort = "date") {
73+export function getAllPosts(fields = [], key, sort = "") {
7274 const slugs = getPostSlugs(key);
7375 const posts = slugs
7476 .map((slug) => getPostBySlug(slug, fields, key))
package-lock.json+446−21
@@ -11,6 +11,8 @@
1111 "@iarna/toml": "^2.2.5",
1212 "@socialgouv/matomo-next": "^1.3.0",
1313 "@tailwindcss/aspect-ratio": "^0.4.0",
14+ "@tlon/sigil-js": "^1.4.5",
15+ "axios": "^0.26.1",
1416 "buffer": "^6.0.3",
1517 "classnames": "^2.3.1",
1618 "deepmerge": "^4.2.2",
@@ -41,7 +43,8 @@
4143 "remark-rehype": "^10.0.0",
4244 "remark-slug": "^6.0.0",
4345 "stream-browserify": "^3.0.0",
44− "unified": "^9.2.1"
46+ "unified": "^9.2.1",
47+ "urbit-ob": "^5.0.1"
4548 },
4649 "devDependencies": {
4750 "autoprefixer": "^10.4.4",
@@ -302,6 +305,19 @@
302305 "tailwindcss": ">=2.0.0 || >=3.0.0 || >=3.0.0-alpha.1"
303306 }
304307 },
308+ "node_modules/@tlon/sigil-js": {
309+ "version": "1.4.5",
310+ "resolved": "https://registry.npmjs.org/@tlon/sigil-js/-/sigil-js-1.4.5.tgz",
311+ "integrity": "sha512-+Fn0EizPetjePaK+z8njjzE74wxzqIRLVM8W53Dlri6XU87wJ4vk82baVGgZQFAQs96rYM/f4WV7kCzuSxSMAw==",
312+ "dependencies": {
313+ "invariant": "^2.2.4",
314+ "svgson": "^4.0.0",
315+ "transformation-matrix": "2.1.1"
316+ },
317+ "peerDependencies": {
318+ "react": "^16 || ^17"
319+ }
320+ },
305321 "node_modules/@tootallnate/once": {
306322 "version": "1.1.2",
307323 "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz",
@@ -509,9 +525,9 @@
509525 "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
510526 },
511527 "node_modules/autoprefixer": {
512− "version": "10.4.4",
513− "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.4.tgz",
514− "integrity": "sha512-Tm8JxsB286VweiZ5F0anmbyGiNI3v3wGv3mz9W+cxEDYB/6jbnj6GM9H9mK3wIL8ftgl+C07Lcwb8PG5PCCPzA==",
528+ "version": "10.4.5",
529+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.5.tgz",
530+ "integrity": "sha512-Fvd8yCoA7lNX/OUllvS+aS1I7WRBclGXsepbvT8ZaPgrH24rgXpZzF0/6Hh3ZEkwg+0AES/Osd196VZmYoEFtw==",
515531 "dev": true,
516532 "funding": [
517533 {
@@ -525,7 +541,7 @@
525541 ],
526542 "dependencies": {
527543 "browserslist": "^4.20.2",
528− "caniuse-lite": "^1.0.30001317",
544+ "caniuse-lite": "^1.0.30001332",
529545 "fraction.js": "^4.2.0",
530546 "normalize-range": "^0.1.2",
531547 "picocolors": "^1.0.0",
@@ -541,6 +557,14 @@
541557 "postcss": "^8.1.0"
542558 }
543559 },
560+ "node_modules/axios": {
561+ "version": "0.26.1",
562+ "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz",
563+ "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==",
564+ "dependencies": {
565+ "follow-redirects": "^1.14.8"
566+ }
567+ },
544568 "node_modules/bail": {
545569 "version": "1.0.5",
546570 "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz",
@@ -583,6 +607,11 @@
583607 "node": ">=8"
584608 }
585609 },
610+ "node_modules/bn.js": {
611+ "version": "4.12.0",
612+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
613+ "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
614+ },
586615 "node_modules/brace-expansion": {
587616 "version": "1.1.11",
588617 "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -934,6 +963,34 @@
934963 "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
935964 "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="
936965 },
966+ "node_modules/deep-rename-keys": {
967+ "version": "0.2.1",
968+ "resolved": "https://registry.npmjs.org/deep-rename-keys/-/deep-rename-keys-0.2.1.tgz",
969+ "integrity": "sha1-7eeFN9emaivmFRfir5Vtf1ij8dg=",
970+ "dependencies": {
971+ "kind-of": "^3.0.2",
972+ "rename-keys": "^1.1.2"
973+ },
974+ "engines": {
975+ "node": ">=0.10.0"
976+ }
977+ },
978+ "node_modules/deep-rename-keys/node_modules/is-buffer": {
979+ "version": "1.1.6",
980+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
981+ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
982+ },
983+ "node_modules/deep-rename-keys/node_modules/kind-of": {
984+ "version": "3.2.2",
985+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
986+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
987+ "dependencies": {
988+ "is-buffer": "^1.1.5"
989+ },
990+ "engines": {
991+ "node": ">=0.10.0"
992+ }
993+ },
937994 "node_modules/deepmerge": {
938995 "version": "4.2.2",
939996 "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
@@ -1032,9 +1089,9 @@
10321089 }
10331090 },
10341091 "node_modules/electron-to-chromium": {
1035− "version": "1.4.114",
1036− "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.114.tgz",
1037− "integrity": "sha512-gRwLpVYWHGbERPU6o8pKfR168V6enWEXzZc6zQNNXbgJ7UJna+9qzAIHY94+9KOv71D/CH+QebLA9pChD2q8zA==",
1092+ "version": "1.4.118",
1093+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.118.tgz",
1094+ "integrity": "sha512-maZIKjnYDvF7Fs35nvVcyr44UcKNwybr93Oba2n3HkKDFAtk0svERkLN/HyczJDS3Fo4wU9th9fUQd09ZLtj1w==",
10381095 "dev": true
10391096 },
10401097 "node_modules/end-of-stream": {
@@ -1133,6 +1190,11 @@
11331190 "node": ">=0.10.0"
11341191 }
11351192 },
1193+ "node_modules/eventemitter3": {
1194+ "version": "2.0.3",
1195+ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz",
1196+ "integrity": "sha1-teEHm1n7XhuidxwKmTvgYKWMmbo="
1197+ },
11361198 "node_modules/execa": {
11371199 "version": "4.1.0",
11381200 "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz",
@@ -1246,6 +1308,25 @@
12461308 "node": ">=8"
12471309 }
12481310 },
1311+ "node_modules/follow-redirects": {
1312+ "version": "1.14.9",
1313+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz",
1314+ "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==",
1315+ "funding": [
1316+ {
1317+ "type": "individual",
1318+ "url": "https://github.com/sponsors/RubenVerborgh"
1319+ }
1320+ ],
1321+ "engines": {
1322+ "node": ">=4.0"
1323+ },
1324+ "peerDependenciesMeta": {
1325+ "debug": {
1326+ "optional": true
1327+ }
1328+ }
1329+ },
12491330 "node_modules/form-data": {
12501331 "version": "3.0.1",
12511332 "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz",
@@ -1310,6 +1391,14 @@
13101391 "url": "https://github.com/sponsors/sindresorhus"
13111392 }
13121393 },
1394+ "node_modules/get-value": {
1395+ "version": "2.0.6",
1396+ "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz",
1397+ "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=",
1398+ "engines": {
1399+ "node": ">=0.10.0"
1400+ }
1401+ },
13131402 "node_modules/github-slugger": {
13141403 "version": "1.4.0",
13151404 "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.4.0.tgz",
@@ -1366,6 +1455,38 @@
13661455 "node": ">=8"
13671456 }
13681457 },
1458+ "node_modules/has-value": {
1459+ "version": "0.3.1",
1460+ "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz",
1461+ "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=",
1462+ "dependencies": {
1463+ "get-value": "^2.0.3",
1464+ "has-values": "^0.1.4",
1465+ "isobject": "^2.0.0"
1466+ },
1467+ "engines": {
1468+ "node": ">=0.10.0"
1469+ }
1470+ },
1471+ "node_modules/has-value/node_modules/isobject": {
1472+ "version": "2.1.0",
1473+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
1474+ "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
1475+ "dependencies": {
1476+ "isarray": "1.0.0"
1477+ },
1478+ "engines": {
1479+ "node": ">=0.10.0"
1480+ }
1481+ },
1482+ "node_modules/has-values": {
1483+ "version": "0.1.4",
1484+ "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz",
1485+ "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=",
1486+ "engines": {
1487+ "node": ">=0.10.0"
1488+ }
1489+ },
13691490 "node_modules/hast-to-hyperscript": {
13701491 "version": "10.0.1",
13711492 "resolved": "https://registry.npmjs.org/hast-to-hyperscript/-/hast-to-hyperscript-10.0.1.tgz",
@@ -1645,6 +1766,14 @@
16451766 "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz",
16461767 "integrity": "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q=="
16471768 },
1769+ "node_modules/invariant": {
1770+ "version": "2.2.4",
1771+ "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
1772+ "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
1773+ "dependencies": {
1774+ "loose-envify": "^1.0.0"
1775+ }
1776+ },
16481777 "node_modules/is-alphabetical": {
16491778 "version": "1.0.4",
16501779 "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz",
@@ -1772,6 +1901,17 @@
17721901 "node": ">=8"
17731902 }
17741903 },
1904+ "node_modules/is-plain-object": {
1905+ "version": "2.0.4",
1906+ "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
1907+ "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
1908+ "dependencies": {
1909+ "isobject": "^3.0.1"
1910+ },
1911+ "engines": {
1912+ "node": ">=0.10.0"
1913+ }
1914+ },
17751915 "node_modules/is-potential-custom-element-name": {
17761916 "version": "1.0.1",
17771917 "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
@@ -1789,12 +1929,25 @@
17891929 "url": "https://github.com/sponsors/sindresorhus"
17901930 }
17911931 },
1932+ "node_modules/isarray": {
1933+ "version": "1.0.0",
1934+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
1935+ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
1936+ },
17921937 "node_modules/isexe": {
17931938 "version": "2.0.0",
17941939 "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
17951940 "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
17961941 "dev": true
17971942 },
1943+ "node_modules/isobject": {
1944+ "version": "3.0.1",
1945+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
1946+ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
1947+ "engines": {
1948+ "node": ">=0.10.0"
1949+ }
1950+ },
17981951 "node_modules/js-tokens": {
17991952 "version": "4.0.0",
18001953 "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -1910,11 +2063,21 @@
19102063 "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
19112064 "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
19122065 },
2066+ "node_modules/lodash.chunk": {
2067+ "version": "4.2.0",
2068+ "resolved": "https://registry.npmjs.org/lodash.chunk/-/lodash.chunk-4.2.0.tgz",
2069+ "integrity": "sha1-ZuXOH3btJ7QwPYxlEujRIW6BBrw="
2070+ },
19132071 "node_modules/lodash.debounce": {
19142072 "version": "4.0.8",
19152073 "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
19162074 "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168="
19172075 },
2076+ "node_modules/lodash.isequal": {
2077+ "version": "4.5.0",
2078+ "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
2079+ "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA="
2080+ },
19182081 "node_modules/longest-streak": {
19192082 "version": "3.0.1",
19202083 "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.0.1.tgz",
@@ -3000,6 +3163,18 @@
30003163 "node": ">= 6"
30013164 }
30023165 },
3166+ "node_modules/omit-deep": {
3167+ "version": "0.3.0",
3168+ "resolved": "https://registry.npmjs.org/omit-deep/-/omit-deep-0.3.0.tgz",
3169+ "integrity": "sha1-IcivNJm8rdKWUaIyy8rLxSRF6+w=",
3170+ "dependencies": {
3171+ "is-plain-object": "^2.0.1",
3172+ "unset-value": "^0.1.1"
3173+ },
3174+ "engines": {
3175+ "node": ">=0.10.0"
3176+ }
3177+ },
30033178 "node_modules/once": {
30043179 "version": "1.4.0",
30053180 "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -4266,6 +4441,14 @@
42664441 "url": "https://opencollective.com/unified"
42674442 }
42684443 },
4444+ "node_modules/rename-keys": {
4445+ "version": "1.2.0",
4446+ "resolved": "https://registry.npmjs.org/rename-keys/-/rename-keys-1.2.0.tgz",
4447+ "integrity": "sha512-U7XpAktpbSgHTRSNRrjKSrjYkZKuhUukfoBlXWXUExCAqhzh1TU3BDRAfJmarcl5voKS+pbKU9MvyLWKZ4UEEg==",
4448+ "engines": {
4449+ "node": ">= 0.8.0"
4450+ }
4451+ },
42694452 "node_modules/repeat-string": {
42704453 "version": "1.6.1",
42714454 "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
@@ -4548,6 +4731,16 @@
45484731 "url": "https://github.com/sponsors/ljharb"
45494732 }
45504733 },
4734+ "node_modules/svgson": {
4735+ "version": "4.1.0",
4736+ "resolved": "https://registry.npmjs.org/svgson/-/svgson-4.1.0.tgz",
4737+ "integrity": "sha512-DodISxHtdLKUghDYA+PGK4Qq350+CbBAkdvGLkBFSmWd9WKSg4dijgjB1IiRPTmsUCd+a7KYe+ILHtklYgQyzQ==",
4738+ "dependencies": {
4739+ "deep-rename-keys": "^0.2.1",
4740+ "omit-deep": "0.3.0",
4741+ "xml-reader": "2.4.3"
4742+ }
4743+ },
45514744 "node_modules/symbol-tree": {
45524745 "version": "3.2.4",
45534746 "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
@@ -4635,6 +4828,11 @@
46354828 "node": ">=8"
46364829 }
46374830 },
4831+ "node_modules/transformation-matrix": {
4832+ "version": "2.1.1",
4833+ "resolved": "https://registry.npmjs.org/transformation-matrix/-/transformation-matrix-2.1.1.tgz",
4834+ "integrity": "sha512-74MoNHhwLVuzwaPDcAecFjSkOA9vwWqyOdkeB0Be8Jc/IWSS5SNZKapFllqzkTliqZptkvqX5CZnVeDvfhN8cw=="
4835+ },
46384836 "node_modules/trough": {
46394837 "version": "1.0.5",
46404838 "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz",
@@ -4645,9 +4843,9 @@
46454843 }
46464844 },
46474845 "node_modules/tslib": {
4648− "version": "2.3.1",
4649− "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
4650− "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="
4846+ "version": "2.4.0",
4847+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
4848+ "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
46514849 },
46524850 "node_modules/type-check": {
46534851 "version": "0.3.2",
@@ -4829,6 +5027,28 @@
48295027 "node": ">= 4.0.0"
48305028 }
48315029 },
5030+ "node_modules/unset-value": {
5031+ "version": "0.1.2",
5032+ "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-0.1.2.tgz",
5033+ "integrity": "sha1-UGgQuGfyfCpabpsEgzYx9t5Y0xA=",
5034+ "dependencies": {
5035+ "has-value": "^0.3.1",
5036+ "isobject": "^3.0.0"
5037+ },
5038+ "engines": {
5039+ "node": ">=0.10.0"
5040+ }
5041+ },
5042+ "node_modules/urbit-ob": {
5043+ "version": "5.0.1",
5044+ "resolved": "https://registry.npmjs.org/urbit-ob/-/urbit-ob-5.0.1.tgz",
5045+ "integrity": "sha512-qGNAwu87XNkW3g8ah4fUwmh2EKXtsdhEbyEiE5qX4Op17rhLH3HSkvu8g9z+MhqX51Uz9sf8ktvqJj/IRwETIQ==",
5046+ "dependencies": {
5047+ "bn.js": "^4.11.8",
5048+ "lodash.chunk": "^4.2.0",
5049+ "lodash.isequal": "^4.5.0"
5050+ }
5051+ },
48325052 "node_modules/util-deprecate": {
48335053 "version": "1.0.2",
48345054 "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -5014,11 +5234,28 @@
50145234 "xml-js": "bin/cli.js"
50155235 }
50165236 },
5237+ "node_modules/xml-lexer": {
5238+ "version": "0.2.2",
5239+ "resolved": "https://registry.npmjs.org/xml-lexer/-/xml-lexer-0.2.2.tgz",
5240+ "integrity": "sha1-UYGTpKozTVj8fSSLVJB5uJkH4EY=",
5241+ "dependencies": {
5242+ "eventemitter3": "^2.0.0"
5243+ }
5244+ },
50175245 "node_modules/xml-name-validator": {
50185246 "version": "3.0.0",
50195247 "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz",
50205248 "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw=="
50215249 },
5250+ "node_modules/xml-reader": {
5251+ "version": "2.4.3",
5252+ "resolved": "https://registry.npmjs.org/xml-reader/-/xml-reader-2.4.3.tgz",
5253+ "integrity": "sha1-n4EMr3xCWlqvuEixxFEDyecddTA=",
5254+ "dependencies": {
5255+ "eventemitter3": "^2.0.0",
5256+ "xml-lexer": "^0.2.2"
5257+ }
5258+ },
50225259 "node_modules/xmlchars": {
50235260 "version": "2.2.0",
50245261 "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
@@ -5176,6 +5413,16 @@
51765413 "integrity": "sha512-WJu0I4PpqNPuutpaA9zDUq2JXR+lorZ7PbLcKNLmb6GL9/HLfC7w3CRsMhJF4BbYd/lkY6CfXOvkYpuGnZfkpQ==",
51775414 "requires": {}
51785415 },
5416+ "@tlon/sigil-js": {
5417+ "version": "1.4.5",
5418+ "resolved": "https://registry.npmjs.org/@tlon/sigil-js/-/sigil-js-1.4.5.tgz",
5419+ "integrity": "sha512-+Fn0EizPetjePaK+z8njjzE74wxzqIRLVM8W53Dlri6XU87wJ4vk82baVGgZQFAQs96rYM/f4WV7kCzuSxSMAw==",
5420+ "requires": {
5421+ "invariant": "^2.2.4",
5422+ "svgson": "^4.0.0",
5423+ "transformation-matrix": "2.1.1"
5424+ }
5425+ },
51795426 "@tootallnate/once": {
51805427 "version": "1.1.2",
51815428 "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz",
@@ -5342,19 +5589,27 @@
53425589 "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
53435590 },
53445591 "autoprefixer": {
5345− "version": "10.4.4",
5346− "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.4.tgz",
5347− "integrity": "sha512-Tm8JxsB286VweiZ5F0anmbyGiNI3v3wGv3mz9W+cxEDYB/6jbnj6GM9H9mK3wIL8ftgl+C07Lcwb8PG5PCCPzA==",
5592+ "version": "10.4.5",
5593+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.5.tgz",
5594+ "integrity": "sha512-Fvd8yCoA7lNX/OUllvS+aS1I7WRBclGXsepbvT8ZaPgrH24rgXpZzF0/6Hh3ZEkwg+0AES/Osd196VZmYoEFtw==",
53485595 "dev": true,
53495596 "requires": {
53505597 "browserslist": "^4.20.2",
5351− "caniuse-lite": "^1.0.30001317",
5598+ "caniuse-lite": "^1.0.30001332",
53525599 "fraction.js": "^4.2.0",
53535600 "normalize-range": "^0.1.2",
53545601 "picocolors": "^1.0.0",
53555602 "postcss-value-parser": "^4.2.0"
53565603 }
53575604 },
5605+ "axios": {
5606+ "version": "0.26.1",
5607+ "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz",
5608+ "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==",
5609+ "requires": {
5610+ "follow-redirects": "^1.14.8"
5611+ }
5612+ },
53585613 "bail": {
53595614 "version": "1.0.5",
53605615 "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz",
@@ -5376,6 +5631,11 @@
53765631 "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
53775632 "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA=="
53785633 },
5634+ "bn.js": {
5635+ "version": "4.12.0",
5636+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
5637+ "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
5638+ },
53795639 "brace-expansion": {
53805640 "version": "1.1.11",
53815641 "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -5611,6 +5871,30 @@
56115871 "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
56125872 "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="
56135873 },
5874+ "deep-rename-keys": {
5875+ "version": "0.2.1",
5876+ "resolved": "https://registry.npmjs.org/deep-rename-keys/-/deep-rename-keys-0.2.1.tgz",
5877+ "integrity": "sha1-7eeFN9emaivmFRfir5Vtf1ij8dg=",
5878+ "requires": {
5879+ "kind-of": "^3.0.2",
5880+ "rename-keys": "^1.1.2"
5881+ },
5882+ "dependencies": {
5883+ "is-buffer": {
5884+ "version": "1.1.6",
5885+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
5886+ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
5887+ },
5888+ "kind-of": {
5889+ "version": "3.2.2",
5890+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
5891+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
5892+ "requires": {
5893+ "is-buffer": "^1.1.5"
5894+ }
5895+ }
5896+ }
5897+ },
56145898 "deepmerge": {
56155899 "version": "4.2.2",
56165900 "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
@@ -5684,9 +5968,9 @@
56845968 }
56855969 },
56865970 "electron-to-chromium": {
5687− "version": "1.4.114",
5688− "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.114.tgz",
5689− "integrity": "sha512-gRwLpVYWHGbERPU6o8pKfR168V6enWEXzZc6zQNNXbgJ7UJna+9qzAIHY94+9KOv71D/CH+QebLA9pChD2q8zA==",
5971+ "version": "1.4.118",
5972+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.118.tgz",
5973+ "integrity": "sha512-maZIKjnYDvF7Fs35nvVcyr44UcKNwybr93Oba2n3HkKDFAtk0svERkLN/HyczJDS3Fo4wU9th9fUQd09ZLtj1w==",
56905974 "dev": true
56915975 },
56925976 "end-of-stream": {
@@ -5751,6 +6035,11 @@
57516035 "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
57526036 "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="
57536037 },
6038+ "eventemitter3": {
6039+ "version": "2.0.3",
6040+ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz",
6041+ "integrity": "sha1-teEHm1n7XhuidxwKmTvgYKWMmbo="
6042+ },
57546043 "execa": {
57556044 "version": "4.1.0",
57566045 "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz",
@@ -5842,6 +6131,11 @@
58426131 "path-exists": "^4.0.0"
58436132 }
58446133 },
6134+ "follow-redirects": {
6135+ "version": "1.14.9",
6136+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz",
6137+ "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w=="
6138+ },
58456139 "form-data": {
58466140 "version": "3.0.1",
58476141 "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz",
@@ -5883,6 +6177,11 @@
58836177 "pump": "^3.0.0"
58846178 }
58856179 },
6180+ "get-value": {
6181+ "version": "2.0.6",
6182+ "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz",
6183+ "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg="
6184+ },
58866185 "github-slugger": {
58876186 "version": "1.4.0",
58886187 "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.4.0.tgz",
@@ -5927,6 +6226,31 @@
59276226 "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
59286227 "dev": true
59296228 },
6229+ "has-value": {
6230+ "version": "0.3.1",
6231+ "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz",
6232+ "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=",
6233+ "requires": {
6234+ "get-value": "^2.0.3",
6235+ "has-values": "^0.1.4",
6236+ "isobject": "^2.0.0"
6237+ },
6238+ "dependencies": {
6239+ "isobject": {
6240+ "version": "2.1.0",
6241+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
6242+ "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
6243+ "requires": {
6244+ "isarray": "1.0.0"
6245+ }
6246+ }
6247+ }
6248+ },
6249+ "has-values": {
6250+ "version": "0.1.4",
6251+ "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz",
6252+ "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E="
6253+ },
59306254 "hast-to-hyperscript": {
59316255 "version": "10.0.1",
59326256 "resolved": "https://registry.npmjs.org/hast-to-hyperscript/-/hast-to-hyperscript-10.0.1.tgz",
@@ -6124,6 +6448,14 @@
61246448 "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz",
61256449 "integrity": "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q=="
61266450 },
6451+ "invariant": {
6452+ "version": "2.2.4",
6453+ "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
6454+ "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
6455+ "requires": {
6456+ "loose-envify": "^1.0.0"
6457+ }
6458+ },
61276459 "is-alphabetical": {
61286460 "version": "1.0.4",
61296461 "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz",
@@ -6197,6 +6529,14 @@
61976529 "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
61986530 "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA=="
61996531 },
6532+ "is-plain-object": {
6533+ "version": "2.0.4",
6534+ "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
6535+ "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
6536+ "requires": {
6537+ "isobject": "^3.0.1"
6538+ }
6539+ },
62006540 "is-potential-custom-element-name": {
62016541 "version": "1.0.1",
62026542 "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
@@ -6208,12 +6548,22 @@
62086548 "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
62096549 "dev": true
62106550 },
6551+ "isarray": {
6552+ "version": "1.0.0",
6553+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
6554+ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
6555+ },
62116556 "isexe": {
62126557 "version": "2.0.0",
62136558 "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
62146559 "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
62156560 "dev": true
62166561 },
6562+ "isobject": {
6563+ "version": "3.0.1",
6564+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
6565+ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
6566+ },
62176567 "js-tokens": {
62186568 "version": "4.0.0",
62196569 "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -6300,11 +6650,21 @@
63006650 "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
63016651 "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
63026652 },
6653+ "lodash.chunk": {
6654+ "version": "4.2.0",
6655+ "resolved": "https://registry.npmjs.org/lodash.chunk/-/lodash.chunk-4.2.0.tgz",
6656+ "integrity": "sha1-ZuXOH3btJ7QwPYxlEujRIW6BBrw="
6657+ },
63036658 "lodash.debounce": {
63046659 "version": "4.0.8",
63056660 "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
63066661 "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168="
63076662 },
6663+ "lodash.isequal": {
6664+ "version": "4.5.0",
6665+ "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
6666+ "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA="
6667+ },
63086668 "longest-streak": {
63096669 "version": "3.0.1",
63106670 "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.0.1.tgz",
@@ -6990,6 +7350,15 @@
69907350 "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
69917351 "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw=="
69927352 },
7353+ "omit-deep": {
7354+ "version": "0.3.0",
7355+ "resolved": "https://registry.npmjs.org/omit-deep/-/omit-deep-0.3.0.tgz",
7356+ "integrity": "sha1-IcivNJm8rdKWUaIyy8rLxSRF6+w=",
7357+ "requires": {
7358+ "is-plain-object": "^2.0.1",
7359+ "unset-value": "^0.1.1"
7360+ }
7361+ },
69937362 "once": {
69947363 "version": "1.4.0",
69957364 "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -7854,6 +8223,11 @@
78548223 }
78558224 }
78568225 },
8226+ "rename-keys": {
8227+ "version": "1.2.0",
8228+ "resolved": "https://registry.npmjs.org/rename-keys/-/rename-keys-1.2.0.tgz",
8229+ "integrity": "sha512-U7XpAktpbSgHTRSNRrjKSrjYkZKuhUukfoBlXWXUExCAqhzh1TU3BDRAfJmarcl5voKS+pbKU9MvyLWKZ4UEEg=="
8230+ },
78578231 "repeat-string": {
78588232 "version": "1.6.1",
78598233 "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
@@ -8038,6 +8412,16 @@
80388412 "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
80398413 "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="
80408414 },
8415+ "svgson": {
8416+ "version": "4.1.0",
8417+ "resolved": "https://registry.npmjs.org/svgson/-/svgson-4.1.0.tgz",
8418+ "integrity": "sha512-DodISxHtdLKUghDYA+PGK4Qq350+CbBAkdvGLkBFSmWd9WKSg4dijgjB1IiRPTmsUCd+a7KYe+ILHtklYgQyzQ==",
8419+ "requires": {
8420+ "deep-rename-keys": "^0.2.1",
8421+ "omit-deep": "0.3.0",
8422+ "xml-reader": "2.4.3"
8423+ }
8424+ },
80418425 "symbol-tree": {
80428426 "version": "3.2.4",
80438427 "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
@@ -8103,15 +8487,20 @@
81038487 "punycode": "^2.1.1"
81048488 }
81058489 },
8490+ "transformation-matrix": {
8491+ "version": "2.1.1",
8492+ "resolved": "https://registry.npmjs.org/transformation-matrix/-/transformation-matrix-2.1.1.tgz",
8493+ "integrity": "sha512-74MoNHhwLVuzwaPDcAecFjSkOA9vwWqyOdkeB0Be8Jc/IWSS5SNZKapFllqzkTliqZptkvqX5CZnVeDvfhN8cw=="
8494+ },
81068495 "trough": {
81078496 "version": "1.0.5",
81088497 "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz",
81098498 "integrity": "sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA=="
81108499 },
81118500 "tslib": {
8112− "version": "2.3.1",
8113− "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
8114− "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="
8501+ "version": "2.4.0",
8502+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
8503+ "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
81158504 },
81168505 "type-check": {
81178506 "version": "0.3.2",
@@ -8239,6 +8628,25 @@
82398628 "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
82408629 "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="
82418630 },
8631+ "unset-value": {
8632+ "version": "0.1.2",
8633+ "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-0.1.2.tgz",
8634+ "integrity": "sha1-UGgQuGfyfCpabpsEgzYx9t5Y0xA=",
8635+ "requires": {
8636+ "has-value": "^0.3.1",
8637+ "isobject": "^3.0.0"
8638+ }
8639+ },
8640+ "urbit-ob": {
8641+ "version": "5.0.1",
8642+ "resolved": "https://registry.npmjs.org/urbit-ob/-/urbit-ob-5.0.1.tgz",
8643+ "integrity": "sha512-qGNAwu87XNkW3g8ah4fUwmh2EKXtsdhEbyEiE5qX4Op17rhLH3HSkvu8g9z+MhqX51Uz9sf8ktvqJj/IRwETIQ==",
8644+ "requires": {
8645+ "bn.js": "^4.11.8",
8646+ "lodash.chunk": "^4.2.0",
8647+ "lodash.isequal": "^4.5.0"
8648+ }
8649+ },
82428650 "util-deprecate": {
82438651 "version": "1.0.2",
82448652 "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -8367,11 +8775,28 @@
83678775 "sax": "^1.2.4"
83688776 }
83698777 },
8778+ "xml-lexer": {
8779+ "version": "0.2.2",
8780+ "resolved": "https://registry.npmjs.org/xml-lexer/-/xml-lexer-0.2.2.tgz",
8781+ "integrity": "sha1-UYGTpKozTVj8fSSLVJB5uJkH4EY=",
8782+ "requires": {
8783+ "eventemitter3": "^2.0.0"
8784+ }
8785+ },
83708786 "xml-name-validator": {
83718787 "version": "3.0.0",
83728788 "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz",
83738789 "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw=="
83748790 },
8791+ "xml-reader": {
8792+ "version": "2.4.3",
8793+ "resolved": "https://registry.npmjs.org/xml-reader/-/xml-reader-2.4.3.tgz",
8794+ "integrity": "sha1-n4EMr3xCWlqvuEixxFEDyecddTA=",
8795+ "requires": {
8796+ "eventemitter3": "^2.0.0",
8797+ "xml-lexer": "^0.2.2"
8798+ }
8799+ },
83758800 "xmlchars": {
83768801 "version": "2.2.0",
83778802 "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
package.json+4−1
@@ -15,6 +15,8 @@
1515 "@iarna/toml": "^2.2.5",
1616 "@socialgouv/matomo-next": "^1.3.0",
1717 "@tailwindcss/aspect-ratio": "^0.4.0",
18+ "@tlon/sigil-js": "^1.4.5",
19+ "axios": "^0.26.1",
1820 "buffer": "^6.0.3",
1921 "classnames": "^2.3.1",
2022 "deepmerge": "^4.2.2",
@@ -45,7 +47,8 @@
4547 "remark-rehype": "^10.0.0",
4648 "remark-slug": "^6.0.0",
4749 "stream-browserify": "^3.0.0",
48− "unified": "^9.2.1"
50+ "unified": "^9.2.1",
51+ "urbit-ob": "^5.0.1"
4952 },
5053 "devDependencies": {
5154 "autoprefixer": "^10.4.4",
pages/applications/[[...application]].jsnew file+166
@@ -0,0 +1,166 @@
1+import Head from "next/head";
2+import Link from "next/link";
3+import { getPage } from "../../lib/lib";
4+import { join } from "path";
5+import CopyLink from "../../components/CopyLink";
6+import Container from "../../components/Container";
7+import SingleColumn from "../../components/SingleColumn";
8+import Section from "../../components/Section";
9+import ob from "urbit-ob";
10+import Markdown from "../../components/Markdown";
11+import GatewayHeader from "../../components/gateway/GatewayHeader";
12+import Gateway404 from "../../components/gateway/Gateway404";
13+import MetadataBlock from "../../components/gateway/MetadataBlock";
14+import MetadataLink from "../../components/gateway/MetadataLink";
15+import Description from "../../components/gateway/Description";
16+
17+const ApplicationPage = ({ data, markdown, params }) => {
18+ const { application } = params;
19+ if (!ob.isValidPatp(application[0])) {
20+ return <Gateway404 type="application" />;
21+ }
22+ const reqParams = [
23+ data?.bgColor ? `color=${encodeURIComponent(data?.bgColor)}` : "",
24+ data?.image ? `images=${encodeURIComponent(data?.image)}` : "",
25+ ].filter((e) => e !== "");
26+ const image = `https://urbit-id-og-cards-78ieodof2-urbit.vercel.app/${
27+ data.title
28+ }.png?${reqParams.join("&")}`;
29+
30+ return (
31+ <Container>
32+ <Head>
33+ <title>{data.title} • Applications • urbit.org</title>
34+ <link rel="icon" type="image/png" href="/images/favicon.ico" />
35+ <meta
36+ name="twitter:card"
37+ content="summary_large_image"
38+ key="twitter-card"
39+ />
40+ <meta
41+ name="og:title"
42+ content={`${data.title} • Applications • urbit.org`}
43+ key="title"
44+ />
45+ <meta
46+ name="og:description"
47+ content={
48+ data?.description ||
49+ "View more about this application on urbit.org."
50+ }
51+ key="description"
52+ />
53+ <meta property="twitter:image" content={image} key="image" />
54+ </Head>
55+ <SingleColumn>
56+ <Section className="space-y-12" narrow>
57+ <GatewayHeader
58+ title={data.title}
59+ color={data?.bgColor}
60+ image={data?.image || data?.bgColor}
61+ item="Urbit Application"
62+ />
63+ <div className="flex flex-wrap md:flex-nowrap justify-between">
64+ <MetadataBlock
65+ title="License"
66+ content={data.license ? data.license : "Unknown"}
67+ />
68+ {data?.website && (
69+ <MetadataLink
70+ title="Website"
71+ href={data?.website}
72+ content={
73+ data?.website ? data.website.replace(/^https?:\/\//, "") : ""
74+ }
75+ />
76+ )}
77+ {ob.isValidPatp(data?.developer) && data.developer.length < 10 ? (
78+ <MetadataLink
79+ title="Developer"
80+ href={`/ids/${data.developer}`}
81+ content={data.developer}
82+ />
83+ ) : (
84+ <MetadataBlock
85+ title="Developer"
86+ content={data.developer ? data.developer : "Unknown"}
87+ />
88+ )}
89+ <CopyLink
90+ className="basis-1/2"
91+ content={data.shortcode ? data.shortcode : data.title}
92+ />
93+ </div>
94+ <Description
95+ description={data.description}
96+ fallback="An application on Urbit."
97+ markdown={markdown}
98+ />
99+ <hr className="text-wall-200" />
100+ <div className="flex flex-col space-y-6">
101+ <h3>
102+ Urbit is a clean-slate, peer-to-peer operating system and network.
103+ </h3>
104+ <div className="flex flex-col space-y-4">
105+ <p className="text-sm font-semibold text-wall-400">
106+ Learn how to install an Urbit application
107+ </p>
108+ <Link href="/guides/installing-applications">
109+ <button className="button-lg max-w-xs bg-green-400 text-white">
110+ Installing Urbit applications
111+ </button>
112+ </Link>
113+ </div>
114+ </div>
115+ <hr className="text-wall-200" />
116+ <div className="flex flex-col space-y-1">
117+ <p>
118+ Have an application you'd like to share publicly through
119+ urbit.org?
120+ </p>
121+ <Link href="/applications/submit">
122+ <a className="type-ui text-green-400">Submit your application</a>
123+ </Link>
124+ </div>
125+ <Link href="/">
126+ <a className="text-xl pt-8 block font-semibold">Urbit.org</a>
127+ </Link>
128+ </Section>
129+ </SingleColumn>
130+ </Container>
131+ );
132+};
133+
134+export const getServerSideProps = async ({ params, res }) => {
135+ res.setHeader(
136+ "Cache-Control",
137+ "public, s-maxage=3600, stale-while-revalidate=604800"
138+ );
139+
140+ let { data, content } = getPage(
141+ join(
142+ process.cwd(),
143+ "content/applications",
144+ params.application?.join("/") || "/"
145+ )
146+ ) || { data: {}, content: "" };
147+
148+ if (!data.title) {
149+ data = {
150+ title: params.application?.join("/"),
151+ description: "An application on Urbit.",
152+ };
153+ }
154+
155+ const markdown = await Markdown({ post: { content: content } }, true);
156+
157+ return {
158+ props: {
159+ data,
160+ markdown,
161+ params,
162+ },
163+ };
164+};
165+
166+export default ApplicationPage;
pages/applications/submit.jsnew file+141
@@ -0,0 +1,141 @@
1+import Link from "next/link";
2+import Container from "../../components/Container";
3+import Section from "../../components/Section";
4+import SingleColumn from "../../components/SingleColumn";
5+import { useInputChange } from "../../lib/hooks";
6+import Head from "next/head";
7+import Meta from "../../components/Meta";
8+
9+const SubmissionPage = () => {
10+ const [form, handleFormChange] = useInputChange();
11+
12+ const href = `https://github.com/urbit/urbit.org/new/master?filename=/content/applications/${
13+ form?.shortcode
14+ }.md&value=${encodeURIComponent(
15+ `+++
16+title = "${form?.appName}"
17+shortcode = "${form?.shortcode}"
18+license = "${form?.license || ""}"
19+image = "${form?.imageUrl || ""}"
20+bgColor = "${form?.bgColor || ""}
21+developer = "${form?.developer || ""}"
22+website = "${form?.website || ""}"
23++++
24+
25+${form?.description || ""}`
26+ )}`;
27+
28+ return (
29+ <Container>
30+ <Head>
31+ <title>Submit an application • Urbit.org</title>
32+ {Meta}
33+ </Head>
34+ <SingleColumn>
35+ <Section narrow className="space-y-12">
36+ <div className="flex flex-col space-y-4">
37+ <h2>Submit your application</h2>
38+ <p>
39+ Application submissions are accomplished through a GitHub pull
40+ request.
41+ </p>
42+ <p>
43+ You’ll need <a href="https://github.com">a GitHub account</a>{" "}
44+ before you get started to submit your group.
45+ </p>
46+ <p>
47+ First, enter your information and then click “Submit with GitHub.”
48+ You’ll be directed to a GitHub pull request page with a
49+ pre-populated markdown file. Click “Propose new file,” and then
50+ “Create pull request” on the following page.
51+ </p>
52+ </div>
53+ <div className="flex flex-col space-y-4">
54+ <h3>Enter your application information</h3>
55+ <div className="flex flex-col">
56+ <p>Application name (required)</p>
57+ <input
58+ className="bg-wall-100 p-2"
59+ name="appName"
60+ placeholder="pals"
61+ onChange={handleFormChange}
62+ />
63+ </div>
64+ <div className="flex flex-col">
65+ <p>Shortcode (required)</p>
66+ <input
67+ className="bg-wall-100 p-2"
68+ name="shortcode"
69+ placeholder="~paldev/pals"
70+ onChange={handleFormChange}
71+ />
72+ </div>
73+ <div className="flex flex-col w-full">
74+ <p>App image URL and color</p>
75+ <div className="flex space-x-4">
76+ <input
77+ className="flex-1 bg-wall-100 p-2"
78+ name="imageUrl"
79+ placeholder="https://..."
80+ onChange={handleFormChange}
81+ />
82+ <input
83+ name="bgColor"
84+ className="bg-wall-100 p-2 flex-1"
85+ placeholder="#FFCED0"
86+ onChange={handleFormChange}
87+ />
88+ </div>
89+ </div>
90+ <div className="flex flex-col">
91+ <p>License</p>
92+ <input
93+ className="bg-wall-100 p-2"
94+ name="license"
95+ placeholder="MIT"
96+ onChange={handleFormChange}
97+ />
98+ </div>
99+ <div className="flex flex-col">
100+ <p>Developer</p>
101+ <input
102+ className="bg-wall-100 p-2"
103+ name="developer"
104+ placeholder="~paldev"
105+ onChange={handleFormChange}
106+ />
107+ </div>
108+ <div className="flex flex-col">
109+ <p>Website</p>
110+ <input
111+ className="bg-wall-100 p-2"
112+ name="website"
113+ placeholder="https://pal.dev"
114+ onChange={handleFormChange}
115+ />
116+ </div>
117+ <div className="flex flex-col">
118+ <p>Description</p>
119+ <textarea
120+ className="resize-none bg-wall-100 p-2 h-60"
121+ name="description"
122+ onChange={handleFormChange}
123+ />
124+ </div>
125+ </div>
126+ <button
127+ className="button-lg bg-wall-600 text-wall-100"
128+ onClick={() => window.open(href)}
129+ >
130+ Submit with GitHub
131+ </button>
132+ <Link href="/">
133+ <a className="text-xl pt-8 block font-semibold">Urbit.org</a>
134+ </Link>
135+ </Section>
136+ </SingleColumn>
137+ </Container>
138+ );
139+};
140+
141+export default SubmissionPage;
pages/blog.js+7−4
@@ -61,9 +61,11 @@ export default function Blog({ posts, search }) {
6161 </div>
6262 ) : null}
6363 {post.extra.ship ? (
64− <div className="type-sub-bold text-wall-500 font-mono">
65− {post.extra.ship}
66− </div>
64+ <Link href={`/ids/${post.extra.ship}`}>
65+ <a className="type-sub-bold text-wall-500 font-mono">
66+ {post.extra.ship}
67+ </a>
68+ </Link>
6769 ) : null}
6870 </div>
6971 <div className="text-wall-500 type-sub">
@@ -84,7 +86,8 @@ export default function Blog({ posts, search }) {
8486export async function getStaticProps() {
8587 const posts = getAllPosts(
8688 ["title", "slug", "date", "description", "extra"],
87− "blog"
89+ "blog",
90+ "date"
8891 );
8992
9093 return {
pages/blog/[slug].js+7−4
@@ -8,6 +8,7 @@ import {
88 generateDisplayDate,
99} from "../../lib/lib";
1010import Head from "next/head";
11+import Link from "next/link";
1112import Meta from "../../components/Meta";
1213import ErrorPage from "../404";
1314import Container from "../../components/Container";
@@ -49,9 +50,11 @@ export default function Post({
4950 <div className="type-sub-bold mr-2">{post.extra.author}</div>
5051 ) : null}
5152 {post.extra.ship ? (
52− <div className="type-sub-bold text-wall-500 font-mono">
53− {post.extra.ship}
54− </div>
53+ <Link href={`/ids/${post.extra.ship}`}>
54+ <a className="type-sub-bold text-wall-500 font-mono">
55+ {post.extra.ship}
56+ </a>
57+ </Link>
5558 ) : null}
5659 </div>
5760 <div className="text-wall-500 type-sub">{formatDate(date)}</div>
@@ -110,7 +113,7 @@ export async function getStaticProps({ params }) {
110113}
111114
112115export async function getStaticPaths() {
113− const posts = getAllPosts(["slug", "date"], "blog");
116+ const posts = getAllPosts(["slug", "date"], "blog", "date");
114117
115118 return {
116119 paths: posts.map((post) => {
pages/events/[slug].js+1−1
@@ -169,7 +169,7 @@ export async function getStaticProps({ params }) {
169169}
170170
171171export async function getStaticPaths() {
172− const events = getAllPosts(["slug", "date"], "events");
172+ const events = getAllPosts(["slug", "date"], "events", "date");
173173
174174 return {
175175 paths: events.map((event) => {
pages/grants.js+2−6
@@ -463,12 +463,8 @@ export async function getStaticProps() {
463463 const types = getGrantsTypes();
464464 const posts = getAllPosts(
465465 ["title", "slug", "date", "description", "extra", "taxonomies"],
466− "grants"
467− );
468−
469− let updates = getAllPosts(
470− ["title", "slug", "date", "description", "extra", "taxonomies"],
471− "updates"
466+ "grants",
467+ "date"
472468 );
473469
474470 // The layout expects exactly 3
pages/grants/[slug].js+7−4
@@ -6,6 +6,7 @@ import {
66 getSimilarGrants,
77} from "../../lib/lib";
88import Head from "next/head";
9+import Link from "next/link";
910import Meta from "../../components/Meta";
1011import { decode } from "html-entities";
1112import classnames from "classnames";
@@ -43,9 +44,11 @@ export default function Grant({ post, markdown, search }) {
4344 </div>
4445 ) : null}
4546 {post.extra.ship ? (
46− <div className="type-ui text-wall-500 font-mono">
47− {post.extra.ship}
48− </div>
47+ <Link href={`/ids/${post.extra.ship}`}>
48+ <a className="type-sub-bold text-wall-500 font-mono">
49+ {post.extra.ship}
50+ </a>
51+ </Link>
4952 ) : null}
5053 <div className="type-ui text-wall-500 mt-4 md:mt-8 lg:mt-10">
5154 {formatDate(DateTime.fromISO(post.date))}
@@ -104,7 +107,7 @@ export async function getStaticProps({ params }) {
104107}
105108
106109export async function getStaticPaths() {
107− const posts = getAllPosts(["slug", "date"], "grants");
110+ const posts = getAllPosts(["slug", "date"], "grants", "date");
108111
109112 return {
110113 paths: posts.map((post) => {
pages/groups/[[...group]].jsnew file+145
@@ -0,0 +1,145 @@
1+import Head from "next/head";
2+import Link from "next/link";
3+import { getPage } from "../../lib/lib";
4+import { join } from "path";
5+import Meta from "../../components/Meta";
6+import CopyLink from "../../components/CopyLink";
7+import Container from "../../components/Container";
8+import SingleColumn from "../../components/SingleColumn";
9+import Section from "../../components/Section";
10+import ob from "urbit-ob";
11+import Markdown from "../../components/Markdown";
12+import GatewayHeader from "../../components/gateway/GatewayHeader";
13+import Gateway404 from "../../components/gateway/Gateway404";
14+import MetadataBlock from "../../components/gateway/MetadataBlock";
15+import MetadataLink from "../../components/gateway/MetadataLink";
16+import Description from "../../components/gateway/Description";
17+
18+const GroupPage = ({ data, markdown, params }) => {
19+ const { group } = params;
20+ if (!ob.isValidPatp(group[0])) {
21+ return <Gateway404 type="group" />;
22+ }
23+ const image = `https://urbit-id-og-cards-ltjw3771z-urbit.vercel.app/${
24+ data.title
25+ }?images=${
26+ data?.tile
27+ ? data.tile
28+ : "https://media.urbit.org/public-links/placeholder-image.png"
29+ }`;
30+
31+ return (
32+ <Container>
33+ <Head>
34+ <title>{data.title} • Groups • urbit.org</title>
35+ <link rel="icon" type="image/png" href="/images/favicon.ico" />
36+ <meta
37+ name="twitter:card"
38+ content="summary_large_image"
39+ key="twitter-card"
40+ />
41+ <meta
42+ name="og:title"
43+ content={`${data.title} • Groups • urbit.org`}
44+ key="title"
45+ />
46+ <meta
47+ name="og:description"
48+ content={
49+ data?.description || "View more about this group on urbit.org."
50+ }
51+ key="description"
52+ />
53+ <meta property="twitter:image" content={image} key="image" />
54+ </Head>
55+ <SingleColumn>
56+ <Section className="space-y-12" narrow>
57+ <GatewayHeader
58+ title={data.title}
59+ image={data?.tile}
60+ item="Urbit Group"
61+ />
62+ <div className="flex flex-wrap md:flex-nowrap justify-between">
63+ <MetadataBlock
64+ title="Group Type"
65+ content={data.type ? data.type : "Unknown"}
66+ />
67+ <MetadataBlock
68+ title="Members"
69+ content={
70+ data.participant_range ? data.participant_range : "Unknown"
71+ }
72+ />
73+ <MetadataLink
74+ title="Host"
75+ href={`/ids/${group[0]}`}
76+ content={group[0]}
77+ />
78+ <CopyLink
79+ className="basis-1/2"
80+ content={data.shortcode ? data.shortcode : data.title}
81+ />
82+ </div>
83+ <Description
84+ description={data.description}
85+ fallback="A group on Urbit."
86+ markdown={markdown}
87+ />
88+ <hr className="text-wall-200" />
89+ <div className="flex flex-col space-y-6">
90+ <h3>
91+ Urbit is a clean-slate, peer-to-peer operating system and network.
92+ </h3>
93+ <div className="flex flex-col space-y-4">
94+ <p className="text-sm font-semibold text-wall-400">
95+ Get on the network and join this group.
96+ </p>
97+ <Link href="/guides/joining-groups">
98+ <button className="button-lg max-w-xs bg-green-400 text-white">
99+ How to join a group
100+ </button>
101+ </Link>
102+ </div>
103+ </div>
104+ <hr className="text-wall-200" />
105+ <div className="flex flex-col space-y-1">
106+ <p>Have a group you'd like to share publicly through urbit.org?</p>
107+ <Link href="/groups/submit">
108+ <a className="type-ui text-green-400">Submit your group</a>
109+ </Link>
110+ </div>
111+ <Link href="/">
112+ <a className="text-xl pt-8 block font-semibold">Urbit.org</a>
113+ </Link>
114+ </Section>
115+ </SingleColumn>
116+ </Container>
117+ );
118+};
119+
120+export const getServerSideProps = async ({ params, res }) => {
121+ res.setHeader(
122+ "Cache-Control",
123+ "public, s-maxage=3600, stale-while-revalidate=604800"
124+ );
125+
126+ let { data, content } = getPage(
127+ join(process.cwd(), "content/groups", params.group?.join("/") || "/")
128+ ) || { data: {}, content: "" };
129+
130+ if (!data.title) {
131+ data = { title: params.group?.join("/"), description: "A group on Urbit." };
132+ }
133+
134+ const markdown = await Markdown({ post: { content: content } }, true);
135+
136+ return {
137+ props: {
138+ data,
139+ markdown,
140+ params,
141+ },
142+ };
143+};
144+
145+export default GroupPage;
pages/groups/submit.jsnew file+150
@@ -0,0 +1,150 @@
1+import Link from "next/link";
2+import { useState } from "react";
3+import Container from "../../components/Container";
4+import Section from "../../components/Section";
5+import SingleColumn from "../../components/SingleColumn";
6+import { useInputChange } from "../../lib/hooks";
7+import Head from "next/head";
8+import Meta from "../../components/Meta";
9+
10+const SubmissionPage = () => {
11+ const [form, handleFormChange] = useInputChange();
12+ const [publicGroup, setPublicGroup] = useState(true);
13+
14+ const href = `https://github.com/urbit/urbit.org/new/master?filename=/content/groups/${
15+ form?.shortcode
16+ }.md&value=${encodeURIComponent(
17+ `+++
18+title = "${form?.groupName}"
19+shortcode = "${form?.shortcode}"
20+type = "${publicGroup ? "Public" : "Private"}"
21+tile = "${form?.tile || ""}"
22+participant_range = "${form?.members || ""}"
23++++
24+
25+${form?.description || ""}`
26+ )}`;
27+
28+ return (
29+ <Container>
30+ <Head>
31+ <title>Submit a group • Urbit.org</title>
32+ {Meta}
33+ </Head>
34+ <SingleColumn>
35+ <Section narrow className="space-y-12">
36+ <div className="flex flex-col space-y-4">
37+ <h2>Submit your group</h2>
38+ <p>
39+ Groups submissions are accomplished through a GitHub pull request.
40+ </p>
41+ <p>
42+ You’ll need <a href="https://github.com">a GitHub account</a>{" "}
43+ before you get started to submit your group.
44+ </p>
45+ <p>
46+ First, enter your information and then click “Submit with GitHub.”
47+ You’ll be directed to a GitHub pull request page with a
48+ pre-populated markdown file. Click “Propose new file,” and then
49+ “Create pull request” on the following page.
50+ </p>
51+ <p>
52+ Then, to validate that you operate this group, send a DM to{" "}
53+ <Link href="/ids/~haddef-sigwen">
54+ <a>~haddef-sigwen</a>
55+ </Link>{" "}
56+ from your group host <code>@p</code> with a link to the pull
57+ request on GitHub. We’ll approve the pull request and your page
58+ will be online shortly.
59+ </p>
60+ </div>
61+ <div className="flex flex-col space-y-4">
62+ <h3>Enter your group information</h3>
63+ <div className="flex flex-col">
64+ <p>Group name (required)</p>
65+ <input
66+ className="bg-wall-100 p-2"
67+ name="groupName"
68+ placeholder="Urbit Community"
69+ onChange={handleFormChange}
70+ />
71+ </div>
72+ <div className="flex flex-col">
73+ <p>Group type</p>
74+ <div className="flex space-x-4">
75+ <div
76+ className="flex items-center space-x-2 cursor-pointer"
77+ onClick={() => setPublicGroup(true)}
78+ >
79+ <input
80+ type="radio"
81+ onChange={() => null}
82+ checked={publicGroup}
83+ />
84+ <p>Public</p>
85+ </div>
86+ <div
87+ className="flex items-center space-x-2 cursor-pointer"
88+ onClick={() => setPublicGroup(false)}
89+ >
90+ <input
91+ type="radio"
92+ onChange={() => null}
93+ checked={!publicGroup}
94+ />
95+ <p>Private</p>
96+ </div>
97+ </div>
98+ </div>
99+ <div className="flex flex-col">
100+ <p>Shortcode (required)</p>
101+ <input
102+ className="bg-wall-100 p-2"
103+ name="shortcode"
104+ placeholder="~bitbet-bolbel/urbit-community"
105+ onChange={handleFormChange}
106+ />
107+ </div>
108+ <div className="flex flex-col">
109+ <p>Group image URL or color</p>
110+ <input
111+ className="bg-wall-100 p-2"
112+ name="tile"
113+ placeholder="https://... or #FFCDE0"
114+ onChange={handleFormChange}
115+ />
116+ </div>
117+ <div className="flex flex-col">
118+ <p>Members</p>
119+ <input
120+ className="bg-wall-100 p-2"
121+ name="members"
122+ placeholder="1000+"
123+ onChange={handleFormChange}
124+ />
125+ </div>
126+ <div className="flex flex-col">
127+ <p>Description</p>
128+ <textarea
129+ className="resize-none bg-wall-100 p-2 h-60"
130+ name="description"
131+ onChange={handleFormChange}
132+ />
133+ </div>
134+ </div>
135+ <button
136+ className="button-lg bg-wall-600 text-wall-100"
137+ onClick={() => window.open(href)}
138+ >
139+ Submit with GitHub
140+ </button>
141+ <Link href="/">
142+ <a className="text-xl pt-8 block font-semibold">Urbit.org</a>
143+ </Link>
144+ </Section>
145+ </SingleColumn>
146+ </Container>
147+ );
148+};
149+
150+export default SubmissionPage;
pages/guides/[slug].jsnew file+75
@@ -0,0 +1,75 @@
1+import { useRouter } from "next/router";
2+import { getPostBySlug, getAllPosts } from "../../lib/lib";
3+import Head from "next/head";
4+import Link from "next/link";
5+import Meta from "../../components/Meta";
6+import ErrorPage from "../404";
7+import Container from "../../components/Container";
8+import Markdown from "../../components/Markdown";
9+import GuideColumn from "../../components/GuideColumn";
10+import Section from "../../components/Section";
11+
12+import { decode } from "html-entities";
13+
14+export default function GuidePage({ post, markdown }) {
15+ const router = useRouter();
16+ if (!router.isFallback && !post?.slug) {
17+ return <ErrorPage />;
18+ }
19+
20+ return (
21+ <Container>
22+ <Head>
23+ <title>Urbit • {post.title}</title>
24+ {Meta(post)}
25+ </Head>
26+ <GuideColumn>
27+ <Section narrow short>
28+ <h2>{post.title}</h2>
29+ </Section>
30+ <Section narrow short className="markdown">
31+ <article
32+ dangerouslySetInnerHTML={{ __html: decode(markdown) }}
33+ ></article>
34+ <Link href="/">
35+ <a className="text-xl pt-12 block font-semibold">Urbit.org</a>
36+ </Link>
37+ </Section>
38+ </GuideColumn>
39+ </Container>
40+ );
41+}
42+
43+//
44+export async function getStaticProps({ params }) {
45+ const post = getPostBySlug(
46+ params.slug,
47+ ["title", "slug", "date", "description", "content", "extra"],
48+ "guides"
49+ );
50+
51+ const markdown = await Markdown({ post });
52+
53+ return {
54+ props: { post, markdown },
55+ };
56+}
57+
58+export async function getStaticPaths() {
59+ const posts = getAllPosts(["slug", "date"], "guides", "date");
60+
61+ const noApplications = posts.filter(
62+ (e) => e.slug !== "installing-applications"
63+ );
64+
65+ return {
66+ paths: noApplications.map((post) => {
67+ return {
68+ params: {
69+ slug: post.slug,
70+ },
71+ };
72+ }),
73+ fallback: false,
74+ };
75+}
pages/guides/installing-applications.jsnew file+106
@@ -0,0 +1,106 @@
1+import Head from "next/head";
2+import Link from "next/link";
3+import { getPostBySlug } from "../../lib/lib";
4+import Meta from "../../components/Meta";
5+import Markdown from "../../components/Markdown";
6+import Container from "../../components/Container";
7+import GuideColumn from "../../components/GuideColumn";
8+import Section from "../../components/Section";
9+import { decode } from "html-entities";
10+
11+export default function GuidePage({ post, markdown }) {
12+ return (
13+ <Container>
14+ <Head>
15+ <title>Urbit • {post.title}</title>
16+ {Meta(post)}
17+ </Head>
18+ <GuideColumn>
19+ <Section narrow short>
20+ <h2>{post.title}</h2>
21+ </Section>
22+ <Section narrow short>
23+ <article
24+ className="markdown"
25+ dangerouslySetInnerHTML={{ __html: decode(markdown) }}
26+ ></article>
27+ <h2 className="text-3xl my-12">5. Add more applications</h2>
28+ <p className="my-2">Here's a few more applications to check out:</p>
29+ <Link href="/applications/~paldev/pals">
30+ <div class="bg-wall-100 rounded-xl p-3 flex flex-col space-y-4 my-6 cursor-pointer">
31+ <div class="flex space-x-4 items-center">
32+ <div
33+ class="rounded-xl"
34+ style={{
35+ height: 100,
36+ width: 100,
37+ backgroundColor: "#A6D2BE",
38+ }}
39+ ></div>
40+ <h3>pals</h3>
41+ </div>
42+ <p>add people you know on the network</p>
43+ </div>
44+ </Link>
45+
46+ <Link href="/applications/~fabnev-himnur/escape">
47+ <div class="bg-wall-100 rounded-xl p-3 flex flex-col space-y-4 my-6 cursor-pointer">
48+ <div class="flex space-x-4 items-center">
49+ <img
50+ class="rounded-xl"
51+ src="https://media.urbit.org/site/application/escape.png"
52+ style={{
53+ backgroundColor: "#ffffff",
54+ height: 100,
55+ width: 100,
56+ }}
57+ />
58+ <h3>EScape</h3>
59+ </div>
60+ <p>
61+ an alternative interface for the Groups application, with pals
62+ integration by{" "}
63+ <span className="text-green-400 font-mono">~fabnev-hinmur</span>
64+ </p>
65+ </div>
66+ </Link>
67+
68+ <Link href="/applications/~paldev/rumors">
69+ <div class="bg-wall-100 rounded-xl p-3 flex flex-col space-y-4 cursor-pointer">
70+ <div class="flex space-x-4 items-center">
71+ <div
72+ class="rounded-xl"
73+ style={{
74+ backgroundColor: "#B17AD7",
75+ height: 100,
76+ width: 100,
77+ }}
78+ ></div>
79+ <h3>rumors</h3>
80+ </div>
81+ <p>the more people you know, the more rumors you’ll hear</p>
82+ </div>
83+ </Link>
84+ <Link href="/">
85+ <a className="text-xl pt-12 block font-semibold">Urbit.org</a>
86+ </Link>
87+ </Section>
88+ </GuideColumn>
89+ </Container>
90+ );
91+}
92+
93+//
94+export async function getStaticProps({ params }) {
95+ const post = getPostBySlug(
96+ "installing-applications",
97+ ["title", "slug", "content"],
98+ "guides"
99+ );
100+
101+ const markdown = await Markdown({ post });
102+
103+ return {
104+ props: { post, markdown },
105+ };
106+}
pages/ids/[id].jsnew file+242
@@ -0,0 +1,242 @@
1+import Head from "next/head";
2+import Link from "next/link";
3+import { getPage, getAllPosts } from "../../lib/lib";
4+import { join } from "path";
5+import Container from "../../components/Container";
6+import SingleColumn from "../../components/SingleColumn";
7+import Section from "../../components/Section";
8+import ob from "urbit-ob";
9+import Markdown from "../../components/Markdown";
10+import ResourceCard from "../../components/ResourceCard";
11+import GatewayHeader from "../../components/gateway/GatewayHeader";
12+import Gateway404 from "../../components/gateway/Gateway404";
13+import MetadataBlock from "../../components/gateway/MetadataBlock";
14+import MetadataLink from "../../components/gateway/MetadataLink";
15+import Description from "../../components/gateway/Description";
16+import axios from "axios";
17+
18+const IdPage = ({ data, markdown, applications, groups, network, params }) => {
19+ let { id } = params;
20+ id = `~${deSig(id)}`;
21+ if (!ob.isValidPatp(id) || id.length > 14) {
22+ return <Gateway404 type="ID" />;
23+ }
24+
25+ const reqParams = [
26+ data?.bgColor ? `color=${encodeURIComponent(data?.bgColor)}` : "",
27+ data?.nickname ? `nickname=${encodeURIComponent(data?.nickname)}` : "",
28+ data?.image ? `images=${encodeURIComponent(data?.image)}` : "",
29+ ].filter((e) => e !== "");
30+ const image = `https://urbit-id-og-cards-kappa.vercel.app/${deSig(
31+ id
32+ )}.png?${reqParams.join("&")}`;
33+
34+ // Parent ID, grabbed from network or fallback to the default sponsor for that node
35+ const parent = network ? network.sponsor["urbit-id"] : ob.sein(id);
36+ // Galaxy name above that parent
37+ const galaxy =
38+ ob.clan(id) === "planet"
39+ ? network?.sponsor?.sponsor?.["urbit-id"] || ob.sein(parent)
40+ : null;
41+ // Galaxies shouldn't show parents, so store it as boolean here for reference.
42+ const isGalaxy = ob.clan(id) === "galaxy";
43+
44+ return (
45+ <Container>
46+ <Head>
47+ <title>{id} • Urbit ID • urbit.org</title>
48+ <link rel="icon" type="image/png" href="/images/favicon.ico" />
49+ <meta
50+ name="twitter:card"
51+ content="summary_large_image"
52+ key="twitter-card"
53+ />
54+ <meta
55+ name="og:title"
56+ content={`${id} • Urbit ID • urbit.org`}
57+ key="title"
58+ />
59+ <meta
60+ name="og:description"
61+ content="View more about this Urbit ID on urbit.org."
62+ key="description"
63+ />
64+ <meta property="twitter:image" content={image} key="image" />
65+ </Head>
66+ <SingleColumn>
67+ <Section className="space-y-12 w-full" narrow>
68+ <GatewayHeader
69+ title={data?.nickname ? data?.nickname : id}
70+ subtitle={data?.nickname ? id : false}
71+ subtitleMono
72+ color={data?.bgColor || "#24201E"}
73+ image={data?.image || false}
74+ patp={!data?.image ? id : false}
75+ item="Urbit ID"
76+ />
77+ <div className="flex flex-wrap md:flex-nowrap justify-between">
78+ <MetadataBlock
79+ title="ID Type"
80+ content={
81+ ob.clan(id).slice(0, 1).toUpperCase() + ob.clan(id).slice(1)
82+ }
83+ />
84+ <MetadataBlock
85+ title="Status"
86+ content={network ? "Spawned/Owned" : "Unspawned"}
87+ />
88+ {!isGalaxy && (
89+ <MetadataLink
90+ title="Parent"
91+ href={`/ids/${parent}`}
92+ content={parent}
93+ />
94+ )}
95+ {galaxy && (
96+ <MetadataLink
97+ title="Galaxy"
98+ href={`/ids/${galaxy}`}
99+ content={galaxy}
100+ />
101+ )}
102+ </div>
103+ <Creations
104+ title="Applications"
105+ id={id}
106+ type="application"
107+ data={applications}
108+ />
109+ <Creations title="Hosted Groups" id={id} type="group" data={groups} />
110+ <Description
111+ description={data.description}
112+ fallback="An Urbit ID."
113+ markdown={markdown}
114+ />
115+ <a
116+ className="flex items-center"
117+ target="_blank"
118+ href={`https://network.urbit.org/${id}`}
119+ >
120+ <div className="flex items-center">
121+ <svg
122+ width="32"
123+ height="32"
124+ viewBox="0 0 32 32"
125+ fill="none"
126+ xmlns="http://www.w3.org/2000/svg"
127+ >
128+ <path
129+ d="M14.56 6.39999C11.9487 6.40124 9.49503 7.65251 7.95999 9.76639C6.42623 11.8789 5.99624 14.6 6.80373 17.0838C7.61122 19.5676 9.55874 21.5139 12.0425 22.3213C14.5274 23.1275 17.2476 22.6963 19.3599 21.1613L23.4236 25.225C23.9236 25.7262 24.7348 25.7262 25.2348 25.225C25.7348 24.725 25.7348 23.9149 25.2348 23.415L21.1712 19.3513V19.35C22.3586 17.7188 22.8974 15.7049 22.6862 13.6988C22.4749 11.6926 21.5274 9.83516 20.0262 8.48764C18.5249 7.1389 16.5775 6.39513 14.5599 6.40015L14.56 6.39999ZM14.56 20.16C13.075 20.16 11.65 19.57 10.6 18.52C9.55 17.47 8.95999 16.045 8.95999 14.56C8.95999 13.075 9.55 11.65 10.6 10.6C11.65 9.55001 13.075 8.95999 14.56 8.95999C16.045 8.95999 17.47 9.55001 18.52 10.6C19.57 11.65 20.16 13.075 20.16 14.56C20.1587 16.045 19.5675 17.4675 18.5175 18.5174C17.4675 19.5674 16.045 20.1587 14.5601 20.1599L14.56 20.16Z"
130+ fill="#80D8B8"
131+ />
132+ </svg>
133+ <p className="text-green-400 font-semibold">
134+ View on Network Explorer
135+ </p>
136+ </div>
137+ </a>
138+ <hr className="text-wall-200" />
139+ <div className="flex flex-col space-y-6">
140+ <h3>
141+ Urbit is a clean-slate, peer-to-peer operating system and network.
142+ </h3>
143+ <div className="flex flex-col space-y-4">
144+ <p className="text-md font-semibold text-wall-400">
145+ Learn how get your own Urbit ID.
146+ </p>
147+ <Link href="/guides/getting-an-urbit-id">
148+ <button className="button-lg max-w-xs bg-green-400 text-white">
149+ Get an Urbit ID
150+ </button>
151+ </Link>
152+ </div>
153+ </div>
154+ <hr className="text-wall-200" />
155+ <div className="flex flex-col space-y-1">
156+ <p>Have an Urbit ID?</p>
157+ <Link href="/ids/submit">
158+ <a className="type-ui text-green-400">
159+ Claim and customize your Urbit ID page
160+ </a>
161+ </Link>
162+ </div>
163+ <Link href="/">
164+ <a className="text-xl pt-8 block font-semibold">Urbit.org</a>
165+ </Link>
166+ </Section>
167+ </SingleColumn>
168+ </Container>
169+ );
170+};
171+
172+const Creations = ({ id, title, data, type }) => {
173+ return (
174+ data?.length > 0 && (
175+ <div className="flex space-y-2 flex-col">
176+ <p className="text-wall-400 font-semibold">{title}</p>
177+ <div className="flex flex-wrap">
178+ {data.map((each) => (
179+ <ResourceCard
180+ type={type}
181+ shortcode={`${id}/${each.slug}`}
182+ title={each.title}
183+ color={each.bgColor}
184+ image={type === "group" ? each.tile : each.image}
185+ />
186+ ))}
187+ </div>
188+ </div>
189+ )
190+ );
191+};
192+
193+function deSig(string) {
194+ return string.startsWith("~") ? string.substring(1) : string;
195+}
196+
197+export const getServerSideProps = async ({ params, res }) => {
198+ res.setHeader(
199+ "Cache-Control",
200+ "public, s-maxage=3600, stale-while-revalidate=604800"
201+ );
202+
203+ let { id } = params;
204+ id = `~${deSig(id)}`;
205+
206+ let { data, content } = getPage(
207+ join(process.cwd(), "content/ids/", id.slice(1))
208+ ) || { data: {}, content: "" };
209+
210+ const applications = getAllPosts(
211+ ["title", "slug", "image", "bgColor"],
212+ `applications/${params.id}`
213+ );
214+ const groups = getAllPosts(["title", "slug", "tile"], `groups/${id}`);
215+
216+ if (!data.ship && ob.isValidPatp(id)) {
217+ data = { ship: id, description: "An Urbit ID." };
218+ }
219+
220+ let network = await axios
221+ .get(
222+ `https://mt2aga2c5l.execute-api.us-east-2.amazonaws.com/get-node?urbit-id=${
223+ ob.isValidPatp(id) ? id : "~zod"
224+ }`
225+ )
226+ .then((res) => res.data);
227+
228+ const markdown = await Markdown({ post: { content: content } }, true);
229+
230+ return {
231+ props: {
232+ data,
233+ applications,
234+ groups,
235+ markdown,
236+ network,
237+ params,
238+ },
239+ };
240+};
241+
242+export default IdPage;
pages/ids/submit.jsnew file+163
@@ -0,0 +1,163 @@
1+import Link from "next/link";
2+import Container from "../../components/Container";
3+import Section from "../../components/Section";
4+import SingleColumn from "../../components/SingleColumn";
5+import { useInputChange } from "../../lib/hooks";
6+import ob from "urbit-ob";
7+import Sigil from "../../components/Sigil";
8+import Head from "next/head";
9+import Meta from "../../components/Meta";
10+
11+const SubmissionPage = () => {
12+ const [form, handleFormChange] = useInputChange();
13+
14+ const href = `https://github.com/urbit/urbit.org/new/master?filename=/content/ids/${form?.ship?.substring(
15+ 1
16+ )}.md&value=${encodeURIComponent(
17+ `+++
18+ship = "${form?.ship}"
19+nickname = "${form?.nickname}"
20+bgcolor = "${form?.bgcolor}"
21+image = "${form?.image}"
22++++
23+
24+${form?.description || ""}`
25+ )}`;
26+
27+ let validColor = /^#[0-9A-F]{6}$/i.test(form?.bgColor)
28+ ? form?.bgColor
29+ : "#24201E";
30+
31+ return (
32+ <Container>
33+ <Head>
34+ <title>Claim an Urbit ID page • Urbit.org</title>
35+ {Meta}
36+ </Head>
37+ <SingleColumn>
38+ <Section narrow className="space-y-12">
39+ <div className="flex flex-col space-y-4">
40+ <h2>Claim your page</h2>
41+ <p>
42+ Urbit ID page claims are accomplished through a GitHub pull
43+ request.
44+ </p>
45+ <p>
46+ You’ll need <a href="https://github.com">a GitHub account</a>{" "}
47+ before you get started.
48+ </p>
49+ <p>
50+ First, enter your information and then click “Submit with GitHub.”
51+ You’ll be directed to a GitHub pull request page with a
52+ pre-populated markdown file. Click “Propose new file,” and then
53+ “Create pull request” on the following page.
54+ </p>
55+ <p>
56+ Then, to validate that you operate this group, send a DM to{" "}
57+ <Link href="/ids/~haddef-sigwen">
58+ <a>~haddef-sigwen</a>
59+ </Link>{" "}
60+ from your group host <code>@p</code> with a link to the pull
61+ request on GitHub. We’ll approve the pull request and your page
62+ will be online shortly.
63+ </p>
64+ </div>
65+ <div className="flex flex-col space-y-4">
66+ <h3>Enter your Urbit ID information</h3>
67+ <div className="flex flex-col">
68+ <p>Urbit ID (required)</p>
69+ <div className="flex items-center">
70+ {form?.image ? (
71+ <img
72+ className="mr-2 rounded-lg"
73+ src={form?.image}
74+ style={{ height: 32, width: 32 }}
75+ />
76+ ) : (
77+ <div
78+ className="flex p-2 rounded-lg mr-2"
79+ style={{
80+ backgroundColor: validColor,
81+ display: form?.ship?.length > 14 ? "none" : "block",
82+ }}
83+ >
84+ <Sigil
85+ icon
86+ color={validColor}
87+ size="25"
88+ patp={
89+ ob.isValidPatp(form?.ship || "") ? form?.ship : "~zod"
90+ }
91+ />
92+ </div>
93+ )}
94+ <input
95+ className="bg-wall-100 p-2 flex-1"
96+ name="ship"
97+ placeholder="~bitbet-bolbel"
98+ onChange={handleFormChange}
99+ />
100+ </div>
101+ </div>
102+ <div className="flex flex-col">
103+ <p>Nickname (optional)</p>
104+ <input placeholder="Bitbet" className="bg-wall-100 p-2" />
105+ </div>
106+ <div>
107+ <p>Sigil color (or avatar image)</p>
108+ <div className="flex space-x-2">
109+ <input
110+ placeholder="#24201E"
111+ name="bgColor"
112+ className="bg-wall-100 p-2 flex-1"
113+ onChange={handleFormChange}
114+ />
115+ <input
116+ placeholder="https://..."
117+ name="image"
118+ className="bg-wall-100 p-2 flex-1"
119+ onChange={handleFormChange}
120+ />
121+ </div>
122+ </div>
123+ <div className="flex flex-col">
124+ <p>Hosted groups</p>
125+ <input
126+ className="bg-wall-100 p-2 text-wall-400"
127+ readOnly
128+ value="Submitted groups associated with this ID will automatically appear"
129+ />
130+ </div>
131+ <div className="flex flex-col">
132+ <p>Applications</p>
133+ <input
134+ className="bg-wall-100 p-2 text-wall-400"
135+ readOnly
136+ value="Submitted applications associated with this ID will automatically appear"
137+ />
138+ </div>
139+ <div className="flex flex-col">
140+ <p>Description</p>
141+ <textarea
142+ className="resize-none bg-wall-100 p-2 h-60"
143+ name="description"
144+ onChange={handleFormChange}
145+ />
146+ </div>
147+ </div>
148+ <button
149+ className="button-lg bg-wall-600 text-wall-100"
150+ onClick={() => window.open(href)}
151+ >
152+ Submit with GitHub
153+ </button>
154+ <Link href="/">
155+ <a className="text-xl pt-8 block font-semibold">Urbit.org</a>
156+ </Link>
157+ </Section>
158+ </SingleColumn>
159+ </Container>
160+ );
161+};
162+
163+export default SubmissionPage;
pages/index.js+2−1
@@ -284,7 +284,8 @@ export async function getStaticProps() {
284284
285285 const posts = getAllPosts(
286286 ["title", "slug", "date", "description", "extra"],
287− "blog"
287+ "blog",
288+ "date"
288289 );
289290
290291 const events = getAllEvents(eventKeys, "events");
pages/media.js+2−1
@@ -100,7 +100,8 @@ export default function Media({ posts, search }) {
100100export async function getStaticProps() {
101101 const posts = getAllPosts(
102102 ["title", "slug", "date", "description", "extra"],
103− "media"
103+ "media",
104+ "date"
104105 );
105106
106107 return {
pages/media/[slug].js+7−4
@@ -6,6 +6,7 @@ import {
66 generateDisplayDate,
77} from "../../lib/lib";
88import Head from "next/head";
9+import Link from "next/link";
910import Meta from "../../components/Meta";
1011import ErrorPage from "../404";
1112import Container from "../../components/Container";
@@ -40,9 +41,11 @@ export default function MediaPage({ post, markdown, search }) {
4041 </div>
4142 ) : null}
4243 {post.extra.ship ? (
43− <div className="type-ui text-wall-500 font-mono">
44− {post.extra.ship}
45− </div>
44+ <Link href={`/ids/${post.extra.ship}`}>
45+ <a className="type-sub-bold text-wall-500 font-mono">
46+ {post.extra.ship}
47+ </a>
48+ </Link>
4649 ) : null}
4750 <div className="type-ui text-wall-500 mt-16">{formatDate(date)}</div>
4851 </Section>
@@ -100,7 +103,7 @@ export async function getStaticProps({ params }) {
100103}
101104
102105export async function getStaticPaths() {
103− const posts = getAllPosts(["slug", "date"], "media");
106+ const posts = getAllPosts(["slug", "date"], "media", "date");
104107
105108 return {
106109 paths: posts.map((post) => {
pages/updates.js+7−4
@@ -47,9 +47,11 @@ export default function Updates({ posts, search }) {
4747 <div className="mx-1 text-wall-500">•</div>
4848 ) : null}
4949 {post?.extra.ship ? (
50− <div className="type-ui text-wall-500 font-mono">
51− {post.extra.ship}
52− </div>
50+ <Link href={`/ids/${post.extra.ship}`}>
51+ <a className="type-sub-bold text-wall-500 font-mono">
52+ {post.extra.ship}
53+ </a>
54+ </Link>
5355 ) : null}
5456 </div>
5557
@@ -71,7 +73,8 @@ export default function Updates({ posts, search }) {
7173export async function getStaticProps() {
7274 const posts = getAllPosts(
7375 ["title", "slug", "date", "description", "extra"],
74− "updates"
76+ "updates",
77+ "date"
7578 );
7679
7780 return {
pages/updates/[slug].js+7−3
@@ -47,8 +47,12 @@ export default function Post({
4747 {post.author}
4848 </div>
4949 ) : null}
50− {post.ship ? (
51− <div className="type-ui text-wall-500 font-mono">{post.ship}</div>
50+ {post?.extra?.ship ? (
51+ <Link href={`/ids/${post.extra.ship}`}>
52+ <a className="type-sub-bold text-wall-500 font-mono">
53+ {post?.extra?.ship}
54+ </a>
55+ </Link>
5256 ) : null}
5357 <div className="type-ui text-wall-500 mt-4 md:mt-8 lg:mt-10">
5458 {formatDate(date)}
@@ -120,7 +124,7 @@ export async function getStaticProps({ params }) {
120124}
121125
122126export async function getStaticPaths() {
123− const posts = getAllPosts(["slug", "date"], "updates");
127+ const posts = getAllPosts(["slug", "date"], "updates", "date");
124128
125129 return {
126130 paths: posts.map((post) => {
styles/globals.css+3
@@ -83,6 +83,9 @@ code .type-code {
8383 @apply bg-wall-100 p-4 font-mono;
8484}
8585
86+.font-bold {
87+ @apply antialiased;
88+}
8689/* Utils */
8790
8891.layout-narrow {