Compare commits
4 Commits
cojson-sto
...
docs/toc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f3990de3f | ||
|
|
7118fa0413 | ||
|
|
60928b3a44 | ||
|
|
c3335f8def |
@@ -1,13 +0,0 @@
|
||||
import { Prose } from "gcmp-design-system/src/app/components/molecules/Prose";
|
||||
|
||||
export default function DocsLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<Prose className="container w-full max-w-full col-span-12 md:col-span-8 lg:col-span-9">
|
||||
{children}
|
||||
</Prose>
|
||||
);
|
||||
}
|
||||
52
homepage/homepage/app/docs/[...slug]/layout.tsx
Normal file
52
homepage/homepage/app/docs/[...slug]/layout.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
// app/docs/[...slug]/layout.tsx
|
||||
|
||||
import React from "react";
|
||||
import { DocNav } from "@/components/docs/nav";
|
||||
import { Prose } from "gcmp-design-system/src/app/components/molecules/Prose";
|
||||
import path from "path";
|
||||
import { extractMDXSourceAndTOC, TOCItem } from "@/lib/extractMDXSourceAndTOC";
|
||||
import { MDXRemote } from "next-mdx-remote/rsc";
|
||||
import { CodeGroup } from "@/components/forMdx"; // Import your custom components
|
||||
import { TOC } from "@/components/TOC";
|
||||
import { compile, evaluate, run } from "@mdx-js/mdx";
|
||||
|
||||
// import TOC from "@/components/TOC";
|
||||
|
||||
// Define the LayoutProps interface
|
||||
interface LayoutProps {
|
||||
children: React.ReactNode;
|
||||
params: { slug: string[] };
|
||||
}
|
||||
|
||||
import * as runtime from "react/jsx-runtime";
|
||||
|
||||
export default async function DocsLayout({ children, params }: LayoutProps) {
|
||||
const { slug } = params;
|
||||
const mdxPath = path.join(
|
||||
process.cwd(),
|
||||
"content",
|
||||
"docs",
|
||||
...slug,
|
||||
"page.mdx",
|
||||
);
|
||||
|
||||
try {
|
||||
const { toc, source } = await extractMDXSourceAndTOC(mdxPath);
|
||||
|
||||
return (
|
||||
<div className="container relative flex justify-between gap-5 py-8">
|
||||
<DocNav className="w-1/4" />
|
||||
<Prose className="w-1/2">
|
||||
{children}
|
||||
<MDXRemote source={source} components={{ CodeGroup }} />
|
||||
</Prose>
|
||||
<aside className="toc-sidebar w-1/4">
|
||||
<TOC toc={toc} />
|
||||
</aside>
|
||||
</div>
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(`Error processing MDX file at ${mdxPath}:`, error);
|
||||
return <div>Error loading documentation.</div>;
|
||||
}
|
||||
}
|
||||
50
homepage/homepage/app/docs/[...slug]/page.tsx
Normal file
50
homepage/homepage/app/docs/[...slug]/page.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
// app/docs/[...slug]/page.tsx
|
||||
|
||||
import React from "react";
|
||||
|
||||
// app/docs/[...slug]/page.tsx
|
||||
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
export async function generateStaticParams() {
|
||||
const docsDirectory = path.join(process.cwd(), "app", "docs");
|
||||
const slugs = getAllMDXSlugs(docsDirectory);
|
||||
|
||||
return slugs.map((slug) => ({
|
||||
slug: slug.split("/").filter(Boolean), // Convert slug string to array
|
||||
}));
|
||||
}
|
||||
|
||||
// Helper function to recursively get all slugs
|
||||
function getAllMDXSlugs(dir: string, relativePath = ""): string[] {
|
||||
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
||||
let slugs: string[] = [];
|
||||
|
||||
for (const entry of entries) {
|
||||
const fullPath = path.join(dir, entry.name);
|
||||
const entryRelativePath = path.join(relativePath, entry.name);
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
slugs = slugs.concat(getAllMDXSlugs(fullPath, entryRelativePath));
|
||||
} else if (entry.isFile() && entry.name === "page.mdx") {
|
||||
const slug = path.dirname(entryRelativePath); // Remove 'page.mdx' from the path
|
||||
slugs.push(slug);
|
||||
}
|
||||
}
|
||||
|
||||
return slugs;
|
||||
}
|
||||
|
||||
// Rest of your page code...
|
||||
|
||||
interface PageProps {
|
||||
params: { slug: string[] };
|
||||
}
|
||||
|
||||
export default function DocsPage(props: PageProps) {
|
||||
console.log("DocsPage props", props);
|
||||
// The content is already handled by the layout
|
||||
// return <MDXRemote source={markdown} />;
|
||||
return <></>;
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
import Guide from "./guide.mdx";
|
||||
import { TableOfContents } from "@/components/docs/TableOfContents";
|
||||
import { Prose } from "gcmp-design-system/src/app/components/molecules/Prose";
|
||||
import { clsx } from "clsx";
|
||||
|
||||
const navItems = [
|
||||
{
|
||||
name: "Project Setup",
|
||||
href: "/docs/guide#project-setup",
|
||||
},
|
||||
{
|
||||
name: "Intro to CoValues",
|
||||
href: "/docs/guide#intro-to-covalues",
|
||||
items: [
|
||||
{
|
||||
name: "Declaration",
|
||||
href: "/docs/guide#declaring-covalues",
|
||||
},
|
||||
{
|
||||
name: "Reading",
|
||||
href: "/docs/guide#reading-covalues",
|
||||
},
|
||||
{
|
||||
name: "Creation",
|
||||
href: "/docs/guide#creating-covalues",
|
||||
},
|
||||
{
|
||||
name: "Editing & Subscription",
|
||||
href: "/docs/guide#editing-and-subscription",
|
||||
},
|
||||
{
|
||||
name: "Persistence",
|
||||
href: "/docs/guide#persistence",
|
||||
},
|
||||
{
|
||||
name: "Remote Sync",
|
||||
href: "/docs/guide#remote-sync",
|
||||
},
|
||||
{
|
||||
name: "Simple Public Sharing",
|
||||
href: "/docs/guide#simple-public-sharing",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Refs & Auto-Subscribe",
|
||||
href: "/docs/guide#refs-and-on-demand-subscribe",
|
||||
items: [
|
||||
{
|
||||
name: "Precise Loading Depths",
|
||||
href: "/docs/guide#loading-depth",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Groups & Permissions",
|
||||
href: "/docs/guide#groups-and-permissions",
|
||||
items: [
|
||||
{
|
||||
name: "Groups/Accounts as Scopes",
|
||||
href: "/docs/guide#groups-accounts-as-scopes",
|
||||
},
|
||||
{
|
||||
name: "Creating Invites",
|
||||
href: "/docs/guide#creating-invites",
|
||||
},
|
||||
{
|
||||
name: "Consuming Invites",
|
||||
href: "/docs/guide#consuming-invites",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"col-span-12 md:col-span-8 lg:col-span-9",
|
||||
"flex justify-center lg:gap-5",
|
||||
)}
|
||||
>
|
||||
<Prose className="overflow-x-hidden lg:flex-1">
|
||||
<Guide />
|
||||
</Prose>
|
||||
<TableOfContents className="w-48 shrink-0" items={navItems} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,19 +1,3 @@
|
||||
import { DocNav } from "@/components/docs/nav";
|
||||
|
||||
export const metadata = {
|
||||
title: "Docs",
|
||||
description: "Jazz Guide & Docs.",
|
||||
};
|
||||
|
||||
export default function DocsLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<div className="container relative grid grid-cols-12 gap-5 py-8">
|
||||
<DocNav />
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
export default async function DocsLayout(props: any) {
|
||||
return props.children;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Prose } from "gcmp-design-system/src/app/components/molecules/Prose";
|
||||
|
||||
export default function Page() {
|
||||
// console.log("plm", plm);
|
||||
// console.log("toc", toc);
|
||||
return (
|
||||
<Prose>
|
||||
<h1>Welcome to the Jazz documentation.</h1>
|
||||
@@ -1,35 +0,0 @@
|
||||
import NextGuide from "./next.mdx";
|
||||
import { TableOfContents } from "@/components/docs/TableOfContents";
|
||||
import { Prose } from "gcmp-design-system/src/app/components/molecules/Prose";
|
||||
import { clsx } from "clsx";
|
||||
|
||||
const navItems = [
|
||||
{
|
||||
name: "Client-side only",
|
||||
href: "/docs/project-setup/react#next-csr",
|
||||
},
|
||||
{
|
||||
name: "SSR use 🧪",
|
||||
href: "/docs/project-setup/react#next-ssr",
|
||||
},
|
||||
{
|
||||
name: "SSR + client-side 🧪",
|
||||
href: "/docs/project-setup/react#next-ssr-plus-csr",
|
||||
},
|
||||
];
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"col-span-12 md:col-span-8 lg:col-span-9",
|
||||
"flex justify-center lg:gap-5",
|
||||
)}
|
||||
>
|
||||
<Prose className="overflow-x-hidden lg:flex-1">
|
||||
<NextGuide />
|
||||
</Prose>
|
||||
<TableOfContents className="w-48 shrink-0" items={navItems} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
import ReactNativeGuide from "./react-native.mdx";
|
||||
import { TableOfContents } from "@/components/docs/TableOfContents";
|
||||
import { Prose } from "gcmp-design-system/src/app/components/molecules/Prose";
|
||||
import { clsx } from "clsx";
|
||||
|
||||
const navItems = [
|
||||
{
|
||||
name: "Setup",
|
||||
href: "/docs/project-setup/react#react-native-setup",
|
||||
},
|
||||
{
|
||||
name: "Using Jazz",
|
||||
href: "/docs/project-setup/react#react-native-using-jazz",
|
||||
},
|
||||
];
|
||||
export default function Page() {
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"col-span-12 md:col-span-8 lg:col-span-9",
|
||||
"flex justify-center lg:gap-5",
|
||||
)}
|
||||
>
|
||||
<Prose className="overflow-x-hidden lg:flex-1">
|
||||
<ReactNativeGuide />
|
||||
</Prose>
|
||||
<TableOfContents className="w-48 shrink-0" items={navItems} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import ReactGuide from "./react.mdx";
|
||||
import { Prose } from "gcmp-design-system/src/app/components/molecules/Prose";
|
||||
import { clsx } from "clsx";
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"col-span-12 md:col-span-8 lg:col-span-9",
|
||||
"flex justify-center lg:gap-5",
|
||||
)}
|
||||
>
|
||||
<Prose className="overflow-x-hidden lg:flex-1">
|
||||
<ReactGuide />
|
||||
</Prose>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
import ServerGuide from "./server-side.mdx";
|
||||
import { TableOfContents } from "@/components/docs/TableOfContents";
|
||||
import { Prose } from "gcmp-design-system/src/app/components/molecules/Prose";
|
||||
import { clsx } from "clsx";
|
||||
|
||||
const navItems = [
|
||||
{
|
||||
name: "Generating Credentials",
|
||||
href: "/docs/project-setup/server-side#generating-credentials",
|
||||
},
|
||||
{
|
||||
name: "Storing and Providing Credentials",
|
||||
href: "/docs/project-setup/server-side#storing-credentials",
|
||||
},
|
||||
{
|
||||
name: "Starting a Server Worker",
|
||||
href: "/docs/project-setup/server-side#starting",
|
||||
},
|
||||
{
|
||||
name: "Using CoValues instead of Requests",
|
||||
href: "/docs/project-setup/server-side#covalues-instead-of-requests",
|
||||
},
|
||||
];
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"col-span-12 md:col-span-8 lg:col-span-9",
|
||||
"flex justify-center lg:gap-5",
|
||||
)}
|
||||
>
|
||||
<Prose className="overflow-x-hidden lg:flex-1">
|
||||
<ServerGuide />
|
||||
</Prose>
|
||||
<TableOfContents className="w-48 shrink-0" items={navItems} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
import CoValuesGuide from "./covalues.mdx";
|
||||
import { TableOfContents } from "@/components/docs/TableOfContents";
|
||||
import { Prose } from "gcmp-design-system/src/app/components/molecules/Prose";
|
||||
import { clsx } from "clsx";
|
||||
|
||||
const navItems = [
|
||||
{
|
||||
name: "CoValues",
|
||||
href: "/docs/schemas/covalues",
|
||||
},
|
||||
{
|
||||
name: "Schemas as Your App's First Step",
|
||||
href: "/docs/schemas/covalues#schemas-as-first-step",
|
||||
},
|
||||
{
|
||||
name: "CoValue field types",
|
||||
href: "/docs/schemas/covalues#field-types",
|
||||
items: [
|
||||
{
|
||||
name: "Primitive Fields",
|
||||
href: "/docs/schemas/covalues#primitive-fields",
|
||||
},
|
||||
{
|
||||
name: "Refs",
|
||||
href: "/docs/schemas/covalues#refs",
|
||||
},
|
||||
{
|
||||
name: "Computed Fields, Methods & Constructors ",
|
||||
href: "/docs/schemas/covalues#custom-fields",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "CoMaps",
|
||||
href: "/docs/schemas/covalues#comaps",
|
||||
items: [
|
||||
{
|
||||
name: "Struct-like CoMaps",
|
||||
href: "/docs/schemas/covalues#comaps-struct-like",
|
||||
},
|
||||
{
|
||||
name: "Dict/Record-like CoMaps",
|
||||
href: "/docs/schemas/covalues#comaps-dict-like",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "CoLists",
|
||||
href: "/docs/schemas/covalues#colists",
|
||||
},
|
||||
{
|
||||
name: "CoStreams",
|
||||
href: "/docs/schemas/covalues#costreams",
|
||||
},
|
||||
{
|
||||
name: "BinaryCoStreams",
|
||||
href: "/docs/schemas/covalues#binarycostreams",
|
||||
},
|
||||
];
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"col-span-12 md:col-span-8 lg:col-span-9",
|
||||
"flex justify-center lg:gap-5",
|
||||
)}
|
||||
>
|
||||
<Prose className="overflow-x-hidden lg:flex-1">
|
||||
<CoValuesGuide />
|
||||
</Prose>
|
||||
<TableOfContents className="w-48 shrink-0" items={navItems} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
import SyncAndStorage from "./sync-and-storage.mdx";
|
||||
import { TableOfContents } from "@/components/docs/TableOfContents";
|
||||
import { Prose } from "gcmp-design-system/src/app/components/molecules/Prose";
|
||||
import { clsx } from "clsx";
|
||||
|
||||
const navItems = [
|
||||
{
|
||||
name: "Using Jazz Cloud",
|
||||
href: "/docs/sync-and-storage#using-jazz-cloud",
|
||||
items: [
|
||||
{
|
||||
name: "Free Public Alpha",
|
||||
href: "/docs/sync-and-storage#free-public-alpha",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Running your own sync server",
|
||||
href: "/docs/sync-and-storage#running-your-own",
|
||||
items: [
|
||||
{
|
||||
name: "Command line options",
|
||||
href: "/docs/sync-and-storage#command-line-options",
|
||||
},
|
||||
{
|
||||
name: "Source code",
|
||||
href: "/docs/sync-and-storage#source-code",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"col-span-12 md:col-span-8 lg:col-span-9",
|
||||
"flex justify-center lg:gap-5",
|
||||
)}
|
||||
>
|
||||
<Prose className="overflow-x-hidden lg:flex-1">
|
||||
<SyncAndStorage />
|
||||
</Prose>
|
||||
<TableOfContents className="w-48 shrink-0" items={navItems} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
16
homepage/homepage/components/TOC.tsx
Normal file
16
homepage/homepage/components/TOC.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import { TOCItem } from "../lib/extractMDXSourceAndTOC";
|
||||
|
||||
export const TOC = ({ toc }: { toc: TOCItem[] }) => (
|
||||
<nav>
|
||||
<ul>
|
||||
{toc.map((heading, index) => (
|
||||
<li
|
||||
key={index}
|
||||
style={{ marginLeft: `${heading.depth - 1}em` }}
|
||||
>
|
||||
{heading.text}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</nav>
|
||||
);
|
||||
@@ -1,45 +0,0 @@
|
||||
import Link from "next/link";
|
||||
import { clsx } from "clsx";
|
||||
|
||||
interface NavItem {
|
||||
name: string;
|
||||
href: string;
|
||||
items?: NavItem[];
|
||||
}
|
||||
|
||||
export function TableOfContents({
|
||||
className,
|
||||
items,
|
||||
}: {
|
||||
items: NavItem[];
|
||||
className?: string;
|
||||
}) {
|
||||
if (!items.length) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"pl-3 sticky align-start top-[4.75rem] h-[calc(100vh-8rem)] overflow-y-auto overflow-x-hidden hidden md:block",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<p className="mb-3">On this page:</p>
|
||||
<ul className="space-y-2 text-sm list-disc pl-4">
|
||||
{items.map(({ name, href, items }) => (
|
||||
<li key={name} className="space-y-2">
|
||||
<Link href={href}>{name}</Link>
|
||||
{items && items?.length > 0 && (
|
||||
<ul className="list-disc pl-4 space-y-2">
|
||||
{items.map(({ name, href }) => (
|
||||
<li key={href}>
|
||||
<Link href={href}>{name}</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
import { CodeGroup } from "@/components/forMdx";
|
||||
|
||||
# <span id="react-native">React Native</span>
|
||||
|
||||
146
homepage/homepage/lib/extractMDXSourceAndTOC.ts
Normal file
146
homepage/homepage/lib/extractMDXSourceAndTOC.ts
Normal file
@@ -0,0 +1,146 @@
|
||||
import fs from "fs/promises";
|
||||
import matter from "gray-matter";
|
||||
import rehypeShiki from "@shikijs/rehype";
|
||||
import { unified } from "unified";
|
||||
import remarkParse from "remark-parse";
|
||||
import remarkStringify from "remark-stringify";
|
||||
import rehypeRaw from "rehype-raw";
|
||||
import remarkRehype from "remark-rehype";
|
||||
import rehypeStringify from "rehype-stringify";
|
||||
import * as shiki from "shiki";
|
||||
|
||||
import {
|
||||
remarkExtractTOC,
|
||||
remarkHighlightPlugin,
|
||||
remarkHtmlToJsx,
|
||||
} from "./mdx-plugins.mjs";
|
||||
|
||||
export interface TOCItem {
|
||||
depth: number;
|
||||
text: string;
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface MDXContents {
|
||||
source: string;
|
||||
toc: TOCItem[];
|
||||
}
|
||||
|
||||
import rehypeHighlight from "rehype-highlight";
|
||||
|
||||
/**
|
||||
* Extracts metadata and TOC from an MDX file.
|
||||
*
|
||||
* @param mdxPath - The absolute path to the MDX file.
|
||||
* @returns An object containing title, description, and toc.
|
||||
*/
|
||||
export async function extractMDXSourceAndTOC(
|
||||
mdxPath: string,
|
||||
): Promise<MDXContents> {
|
||||
const source = await fs.readFile(mdxPath, "utf8");
|
||||
|
||||
// Extract frontmatter and content using gray-matter
|
||||
// const { data, content } = matter(source);
|
||||
|
||||
// Initialize the processor with remark plugins and a compiler
|
||||
const processor = unified()
|
||||
.use(remarkParse)
|
||||
.use(remarkExtractTOC)
|
||||
// .use(remarkRehype, { allowDangerousHtml: true })
|
||||
// .use(rehypeHighlight)
|
||||
// .use(rehypeShiki, {
|
||||
// theme: "light-plus",
|
||||
// })
|
||||
.use(remarkHighlightPlugin)
|
||||
// .use(remarkHtmlToJsx)
|
||||
// .use(rehypeRaw)
|
||||
.use(remarkStringify); // Adds compiler to satisfy `process` requirements
|
||||
// .use(rehypeStringify, { allowDangerousHtml: true });
|
||||
|
||||
// Process the content and extract TOC
|
||||
const file = await processor.process(source);
|
||||
|
||||
// Extract TOC from the file's data and ensure type safety
|
||||
const toc: TOCItem[] = (file.data.toc as TOCItem[]) || [];
|
||||
|
||||
// console.log("source", source);
|
||||
|
||||
return {
|
||||
toc,
|
||||
source,
|
||||
};
|
||||
}
|
||||
|
||||
import { compile } from "@mdx-js/mdx";
|
||||
import { serialize } from "next-mdx-remote/serialize";
|
||||
// import rehypeRaw from "rehype-raw"; // Import rehype-raw
|
||||
|
||||
// export async function extractMDXSourceAndTOC(mdxPath: string) {
|
||||
// const source = await fs.readFile(mdxPath, "utf8");
|
||||
|
||||
// // Extract frontmatter and content using gray-matter
|
||||
// const { data, content } = matter(source);
|
||||
|
||||
// // Compile MDX with @mdx-js/mdx
|
||||
// const compiledMDX = await compile(content, {
|
||||
// remarkPlugins: [
|
||||
// remarkParse,
|
||||
// remarkHighlightPlugin,
|
||||
// remarkExtractTOC,
|
||||
// remarkStringify,
|
||||
// remarkHtmlToJsx,
|
||||
// ],
|
||||
// rehypePlugins: [], // Add any rehype plugins if necessary
|
||||
// jsx: true, // Enables JSX syntax in the MDX
|
||||
// });
|
||||
|
||||
// // Serialize the MDX to a format compatible with MDXRemote
|
||||
// const mdxSource = await serialize(String(compiledMDX));
|
||||
|
||||
// // You might want to extract the TOC from frontmatter if added there
|
||||
// const toc = data.toc || []; // Assume `remarkExtractTOC` adds `toc` to frontmatter
|
||||
|
||||
// return {
|
||||
// toc,
|
||||
// source: mdxSource,
|
||||
// };
|
||||
// }
|
||||
|
||||
////
|
||||
|
||||
// export async function extractMDXSourceAndTOC(mdxPath) {
|
||||
// // Read the MDX file
|
||||
// const source = await fs.readFile(mdxPath, "utf8");
|
||||
|
||||
// // Extract frontmatter and content using gray-matter
|
||||
// const { data, content } = matter(source);
|
||||
|
||||
// // Serialize the MDX content to a format compatible with MDXRemote
|
||||
// const mdxSource = await serialize(content, {
|
||||
// mdxOptions: {
|
||||
// // jsx: true,
|
||||
// // format: "mdx",
|
||||
// remarkPlugins: [
|
||||
// // remarkParse,
|
||||
// // remarkHighlightPlugin,
|
||||
// // remarkExtractTOC,
|
||||
// // remarkStringify,
|
||||
// ], // Add your remark plugins here
|
||||
// rehypePlugins: [], // Add your rehype plugins here
|
||||
|
||||
// // You can add more options as needed
|
||||
// },
|
||||
// // Optionally, pass the frontmatter data to MDX
|
||||
// // scope: data,
|
||||
// });
|
||||
|
||||
// console.log("mdxSource", mdxSource.compiledSource);
|
||||
|
||||
// // Extract TOC from frontmatter or from the plugin's result
|
||||
// const toc = data.toc || []; // Adjust based on how `remarkExtractToc` provides TOC
|
||||
|
||||
// return {
|
||||
// toc,
|
||||
// source: mdxSource,
|
||||
// };
|
||||
// }
|
||||
143
homepage/homepage/lib/mdx-plugins.mjs
Normal file
143
homepage/homepage/lib/mdx-plugins.mjs
Normal file
@@ -0,0 +1,143 @@
|
||||
import { getHighlighter } from "shiki";
|
||||
import { visit, SKIP } from "unist-util-visit";
|
||||
|
||||
export function remarkHighlightPlugin() {
|
||||
return async function transformer(tree) {
|
||||
const highlighter = await getHighlighter({
|
||||
langs: ["typescript", "bash", "tsx", "json"],
|
||||
theme: "css-variables", // use the theme
|
||||
});
|
||||
|
||||
visit(tree, "code", visitor);
|
||||
|
||||
// console.log("highlighter", highlighter);
|
||||
|
||||
function visitor(node) {
|
||||
// console.log("nodex", node);
|
||||
const lines = highlighter.codeToThemedTokens(
|
||||
node.value,
|
||||
node.lang, // "json",
|
||||
"css-variables",
|
||||
);
|
||||
|
||||
console.log("linesx", lines);
|
||||
|
||||
let lineNo = -1;
|
||||
|
||||
node.type = "html";
|
||||
node.value = `<code class="not-prose py-2 flex flex-col leading-relaxed">${lines
|
||||
.map((line) => {
|
||||
let lineClassName = "";
|
||||
|
||||
const isSubduedLine = line.some((token) =>
|
||||
token.content.includes("// old"),
|
||||
);
|
||||
const isBinnedLine = line.some((token) =>
|
||||
token.content.includes("// *bin*"),
|
||||
);
|
||||
const isHighlighted = line.some((token) =>
|
||||
token.content.includes("// *highlight*"),
|
||||
);
|
||||
if (!isBinnedLine) {
|
||||
lineNo++;
|
||||
}
|
||||
|
||||
if (isBinnedLine) {
|
||||
lineClassName = "bg-red-100 dark:bg-red-800";
|
||||
} else if (isHighlighted) {
|
||||
lineClassName =
|
||||
"my-0.5 bg-blue-50 text-blue dark:bg-stone-900 dark:text-blue-300";
|
||||
}
|
||||
|
||||
return (
|
||||
`<span class="block px-3 min-h-[1em] ${lineClassName}" style="${isBinnedLine ? "user-select: none" : ""}">` +
|
||||
line
|
||||
.map((token) => {
|
||||
let color = isHighlighted
|
||||
? "currentColor"
|
||||
: token.color;
|
||||
return `<span style="color: ${color};${isSubduedLine ? "opacity: 0.4;" : ""}">${escape(token.content.replace("// old", "").replace("// *bin*", "").replace("// *highlight*", ""))}</span>`;
|
||||
})
|
||||
.join("") +
|
||||
"</span>"
|
||||
);
|
||||
})
|
||||
.join("\n")}</code>`;
|
||||
|
||||
console.log("node.value", node.value);
|
||||
|
||||
node.children = [];
|
||||
return SKIP;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function remarkHtmlToJsx() {
|
||||
async function transform(...args) {
|
||||
// Async import since these packages are all in ESM
|
||||
const { visit, SKIP } = await import("unist-util-visit");
|
||||
const { mdxFromMarkdown } = await import("mdast-util-mdx");
|
||||
const { fromMarkdown } = await import("mdast-util-from-markdown");
|
||||
const { mdxjs } = await import("micromark-extension-mdxjs");
|
||||
|
||||
// This is a horror show, but it's the only way I could get the raw HTML into MDX.
|
||||
const [ast] = args;
|
||||
visit(ast, "html", (node) => {
|
||||
const escapedHtml = JSON.stringify(node.value);
|
||||
console.log("escapedHtml", escapedHtml);
|
||||
const jsx = `<div dangerouslySetInnerHTML={{__html: ${escapedHtml} }}/>`;
|
||||
const rawHtmlNode = fromMarkdown(jsx, {
|
||||
extensions: [mdxjs()],
|
||||
mdastExtensions: [mdxFromMarkdown()],
|
||||
}).children[0];
|
||||
|
||||
Object.assign(node, rawHtmlNode);
|
||||
|
||||
return SKIP;
|
||||
});
|
||||
}
|
||||
|
||||
return transform;
|
||||
}
|
||||
|
||||
export function remarkExtractTOC() {
|
||||
return (tree, file) => {
|
||||
const toc = [];
|
||||
const idCounts = {};
|
||||
|
||||
visit(tree, "heading", (node) => {
|
||||
const text = node.children
|
||||
.filter(
|
||||
(child) =>
|
||||
child.type === "text" || child.type === "inlineCode",
|
||||
)
|
||||
.map((child) => child.value)
|
||||
.join("");
|
||||
|
||||
let id = text
|
||||
.toLowerCase()
|
||||
.replace(/[^\w]+/g, "-")
|
||||
.replace(/^-+|-+$/g, "");
|
||||
|
||||
// Ensure unique ID
|
||||
if (idCounts[id]) {
|
||||
idCounts[id] += 1;
|
||||
id = `${id}-${idCounts[id]}`;
|
||||
} else {
|
||||
idCounts[id] = 1;
|
||||
}
|
||||
|
||||
toc.push({
|
||||
depth: node.depth,
|
||||
text,
|
||||
id,
|
||||
});
|
||||
});
|
||||
|
||||
file.data.toc = toc;
|
||||
};
|
||||
}
|
||||
|
||||
function escape(s) {
|
||||
return s.replace(/[^0-9A-Za-z ]/g, (c) => "&#" + c.charCodeAt(0) + ";");
|
||||
}
|
||||
@@ -1,23 +1,22 @@
|
||||
import createMDX from "@next/mdx";
|
||||
import { visit, SKIP } from "unist-util-visit";
|
||||
import { getHighlighter } from "shiki";
|
||||
import withSlugs from "rehype-slug";
|
||||
import { remarkHighlightPlugin, remarkHtmlToJsx } from "./lib/mdx-plugins.mjs";
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
// Configure `pageExtensions`` to include MDX files
|
||||
pageExtensions: ["js", "jsx", "mdx", "ts", "tsx"],
|
||||
transpilePackages: ["gcmp-design-system"],
|
||||
// Optionally, add any other Next.js config below
|
||||
experimental: {
|
||||
serverActions: true,
|
||||
},
|
||||
};
|
||||
|
||||
const withMDX = createMDX({
|
||||
// Add markdown plugins here, as desired
|
||||
options: {
|
||||
remarkPlugins: [highlightPlugin, remarkHtmlToJsx],
|
||||
rehypePlugins: [],
|
||||
remarkPlugins: [remarkHighlightPlugin, remarkHtmlToJsx],
|
||||
rehypePlugins: [withSlugs],
|
||||
},
|
||||
});
|
||||
|
||||
@@ -26,97 +25,4 @@ const config = {
|
||||
output: "standalone",
|
||||
};
|
||||
|
||||
function highlightPlugin() {
|
||||
return async function transformer(tree) {
|
||||
const highlighter = await getHighlighter({
|
||||
langs: ["typescript", "bash", "tsx", "json"],
|
||||
theme: "css-variables", // use the theme
|
||||
});
|
||||
|
||||
visit(tree, "code", visitor);
|
||||
|
||||
function visitor(node) {
|
||||
const lines = highlighter.codeToThemedTokens(
|
||||
node.value,
|
||||
node.lang,
|
||||
"css-variables",
|
||||
);
|
||||
|
||||
let lineNo = -1;
|
||||
|
||||
node.type = "html";
|
||||
node.value = `<code class="not-prose py-2 flex flex-col leading-relaxed">${lines
|
||||
.map((line) => {
|
||||
let lineClassName = "";
|
||||
|
||||
const isSubduedLine = line.some((token) =>
|
||||
token.content.includes("// old"),
|
||||
);
|
||||
const isBinnedLine = line.some((token) =>
|
||||
token.content.includes("// *bin*"),
|
||||
);
|
||||
const isHighlighted = line.some((token) =>
|
||||
token.content.includes("// *highlight*"),
|
||||
);
|
||||
if (!isBinnedLine) {
|
||||
lineNo++;
|
||||
}
|
||||
|
||||
if (isBinnedLine) {
|
||||
lineClassName = 'bg-red-100 dark:bg-red-800'
|
||||
} else if (isHighlighted) {
|
||||
lineClassName = 'my-0.5 bg-blue-50 text-blue dark:bg-stone-900 dark:text-blue-300'
|
||||
}
|
||||
|
||||
return (
|
||||
`<span class="block px-3 min-h-[1em] ${lineClassName}" style="${isBinnedLine ? "user-select: none" : ""}">` +
|
||||
line
|
||||
.map(
|
||||
(token) => {
|
||||
let color = isHighlighted ? 'currentColor' : token.color
|
||||
return `<span style="color: ${color};${isSubduedLine ? "opacity: 0.4;" : ""}">${escape(token.content.replace("// old", "").replace("// *bin*", "").replace("// *highlight*", ""))}</span>`
|
||||
}
|
||||
)
|
||||
.join("") +
|
||||
"</span>"
|
||||
);
|
||||
})
|
||||
.join("\n")}</code>`;
|
||||
node.children = [];
|
||||
return SKIP;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function escape(s) {
|
||||
return s.replace(/[^0-9A-Za-z ]/g, (c) => "&#" + c.charCodeAt(0) + ";");
|
||||
}
|
||||
|
||||
function remarkHtmlToJsx() {
|
||||
async function transform(...args) {
|
||||
// Async import since these packages are all in ESM
|
||||
const { visit, SKIP } = await import("unist-util-visit");
|
||||
const { mdxFromMarkdown } = await import("mdast-util-mdx");
|
||||
const { fromMarkdown } = await import("mdast-util-from-markdown");
|
||||
const { mdxjs } = await import("micromark-extension-mdxjs");
|
||||
|
||||
// This is a horror show, but it's the only way I could get the raw HTML into MDX.
|
||||
const [ast] = args;
|
||||
visit(ast, "html", (node) => {
|
||||
const escapedHtml = JSON.stringify(node.value);
|
||||
const jsx = `<div dangerouslySetInnerHTML={{__html: ${escapedHtml} }}/>`;
|
||||
const rawHtmlNode = fromMarkdown(jsx, {
|
||||
extensions: [mdxjs()],
|
||||
mdastExtensions: [mdxFromMarkdown()],
|
||||
}).children[0];
|
||||
|
||||
Object.assign(node, rawHtmlNode);
|
||||
|
||||
return SKIP;
|
||||
});
|
||||
}
|
||||
|
||||
return transform;
|
||||
}
|
||||
|
||||
export default config;
|
||||
|
||||
@@ -17,28 +17,41 @@
|
||||
"packageManager": "pnpm@9.1.4",
|
||||
"dependencies": {
|
||||
"@icons-pack/react-simple-icons": "^9.1.0",
|
||||
"@jsdevtools/rehype-toc": "^3.0.2",
|
||||
"@mdx-js/loader": "^2.3.0",
|
||||
"@mdx-js/mdx": "^3.1.0",
|
||||
"@mdx-js/react": "^2.3.0",
|
||||
"@next/mdx": "^13.5.4",
|
||||
"@stefanprobst/rehype-extract-toc": "^2.2.0",
|
||||
"@types/mdx": "^2.0.8",
|
||||
"@vercel/analytics": "^1.3.1",
|
||||
"@vercel/speed-insights": "^1.0.12",
|
||||
"clsx": "^2.1.1",
|
||||
"gcmp-design-system": "workspace:*",
|
||||
"gray-matter": "^4.0.3",
|
||||
"lucide-react": "^0.436.0",
|
||||
"mdast-util-from-markdown": "^2.0.0",
|
||||
"mdast-util-mdx": "^3.0.0",
|
||||
"micromark-extension-mdxjs": "^3.0.0",
|
||||
"next": "13.5.4",
|
||||
"next-mdx-remote": "^5.0.0",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"rehype-highlight": "^7.0.1",
|
||||
"rehype-raw": "^7.0.0",
|
||||
"rehype-slug": "^6.0.0",
|
||||
"remark-parse": "^11.0.0",
|
||||
"remark-rehype": "^11.1.1",
|
||||
"remark-stringify": "^11.0.0",
|
||||
"shiki": "^0.14.6",
|
||||
"shiki-twoslash": "^3.1.2",
|
||||
"tailwind-merge": "^1.14.0",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"unified": "^11.0.5",
|
||||
"unist-util-visit": "^5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@shikijs/rehype": "^1.22.2",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
@@ -47,6 +60,7 @@
|
||||
"eslint-config-next": "13.5.4",
|
||||
"postcss": "^8",
|
||||
"prettier": "^3.2.5",
|
||||
"rehype-stringify": "^10.0.1",
|
||||
"tailwindcss": "^3",
|
||||
"typedoc": "^0.25.13",
|
||||
"typescript": "^5.3.3"
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
@@ -19,9 +23,19 @@
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./*"]
|
||||
"@/*": [
|
||||
"./*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts",
|
||||
"lib/remark-extract-toc.mjs"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
813
homepage/pnpm-lock.yaml
generated
813
homepage/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -5,26 +5,36 @@ import { createWorkerAccount } from "../createWorkerAccount.js";
|
||||
describe("createWorkerAccount - integration tests", () => {
|
||||
it("should create a worker account using the local sync server", async () => {
|
||||
// Pass port: undefined to let the server choose a random port
|
||||
const server = await startSyncServer({ port: undefined, inMemory: true, db: '' });
|
||||
const server = await startSyncServer({
|
||||
port: undefined,
|
||||
inMemory: true,
|
||||
db: "",
|
||||
});
|
||||
|
||||
onTestFinished(() => {
|
||||
server.close()
|
||||
server.close();
|
||||
});
|
||||
|
||||
const address = server.address();
|
||||
|
||||
if (typeof address !== 'object' || address === null) {
|
||||
throw new Error('Server address is not an object');
|
||||
if (typeof address !== "object" || address === null) {
|
||||
throw new Error("Server address is not an object");
|
||||
}
|
||||
|
||||
const { accountId, agentSecret } = await createWorkerAccount({ name: "test", peer: `ws://localhost:${address.port}` });
|
||||
const { accountId, agentSecret } = await createWorkerAccount({
|
||||
name: "test",
|
||||
peer: `ws://localhost:${address.port}`,
|
||||
});
|
||||
|
||||
expect(accountId).toBeDefined();
|
||||
expect(agentSecret).toBeDefined();
|
||||
});
|
||||
|
||||
it("should create a worker account using the Jazz cloud", async () => {
|
||||
const { accountId, agentSecret } = await createWorkerAccount({ name: "test", peer: `wss://cloud.jazz.tools` });
|
||||
const { accountId, agentSecret } = await createWorkerAccount({
|
||||
name: "test",
|
||||
peer: `wss://cloud.jazz.tools`,
|
||||
});
|
||||
|
||||
expect(accountId).toBeDefined();
|
||||
expect(agentSecret).toBeDefined();
|
||||
|
||||
Reference in New Issue
Block a user