Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

83 linesCodeBlameRaw
1/**
2 * The renderer for the policies, which are written in Markdown under
3 * app/content/policies so that counsel can read and mark them up as plain
4 * text. The frame of a trust page is in trust-page.tsx.
5 */
6import { type ReactNode, isValidElement } from "react";
7import ReactMarkdown from "react-markdown";
8import { Link } from "react-router";
9import remarkGfm from "remark-gfm";
10
11function textOf(node: ReactNode): string {
12 if (node == null || typeof node === "boolean") return "";
13 if (typeof node === "string" || typeof node === "number") return String(node);
14 if (Array.isArray(node)) return node.map(textOf).join("");
15 if (isValidElement<{ children?: ReactNode }>(node)) return textOf(node.props.children);
16 return "";
17}
18
19/** A heading's anchor: "8. Paying for g1t" is `paying-for-g1t`. */
20export function anchor(children: ReactNode): string {
21 return textOf(children)
22 .toLowerCase()
23 .replace(/^\d+\.\s*/, "")
24 .replace(/[^a-z0-9]+/g, "-")
25 .replace(/^-|-$/g, "");
26}
27
28/** The headings of a policy, for its contents list. */
29export function outline(markdown: string): { id: string; title: string }[] {
30 return [...markdown.matchAll(/^## (.+)$/gm)].map((match) => ({ id: anchor(match[1]), title: match[1] }));
31}
32
33export { TrustPage } from "./trust-page";
34
35/** A Markdown policy. Links to g1t stay in the app; others are plain. */
36export function PolicyText({ source }: { source: string }) {
37 return (
38 <div className="prose max-w-3xl">
39 <ReactMarkdown
40 remarkPlugins={[remarkGfm]}
41 components={{
42 h2: ({ children }) => (
43 <h2 id={anchor(children)} className="scroll-mt-20">
44 {children}
45 </h2>
46 ),
47 h3: ({ children }) => (
48 <h3 id={anchor(children)} className="scroll-mt-20">
49 {children}
50 </h3>
51 ),
52 a: ({ href = "", children }) => (href.startsWith("/") ? <Link to={href}>{children}</Link> : <a href={href}>{children}</a>),
53 table: ({ children }) => (
54 <div className="overflow-x-auto">
55 <table>{children}</table>
56 </div>
57 ),
58 }}
59 >
60 {source}
61 </ReactMarkdown>
62 </div>
63 );
64}
65
66/** "On this page", for the side of a long page. */
67export function Contents({ items }: { items: { id: string; title: string }[] }) {
68 if (items.length < 3) return null;
69 return (
70 <nav aria-label="On this page" className="lg:sticky lg:top-20">
71 <p className="text-xs font-medium tracking-wider text-faint uppercase">On this page</p>
72 <ol className="mt-3 space-y-1.5 border-l border-line text-sm">
73 {items.map((item) => (
74 <li key={item.id}>
75 <a href={`#${item.id}`} className="-ml-px block border-l border-transparent pl-3 text-muted hover:border-line-strong hover:text-fg">
76 {item.title.replace(/^\d+\.\s*/, "")}
77 </a>
78 </li>
79 ))}
80 </ol>
81 </nav>
82 );
83}