Skip to content
Merged
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
3 changes: 3 additions & 0 deletions .env.example
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
NEXT_PUBLIC_SITE_URL=https://dripnex.app
NEXT_PUBLIC_DOCS_URL=https://docs.dripnex.app
NEXT_PUBLIC_DEVELOPERS_URL=https://developers.dripnex.app
24 changes: 24 additions & 0 deletions .github/workflows/ci.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
name: CI

on:
push:
branches: [main]
pull_request:

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: pnpm/action-setup@v5
- uses: actions/setup-node@v5
with:
node-version: '22'
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: pnpm typecheck
- run: pnpm build
env:
NEXT_PUBLIC_SITE_URL: https://dripnex.app
NEXT_PUBLIC_DOCS_URL: https://docs.dripnex.app
NEXT_PUBLIC_DEVELOPERS_URL: https://dripnex-developers.pages.dev
9 changes: 9 additions & 0 deletions .gitignore
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
.next/
.source/
node_modules/
out/
.vercel
.env
.env.local
*.tsbuildinfo
.wrangler/
31 changes: 29 additions & 2 deletions README.md
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,29 @@
# developers
Dripnex developer docs — plugin and theme API (Fumadocs). Mirrors developers.inkdrop.app.
# Dripnex developers

Plugin and theme API docs for Dripnex. Fumadocs + Next.js static export on Cloudflare Pages.

Modeled on [developers.inkdrop.app](https://developers.inkdrop.app/). The user manual lives in [`dripnex/docs-site`](https://github.com/dripnex/docs-site) ([docs.dripnex.app](https://docs.dripnex.app)). Marketing lives in [`dripnex/marketing`](https://github.com/dripnex/marketing).

The API described here is `@dripnex/plugin-api` in [`dripnex/app`](https://github.com/dripnex/app) (`packages/plugin-api`, default branch `develop`). `PLUGIN_API_VERSION` is `"1"`.

## Develop

```bash
pnpm install
pnpm dev
```

Content is MDX under `content/docs/`. The site is served at `/`.

## Deploy

```bash
NEXT_PUBLIC_SITE_URL=https://dripnex.app \
NEXT_PUBLIC_DOCS_URL=https://docs.dripnex.app \
NEXT_PUBLIC_DEVELOPERS_URL=https://dripnex-developers.pages.dev \
pnpm deploy
```

Pages project: `dripnex-developers` → https://dripnex-developers.pages.dev

When DNS is ready, CNAME `developers` → `dripnex-developers.pages.dev` (proxied).
61 changes: 61 additions & 0 deletions app/[[...slug]]/page.tsx
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
import { notFound } from 'next/navigation';
import { DocsPage, DocsBody, DocsTitle, DocsDescription } from 'fumadocs-ui/page';
import defaultMdxComponents from 'fumadocs-ui/mdx';
import { Card, Cards } from 'fumadocs-ui/components/card';
import { Callout } from 'fumadocs-ui/components/callout';
import { Step, Steps } from 'fumadocs-ui/components/steps';
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
import { Accordion, Accordions } from 'fumadocs-ui/components/accordion';
import { File, Folder, Files } from 'fumadocs-ui/components/files';
import { TypeTable } from 'fumadocs-ui/components/type-table';
import { source } from '@/lib/source';

const mdxComponents = {
...defaultMdxComponents,
Card,
Cards,
Callout,
Step,
Steps,
Tab,
Tabs,
Accordion,
Accordions,
File,
Folder,
Files,
TypeTable,
};

export default async function Page(props: { params: Promise<{ slug?: string[] }> }) {
const params = await props.params;
const page = source.getPage(params.slug);
if (!page) notFound();

const MDX = page.data.body;

return (
<DocsPage toc={page.data.toc} full={page.data.full}>
<DocsTitle>{page.data.title}</DocsTitle>
<DocsDescription>{page.data.description}</DocsDescription>
<DocsBody>
<MDX components={mdxComponents} />
</DocsBody>
</DocsPage>
);
}

export function generateStaticParams() {
return source.generateParams();
}

export async function generateMetadata(props: { params: Promise<{ slug?: string[] }> }) {
const params = await props.params;
const page = source.getPage(params.slug);
if (!page) notFound();

return {
title: page.data.title,
description: page.data.description,
};
}
135 changes: 135 additions & 0 deletions app/globals.css
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
@import 'tailwindcss';
@import 'fumadocs-ui/css/neutral.css';
@import 'fumadocs-ui/css/preset.css';

@theme {
--color-background: #09090b;
--color-surface: #111113;
--color-surface-elevated: #1a1a1f;
--color-inset: #0c0c0e;
--color-border: #27272a;
--color-border-accent: rgba(255, 255, 255, 0.12);

--color-text-primary: #fafafa;
--color-text-secondary: #a1a1aa;
--color-text-muted: #52525b;

--color-accent: #a1a1aa;
--color-accent-hover: #d4d4d8;
--color-accent-glow: rgba(255, 255, 255, 0.06);

--font-sans: 'Inter', ui-sans-serif, system-ui, sans-serif;
--font-mono: 'JetBrains Mono Variable', ui-monospace, monospace;
}

:root,
.dark {
--rd-violet: #8b5cf6;
--rd-violet-light: #a78bfa;
--rd-violet-lighter: #c4b5fd;
--rd-violet-glow-10: rgba(139, 92, 246, 0.1);
--rd-violet-glow-12: rgba(139, 92, 246, 0.12);
--rd-violet-glow-20: rgba(139, 92, 246, 0.2);
--rd-violet-glow-30: rgba(139, 92, 246, 0.3);
--rd-violet-glow-08: rgba(139, 92, 246, 0.08);
--rd-surface: #111113;
--rd-inset: #0c0c0e;
--rd-foreground: #fafafa;
--rd-muted-foreground: #a1a1aa;
--rd-subtle-foreground: #e4e4e7;
--rd-faint: #52525b;
--rd-border-subtle: rgba(255, 255, 255, 0.06);
--rd-border: rgba(255, 255, 255, 0.08);

--color-fd-background: #09090b;
--color-fd-foreground: var(--rd-foreground);
--color-fd-muted: var(--rd-surface);
--color-fd-muted-foreground: var(--rd-muted-foreground);
--color-fd-popover: var(--rd-surface);
--color-fd-popover-foreground: var(--rd-subtle-foreground);
--color-fd-card: var(--rd-surface);
--color-fd-card-foreground: var(--rd-foreground);
--color-fd-border: var(--rd-border);
--color-fd-primary: var(--rd-violet);
--color-fd-primary-foreground: #ffffff;
--color-fd-secondary: #1a1a1f;
--color-fd-secondary-foreground: var(--rd-subtle-foreground);
--color-fd-accent: var(--rd-violet-glow-12);
--color-fd-accent-foreground: var(--rd-subtle-foreground);
--color-fd-ring: var(--rd-violet);
}

.fd-sidebar {
--fd-sidebar-width: 260px;
}

.fd-sidebar [data-active='true'] {
color: var(--rd-violet) !important;
}

nav[data-fumadocs] {
border-bottom: 1px solid var(--rd-border-subtle);
}

pre:has(code) {
background: var(--rd-inset) !important;
border: 1px solid var(--rd-border-subtle);
border-radius: 0.75rem;
}

:not(pre) > code {
background: var(--rd-violet-glow-10) !important;
color: var(--rd-violet-lighter) !important;
border: 1px solid var(--rd-violet-glow-20);
border-radius: 0.375rem;
padding: 0.125rem 0.375rem;
font-size: 0.875em;
}

[data-toc] a[data-active='true'] {
color: var(--rd-violet);
border-left-color: var(--rd-violet);
}

.fd-card {
background: var(--rd-surface);
border-color: var(--rd-border);
transition:
border-color 0.2s,
box-shadow 0.2s;
}

.fd-card:hover {
border-color: var(--rd-violet-glow-30);
box-shadow: 0 0 30px var(--rd-violet-glow-08);
}

[data-fumadocs-search] {
--color-fd-background: var(--rd-inset);
--color-fd-popover: var(--rd-surface);
}

nav[aria-label='Breadcrumb'] {
color: var(--rd-faint);
}

nav[aria-label='Breadcrumb'] a:hover {
color: var(--rd-violet);
}

.fd-page h1,
.fd-page h2,
.fd-page h3,
.fd-page h4 {
color: var(--rd-foreground);
}

.fd-page a:not([class]) {
color: var(--rd-violet-light);
text-decoration-color: var(--rd-violet-glow-30);
}

.fd-page a:not([class]):hover {
color: var(--rd-violet-lighter);
text-decoration-color: var(--rd-violet);
}
76 changes: 76 additions & 0 deletions app/layout.tsx
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
import './globals.css';
import '@fontsource/inter/400.css';
import '@fontsource/inter/500.css';
import '@fontsource/inter/600.css';
import '@fontsource/inter/700.css';
import '@fontsource-variable/jetbrains-mono';
import { RootProvider } from 'fumadocs-ui/provider/next';
import { DocsLayout } from 'fumadocs-ui/layouts/docs';
import type { ReactNode } from 'react';
import type { Metadata } from 'next';
import { source } from '@/lib/source';
import { baseOptions } from '@/lib/layout.shared';
import { DEVELOPERS_URL, DOCS_URL } from '@/lib/site';

export const metadata: Metadata = {
metadataBase: new URL(DEVELOPERS_URL),
title: {
default: 'Dripnex Developers',
template: '%s | Dripnex Developers',
},
description:
'Plugin and theme API for Dripnex — extend the desktop editor with commands, CodeMirror, layout zones, and palettes.',
applicationName: 'Dripnex Developers',
icons: {
icon: [
{ url: '/favicon.ico' },
{ url: '/favicon.png', type: 'image/png', sizes: '32x32' },
{ url: '/icon.png', type: 'image/png', sizes: '512x512' },
],
apple: [{ url: '/apple-touch-icon.png', sizes: '180x180' }],
},
openGraph: {
type: 'website',
url: DEVELOPERS_URL,
siteName: 'Dripnex Developers',
images: [{ url: '/icon.png', width: 512, height: 512, alt: 'Dripnex' }],
},
twitter: {
card: 'summary',
images: ['/icon.png'],
},
};

export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="en" suppressHydrationWarning className="dark">
<body className="bg-background text-text-primary antialiased">
<RootProvider>
<DocsLayout
tree={source.pageTree}
{...baseOptions()}
sidebar={{
banner: (
<div className="rounded-lg border border-[var(--color-border-accent)] bg-[var(--color-accent-glow)] px-3 py-2">
<p className="text-xs font-medium text-fd-foreground">
Desktop plugin API.{' '}
<a
href={DOCS_URL}
target="_blank"
rel="noopener noreferrer"
className="text-[var(--color-accent)] underline underline-offset-2 hover:text-[var(--color-accent-hover)]"
>
User manual
</a>
</p>
</div>
),
}}
>
{children}
</DocsLayout>
</RootProvider>
</body>
</html>
);
}
19 changes: 19 additions & 0 deletions app/not-found.tsx
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
import Link from 'next/link';

export default function NotFound() {
return (
<div className="mx-auto max-w-lg px-6 py-24 text-center">
<p className="font-mono text-6xl font-extrabold text-accent">404</p>
<h1 className="mt-4 text-xl font-semibold text-text-primary">This page wandered off</h1>
<p className="mt-2 text-sm text-text-secondary">
That API page doesn&apos;t exist, or the URL changed.
</p>
<Link
href="/"
className="mt-8 inline-flex rounded-md bg-text-primary px-4 py-2 text-sm font-medium text-background hover:opacity-80"
>
Back to the API
</Link>
</div>
);
}
Loading
Loading