Skip to content

Commit

Permalink
navigable site
Browse files Browse the repository at this point in the history
  • Loading branch information
abvthecity committed Feb 6, 2025
1 parent d2dc25f commit 6e6605b
Show file tree
Hide file tree
Showing 44 changed files with 487 additions and 413 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"elliptic": "6.6.0",
"esbuild": "0.24.2",
"eslint": "9.17.0",
"eslint-config-next": "15.1.2",
"eslint-config-next": "14.2.23",
"instantsearch.js": "4.75.4",
"jsonpath-plus": "10.0.7",
"markdown-to-jsx": "7.4.0",
Expand Down Expand Up @@ -84,7 +84,7 @@
"depcheck": "^1.4.7",
"dotenv": "^16.4.5",
"eslint": "^9",
"eslint-config-next": "15.1.2",
"eslint-config-next": "14.2.23",
"eslint-config-prettier": "^9.1.0",
"eslint-config-turbo": "^2.3.3",
"eslint-plugin-deprecation": "^3.0.0",
Expand Down
4 changes: 4 additions & 0 deletions packages/commons/core-utils/src/withDefaultProtocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ export function withDefaultProtocol(
return undefined;
}

if (endpoint === "") {
throw new Error(`URL is empty`);
}

// matches any protocol scheme at the beginning of the string (e.g., "http://", "https://", "ftp://")
const protocolRegex = /^[a-z]+:\/\//i;

Expand Down
4 changes: 3 additions & 1 deletion packages/fern-docs/bundle/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
"chromatic": "pnpx chromatic --project-token=chpt_48b3c560025e978",
"depcheck": "depcheck",
"docs:build": "next build",
"docs:dev": "NODE_OPTIONS='--inspect' next dev",
"docs:dev": "next dev",
"docs:dev:inspect": "NODE_OPTIONS='--inspect' next dev",
"docs:start": "next start",
"format": "prettier --write --ignore-unknown \"**\"",
"format:check": "prettier --check --ignore-unknown \"**\"",
Expand Down Expand Up @@ -79,6 +80,7 @@
"@segment/snippet": "^5.2.1",
"@types/qs": "6.9.14",
"@upstash/qstash": "^2.7.16",
"@vercel/functions": "^2.0.0",
"@vercel/kv": "^2.0.0",
"@vercel/otel": "^1.10.0",
"@workos-inc/node": "^7.31.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ import FeedbackPopover from "@/components/feedback/FeedbackPopover";
import { serializeMdx } from "@/components/mdx/bundlers/mdx-bundler";
import { DocsContent } from "@/components/resolver/DocsContent";
import { renderThemeStylesheet } from "@/components/themes/stylesheet/renderThemeStylesheet";
import { ThemedDocs } from "@/components/themes/ThemedDocs";
import { getApiRouteSupplier } from "@/components/util/getApiRouteSupplier";
import { getGitHubInfo, getGitHubRepo } from "@/components/util/github";
import { getOrigin } from "@/server/auth/origin";
import { getReturnToQueryParam } from "@/server/auth/return-to";
import { createCachedDocsLoader, DocsLoader } from "@/server/docs-loader";
import { createFileResolver } from "@/server/file-resolver";
Expand Down Expand Up @@ -44,13 +46,15 @@ import React from "react";
import urlJoin from "url-join";
import { toImageDescriptor } from "../seo";

export default async function Page({
export async function DocsPageComponent({
params,
fern_token,
}: {
params: { slug?: string[] };
params: { slug?: string[]; domain: string };
fern_token?: string;
}) {
const slug = FernNavigation.slugjoin(params.slug);
const loader = await createCachedDocsLoader();
const loader = await createCachedDocsLoader(params.domain, fern_token);
const [baseUrl, config, authState, edgeFlags, files, colors] =
await Promise.all([
loader.getBaseUrl(),
Expand Down Expand Up @@ -187,7 +191,7 @@ export default async function Page({
const redirect = new URL(withDefaultProtocol(loader.authConfig.redirect));
redirect.searchParams.set(
getReturnToQueryParam(loader.authConfig),
urlJoin(withDefaultProtocol(loader.host), slug)
urlJoin(getOrigin(), slug)
);

navbarLinks.push({
Expand All @@ -206,11 +210,11 @@ export default async function Page({
if (authState.authed) {
const logout = new URL(
getApiRoute("/api/fern-docs/auth/logout"),
withDefaultProtocol(loader.host)
withDefaultProtocol(getOrigin())
);
logout.searchParams.set(
getReturnToQueryParam(loader.authConfig),
urlJoin(withDefaultProtocol(loader.host), slug)
urlJoin(withDefaultProtocol(getOrigin()), slug)
);

navbarLinks.push({
Expand Down Expand Up @@ -369,38 +373,43 @@ export default async function Page({

return (
<NextApp pageProps={props}>
<DocsPage theme={props.theme}>
<FeedbackPopoverProvider>
<DocsMainContent
node={found.node}
parents={found.parents}
neighbors={await getNeighbors(
{ prev: found.prev, next: found.next },
loader
)}
breadcrumb={found.breadcrumb}
// apiReferenceNodes={apiReferenceNodes}
scope={{
authed: authState.authed,
user: authState.authed ? authState.user : undefined,
version: found?.currentVersion?.versionId,
tab: found?.currentTab?.title,
slug: slug,
}}
/>
</FeedbackPopoverProvider>
<DocsPage>
<ThemedDocs theme={props.theme}>
<FeedbackPopoverProvider>
<DocsMainContent
domain={params.domain}
node={found.node}
parents={found.parents}
neighbors={await getNeighbors(
{ prev: found.prev, next: found.next },
loader
)}
breadcrumb={found.breadcrumb}
// apiReferenceNodes={apiReferenceNodes}
scope={{
authed: authState.authed,
user: authState.authed ? authState.user : undefined,
version: found?.currentVersion?.versionId,
tab: found?.currentTab?.title,
slug: slug,
}}
/>
</FeedbackPopoverProvider>
</ThemedDocs>
</DocsPage>
</NextApp>
);
}

export async function generateMetadata({
export async function generateDocsPageMetadata({
params,
fern_token,
}: {
params: { slug?: string[] };
params: { slug?: string[]; domain: string };
fern_token?: string;
}): Promise<Metadata> {
const slug = FernNavigation.slugjoin(params.slug);
const docsLoader = await createCachedDocsLoader();
const docsLoader = await createCachedDocsLoader(params.domain, fern_token);
const findNode = createFindNode(docsLoader);
const [files, node, config, isSeoDisabled] = await Promise.all([
docsLoader.getFiles(),
Expand Down
190 changes: 190 additions & 0 deletions packages/fern-docs/bundle/src/app/[domain]/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
"use server";

import { CustomerAnalytics } from "@/components/analytics/CustomerAnalytics";
import Preload, { PreloadHref } from "@/components/preload";
import { ThemeScript } from "@/components/themes/ThemeScript";
import { createCachedDocsLoader } from "@/server/docs-loader";
import { RgbaColor } from "@/server/types";
import { getDocsDomainApp } from "@/server/xfernhost/app";
import { DocsV2Read } from "@fern-api/fdr-sdk/client/types";
import { withDefaultProtocol } from "@fern-api/ui-core-utils";
import { Toaster } from "@fern-docs/components";
import { getEdgeFlags } from "@fern-docs/edge-config";
import { EdgeFlags } from "@fern-docs/utils";
import { compact, uniqBy } from "es-toolkit/array";
import { Metadata, Viewport } from "next/types";
import tinycolor from "tinycolor2";
import { toImageDescriptor } from "../seo";

export default async function Layout({
children,
params,
}: {
children: React.ReactNode;
params: {
domain: string;
};
}) {
const docsLoader = await createCachedDocsLoader(params.domain);
const [config, edgeFlags, files, colors] = await Promise.all([
docsLoader.getConfig(),
getEdgeFlags(params.domain),
docsLoader.getFiles(),
docsLoader.getColors(),
]);
const preloadHrefs = generatePreloadHrefs(
config?.typographyV2,
files,
edgeFlags
);
return (
<>
{preloadHrefs.map((href) => (
<Preload key={href.href} href={href.href} options={href.options} />
))}
<ThemeScript colors={colors} />
<Toaster />
{children}
<CustomerAnalytics />
</>
);
}

export async function generateViewport(): Promise<Viewport> {
const domain = getDocsDomainApp();
const docsLoader = await createCachedDocsLoader(domain);
const colors = await docsLoader.getColors();
const dark = maybeToHex(
colors.dark?.background ?? colors.dark?.accentPrimary
);
const light = maybeToHex(
colors.light?.background ?? colors.light?.accentPrimary
);
return {
themeColor: compact([
dark ? { color: dark, media: "(prefers-color-scheme: dark)" } : undefined,
light
? { color: light, media: "(prefers-color-scheme: light)" }
: undefined,
]),
};
}

function maybeToHex(color: RgbaColor | undefined): string | undefined {
if (color == null) {
return undefined;
}
return tinycolor(color).toHexString();
}

export async function generateMetadata({
params,
}: {
params: {
domain: string;
};
}): Promise<Metadata> {
const docsLoader = await createCachedDocsLoader(params.domain, params.domain);
const [files, config, baseUrl] = await Promise.all([
docsLoader.getFiles(),
docsLoader.getConfig(),
docsLoader.getBaseUrl(),
]);
const index = config?.metadata?.noindex ? false : undefined;
const follow = config?.metadata?.nofollow ? false : undefined;

return {
metadataBase: new URL(
baseUrl.basePath || "/",
withDefaultProtocol(docsLoader.domain)
),
applicationName: config?.title,
title: {
template: config?.title ? "%s | " + config?.title : "%s",
default: config?.title ?? "Documentation",
},
robots: { index, follow },
openGraph: {
title: config?.metadata?.["og:title"],
description: config?.metadata?.["og:description"],
locale: config?.metadata?.["og:locale"],
url: config?.metadata?.["og:url"],
siteName: config?.metadata?.["og:site_name"],
images: toImageDescriptor(
files,
config?.metadata?.["og:image"],
config?.metadata?.["og:image:width"],
config?.metadata?.["og:image:height"]
),
},
twitter: {
site: config?.metadata?.["twitter:site"],
creator: config?.metadata?.["twitter:handle"],
title: config?.metadata?.["twitter:title"],
description: config?.metadata?.["twitter:description"],
images: toImageDescriptor(files, config?.metadata?.["twitter:image"]),
},
icons: {
icon: config?.favicon
? toImageDescriptor(files, {
type: "fileId",
value: config.favicon,
})?.url
: undefined,
},
};
}

function generatePreloadHrefs(
typography: DocsV2Read.LoadDocsForUrlResponse["definition"]["config"]["typographyV2"],
files: Record<string, { src: string }>,
edgeFlags: EdgeFlags
): PreloadHref[] {
const toReturn: PreloadHref[] = [];

const fontVariants = compact([
typography?.bodyFont?.variants,
typography?.headingsFont?.variants,
typography?.codeFont?.variants,
]).flat();

fontVariants.forEach((variant) => {
try {
const file = files[variant.fontFile];
if (file != null) {
toReturn.push({
href: file.src,
options: {
as: "font",
crossOrigin: "anonymous",
type: `font/${getFontExtension(file.src)}`,
},
});
}
} catch {}
});

if (edgeFlags.isApiPlaygroundEnabled) {
toReturn.push({
href: "/api/fern-docs/auth/api-key-injection",
options: { as: "fetch" },
});
}

toReturn.push({
href: edgeFlags.isSearchV2Enabled
? "/api/fern-docs/search/v2/key"
: "/api/fern-docs/search/v1/key",
options: { as: "fetch" },
});

return uniqBy(toReturn, (href) => href.href);
}

function getFontExtension(url: string): string {
const ext = new URL(url).pathname.split(".").pop();
if (ext == null) {
throw new Error("No extension found for font: " + url);
}
return ext;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"use server";

import { cookies } from "next/headers";
import { Metadata } from "next/types";
import { DocsPageComponent, generateDocsPageMetadata } from "../../docs-page";

export default async function Page({
params,
}: {
params: { slug?: string[]; domain: string };
}) {
const fern_token = cookies().get("fern_token")?.value;
return <DocsPageComponent params={params} fern_token={fern_token} />;
}

export async function generateMetadata({
params,
}: {
params: { slug?: string[]; domain: string };
}): Promise<Metadata> {
const fern_token = cookies().get("fern_token")?.value;
return generateDocsPageMetadata({ params, fern_token });
}
Loading

0 comments on commit 6e6605b

Please sign in to comment.