Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions apps/website/app/(docs)/docs/(landing)/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import "~/globals.css";

type DocsLandingLayoutProps = {
children: React.ReactNode;
};

const DocsLandingLayout = ({
children,
}: DocsLandingLayoutProps): React.ReactElement => <>{children}</>;

export default DocsLandingLayout;
95 changes: 95 additions & 0 deletions apps/website/app/(docs)/docs/(landing)/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import type { Metadata } from "next";
import Link from "next/link";
import { ArrowRight } from "lucide-react";
import { Card, CardContent } from "@repo/ui/components/ui/card";
import { PlatformBadge } from "~/components/PlatformBadge";
import { Logo } from "~/components/Logo";

export const metadata: Metadata = {
title: "Documentation",
description:
"Choose the Discourse Graphs documentation for Roam Research or Obsidian.",
};

const DOCS_DESTINATIONS = [
{
description:
"Installation, graph building, querying, and advanced workflows for the Roam Research plugin.",
href: "/docs/roam",
platform: "roam",
title: "Roam docs",
},
{
description:
"Setup, node and relation authoring, sync, and workspace configuration for the Obsidian plugin.",
href: "/docs/obsidian",
platform: "obsidian",
title: "Obsidian docs",
},
] as const;

const DocsLandingPage = (): React.ReactElement => {
return (
<div className="font-[family:var(--font-inter)] min-h-screen bg-neutral-light text-neutral-dark">
<header className="border-b border-black/5 bg-white/80 backdrop-blur">
<div className="mx-auto flex max-w-6xl items-center justify-between gap-6 px-6 py-4">
<Logo />
<Link
href="/"
className="text-sm font-medium text-neutral-dark/70 hover:text-neutral-dark"
>
Back to site
</Link>
</div>
</header>

<main className="px-6 py-16 sm:py-24">
<div className="mx-auto max-w-6xl">
<section className="max-w-3xl">
<p className="text-sm font-semibold uppercase tracking-[0.18em] text-secondary">
Documentation
</p>
<h1 className="mt-4 text-4xl font-semibold tracking-tight text-primary sm:text-5xl">
Choose your docs
</h1>
<p className="mt-6 text-lg leading-8 text-neutral-dark/80">
Discourse Graphs has separate documentation for each client. Pick
the one you are using to get the right setup instructions,
workflows, and reference pages.
</p>
</section>

<section className="mt-12 grid gap-6 md:grid-cols-2">
{DOCS_DESTINATIONS.map(({ description, href, platform, title }) => (
<Link key={href} href={href} className="group block h-full">
<Card className="h-full rounded-2xl border border-black/5 bg-white shadow-sm transition-transform duration-200 group-hover:-translate-y-1 group-hover:shadow-lg">
<CardContent className="flex h-full flex-col gap-8 p-8">
<div className="flex items-start justify-between gap-4">
<PlatformBadge platform={platform} />
<ArrowRight className="h-5 w-5 shrink-0 text-neutral-dark/40 transition-transform duration-200 group-hover:translate-x-1 group-hover:text-secondary" />
</div>

<div className="space-y-4">
<h2 className="text-2xl font-semibold tracking-tight text-primary">
{title}
</h2>
<p className="text-base leading-7 text-neutral-dark/75">
{description}
</p>
</div>

<p className="mt-auto text-sm font-semibold text-secondary">
Open documentation
</p>
</CardContent>
</Card>
</Link>
))}
</section>
</div>
</main>
</div>
);
};

export default DocsLandingPage;
34 changes: 34 additions & 0 deletions apps/website/app/(docs)/docs/_components/DocsPageTemplate.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { EvaluateResult } from "nextra";
import { useMDXComponents } from "mdx-components";

type DocsPageTemplateProps = Omit<EvaluateResult, "default"> & {
children: React.ReactNode;
};

const hasPrimaryHeading = (sourceCode: string): boolean =>
/(^|\n)#\s+\S/m.test(sourceCode);

const DocsPageTemplate = ({
children,
metadata,
sourceCode,
...wrapperProps
}: DocsPageTemplateProps): React.ReactElement => {
const { h1, wrapper } = useMDXComponents();
// eslint-disable-next-line @typescript-eslint/naming-convention
const Wrapper = wrapper as React.ComponentType<DocsPageTemplateProps>;
const H1 = h1 as React.ComponentType<
React.HTMLAttributes<HTMLHeadingElement> & {
children: React.ReactNode;
}
>;

return (
<Wrapper metadata={metadata} sourceCode={sourceCode} {...wrapperProps}>
{!hasPrimaryHeading(sourceCode) && <H1>{metadata.title}</H1>}
{children}
</Wrapper>
);
};

export default DocsPageTemplate;
86 changes: 86 additions & 0 deletions apps/website/app/(docs)/docs/_components/DocsThemeLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import type { PageMapItem } from "nextra";
import { Search } from "nextra/components";
import { Footer, Layout, Navbar } from "nextra-theme-docs";
import { Logo } from "~/components/Logo";

type DocsSearchScope = "roam" | "obsidian";

type DocsThemeLayoutProps = {
children: React.ReactNode;
hideSearch?: boolean;
pageMap: PageMapItem[];
searchScope?: DocsSearchScope;
};

const SEARCH_PLACEHOLDERS: Record<DocsSearchScope, string> = {
obsidian: "Search Obsidian docs...",
roam: "Search Roam docs...",
};

const renderSearch = ({
hideSearch,
searchScope,
}: Pick<DocsThemeLayoutProps, "hideSearch" | "searchScope">):
| React.ReactElement
| null
| undefined => {
if (hideSearch) {
return null;
}

if (!searchScope) {
return undefined;
}

return (
<Search
placeholder={SEARCH_PLACEHOLDERS[searchScope]}
searchOptions={{
filters: {
platform: [searchScope],
},
}}
/>
);
};

const DocsThemeLayout = ({
children,
hideSearch,
pageMap,
searchScope,
}: DocsThemeLayoutProps): React.ReactElement => {
const search = renderSearch({ hideSearch, searchScope });

return (
<div className="nextra-reset">
<Layout
editLink={null}
feedback={{ content: null }}
footer={
<Footer>
Apache 2.0 {new Date().getFullYear()} (c) Discourse Graphs.
</Footer>
}
navbar={
<Navbar
logo={<Logo linked={false} textClassName="text-inherit" />}
projectLink="https://github.com/DiscourseGraphs/discourse-graph"
/>
}
pageMap={pageMap}
search={search}
sidebar={{
defaultMenuCollapseLevel: 1,
}}
toc={{
backToTop: "Back to top",
}}
>
{children}
</Layout>
</div>
);
};

export default DocsThemeLayout;
69 changes: 69 additions & 0 deletions apps/website/app/(docs)/docs/obsidian/[[...mdxPath]]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import type { Metadata } from "next";
import { notFound } from "next/navigation";
import { generateStaticParamsFor, importPage } from "nextra/pages";
import DocsPageTemplate from "../../_components/DocsPageTemplate";

type DocsPageProps = {
params: Promise<{
mdxPath?: string[];
}>;
};

type ImportedPage = Awaited<ReturnType<typeof importPage>>;

const generateAllStaticParams = generateStaticParamsFor("mdxPath");

const loadPage = async (mdxPath?: string[]): Promise<ImportedPage> =>
importPage(["obsidian", ...(mdxPath ?? [])]);

export const generateStaticParams = async (): Promise<
Array<{ mdxPath?: string[] }>
> => {
const staticParams = await generateAllStaticParams();

return staticParams.flatMap(({ mdxPath }) => {
if (!Array.isArray(mdxPath) || mdxPath[0] !== "obsidian") {
return [];
}

const platformPath = mdxPath.slice(1);

return platformPath.length ? [{ mdxPath: platformPath }] : [{}];
});
};

const Page = async ({ params }: DocsPageProps): Promise<React.ReactElement> => {
try {
const { mdxPath } = await params;
const result = await loadPage(mdxPath);
const { default: MDXContent, ...wrapperProps } = result;

return (
<DocsPageTemplate {...wrapperProps}>
<MDXContent params={{ mdxPath: mdxPath ?? [] }} />
</DocsPageTemplate>
);
} catch (error) {
console.error("Error rendering Obsidian docs page:", error);
notFound();
}
};

export const generateMetadata = async ({
params,
}: DocsPageProps): Promise<Metadata> => {
try {
const { mdxPath } = await params;
const { metadata } = await loadPage(mdxPath);

return metadata;
} catch (error) {
console.error("Error generating Obsidian docs metadata:", error);

return {
title: "Obsidian docs",
};
}
};

export default Page;
32 changes: 0 additions & 32 deletions apps/website/app/(docs)/docs/obsidian/[slug]/page.tsx

This file was deleted.

28 changes: 20 additions & 8 deletions apps/website/app/(docs)/docs/obsidian/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
import { navigation } from "./navigation";
import { Layout } from "~/components/DocsLayout";
import { getPageMap } from "nextra/page-map";
import DocsThemeLayout from "../_components/DocsThemeLayout";
import "../../../(nextra)/nextra-css.css";
import "nextra-theme-docs/style-prefixed.css";

export default function RootLayout({
children,
}: {
type ObsidianDocsLayoutProps = {
children: React.ReactNode;
}) {
return <Layout navigationList={navigation}>{children}</Layout>;
}
};

const ObsidianDocsLayout = async ({
children,
}: ObsidianDocsLayoutProps): Promise<React.ReactElement> => {
const pageMap = await getPageMap("/docs/obsidian");

return (
<DocsThemeLayout pageMap={pageMap} searchScope="obsidian">
{children}
</DocsThemeLayout>
);
};

export default ObsidianDocsLayout;
6 changes: 0 additions & 6 deletions apps/website/app/(docs)/docs/obsidian/page.tsx

This file was deleted.

Loading