Skip to content

Commit

Permalink
feat: resolve hrefs
Browse files Browse the repository at this point in the history
  • Loading branch information
abvthecity committed Feb 8, 2025
1 parent c282dca commit f9cd44a
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 4 deletions.
34 changes: 33 additions & 1 deletion packages/fern-docs/bundle/src/server/withInitialProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
import { getMdxBundler } from "@fern-docs/ui/bundlers";
import {
addLeadingSlash,
conformTrailingSlash,
getRedirectForPath,
isTrailingSlashEnabled,
} from "@fern-docs/utils";
Expand Down Expand Up @@ -280,7 +281,8 @@ export async function withInitialProps({
: undefined,
};

const currentVersionId = found.currentVersion?.versionId;
const currentVersion = found.currentVersion;
const currentVersionId = currentVersion?.versionId;
const versions = withVersionSwitcherInfo({
node: found.node,
parents: found.parents,
Expand Down Expand Up @@ -327,6 +329,35 @@ export async function withInitialProps({
}
}

// sometimes absolute paths need to be attached to the current version prefix, or basepath
const rootSlug = root.slug;
const versionSlug = currentVersion?.slug;
const slugMap = found.collector.slugMap;
function resolveLinkHref(href: string): string | undefined {
if (href.startsWith("/")) {
const url = new URL(href, withDefaultProtocol(domain));
if (versionSlug != null) {
const slugWithVersion = FernNavigation.slugjoin(
versionSlug,
url.pathname
);
const found = slugMap.get(slugWithVersion);
if (found) {
return `${conformTrailingSlash(addLeadingSlash(found.slug))}${url.search}${url.hash}`;
}
}

if (rootSlug.length > 0) {
const slugWithRoot = FernNavigation.slugjoin(rootSlug, url.pathname);
const found = slugMap.get(slugWithRoot);
if (found) {
return `${conformTrailingSlash(addLeadingSlash(found.slug))}${url.search}${url.hash}`;
}
}
}
return;
}

const content = await withResolvedDocsContent({
domain: docs.baseUrl.domain,
found,
Expand All @@ -343,6 +374,7 @@ export async function withInitialProps({
slug: slug,
},
replaceSrc: resolveFileSrc,
replaceHref: resolveLinkHref,
});

const frontmatter = extractFrontmatterFromDocsContent(found.node.id, content);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ interface WithResolvedDocsContentOpts {
edgeFlags: EdgeFlags;
scope?: Record<string, unknown>;
replaceSrc?: (src: string) => ImageData | undefined;
replaceHref?: (href: string) => string | undefined;
}

export async function withResolvedDocsContent({
Expand All @@ -30,6 +31,7 @@ export async function withResolvedDocsContent({
edgeFlags,
scope,
replaceSrc,
replaceHref,
}: WithResolvedDocsContentOpts): Promise<DocsContent | undefined> {
const node = withPrunedNavigation(found.node, {
visibleNodeIds: [found.node.id],
Expand Down Expand Up @@ -81,6 +83,8 @@ export async function withResolvedDocsContent({

// inject the file url and dimensions for images and other embeddable files
replaceSrc,
// resolve absolute pathed hrefs to the correct path (considers version and basepath)
replaceHref,
},
serializeMdx,
domain,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ import {
type DocsV2Read,
} from "@fern-api/fdr-sdk/client/types";
import * as FernNavigation from "@fern-api/fdr-sdk/navigation";
import { visitDiscriminatedUnion } from "@fern-api/ui-core-utils";
import {
visitDiscriminatedUnion,
withDefaultProtocol,
} from "@fern-api/ui-core-utils";
import { getFrontmatter } from "@fern-docs/mdx";
import { withSeo } from "@fern-docs/seo";
import {
Expand All @@ -31,6 +34,8 @@ import {
} from "@fern-docs/ui";
import { serializeMdx } from "@fern-docs/ui/bundlers/next-mdx-remote";
import {
addLeadingSlash,
conformTrailingSlash,
DEFAULT_EDGE_FLAGS,
EdgeFlags,
getRedirectForPath,
Expand Down Expand Up @@ -119,6 +124,35 @@ export async function getDocsPageProps(
}
}

// sometimes absolute paths need to be attached to the current version prefix, or basepath
const rootSlug = root.slug;
const versionSlug = node.currentVersion?.slug;
const slugMap = node.collector.slugMap;
function resolveLinkHref(href: string): string | undefined {
if (href.startsWith("/")) {
const url = new URL(href, withDefaultProtocol(docs.baseUrl.domain));
if (versionSlug != null) {
const slugWithVersion = FernNavigation.slugjoin(
versionSlug,
url.pathname
);
const found = slugMap.get(slugWithVersion);
if (found) {
return `${conformTrailingSlash(addLeadingSlash(found.slug))}${url.search}${url.hash}`;
}
}

if (rootSlug.length > 0) {
const slugWithRoot = FernNavigation.slugjoin(rootSlug, url.pathname);
const found = slugMap.get(slugWithRoot);
if (found) {
return `${conformTrailingSlash(addLeadingSlash(found.slug))}${url.search}${url.hash}`;
}
}
}
return;
}

const content = await resolveDocsContent({
domain: docs.baseUrl.domain,
node: node.node,
Expand Down Expand Up @@ -154,6 +188,7 @@ export async function getDocsPageProps(

// inject the file url and dimensions for images and other embeddable files
replaceSrc: resolveFileSrc,
replaceHref: resolveLinkHref,
},
serializeMdx,
engine: "next-mdx-remote",
Expand Down
3 changes: 3 additions & 0 deletions packages/fern-docs/ui/src/mdx/bundlers/mdx-bundler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import remarkGfm from "remark-gfm";
import remarkMath from "remark-math";
import remarkSmartypants from "remark-smartypants";
import { rehypeFiles } from "../plugins/rehype-files";
import { rehypeLinks } from "../plugins/rehype-links";
import { rehypeExtractAsides } from "../plugins/rehypeExtractAsides";
import { rehypeFernCode } from "../plugins/rehypeFernCode";
import { rehypeFernComponents } from "../plugins/rehypeFernComponents";
Expand All @@ -50,6 +51,7 @@ async function serializeMdxImpl(
filename,
scope = {},
replaceSrc,
replaceHref,
}: FernSerializeMdxOptions = {}
): Promise<FernDocs.MarkdownText | undefined> {
if (content == null) {
Expand Down Expand Up @@ -141,6 +143,7 @@ async function serializeMdxImpl(
rehypeSqueezeParagraphs,
rehypeMdxClassStyle,
[rehypeFiles, { replaceSrc }],
[rehypeLinks, { replaceHref }],
rehypeAcornErrorBoundary,
rehypeSlug,
rehypeKatex,
Expand Down
28 changes: 26 additions & 2 deletions packages/fern-docs/ui/src/mdx/plugins/rehype-files.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import type {
Hast,
MdxExpression,
MdxJsxAttribute,
MdxJsxExpressionAttribute,
} from "@fern-docs/mdx";
import {
isMdxExpression,
isMdxJsxAttribute,
isMdxJsxElementHast,
mdxJsxAttributeToString,
Expand Down Expand Up @@ -33,9 +35,12 @@ export interface RehypeFilesOptions {
* @returns a function that will transform the tree
*/
export function rehypeFiles(
options: RehypeFilesOptions
options: RehypeFilesOptions = {}
): (tree: Hast.Root) => void {
return function (tree: Hast.Root): void {
if (options == null) {
return;
}
visit(tree, (node) => {
if (isMdxJsxElementHast(node)) {
const attributes = node.attributes.filter(isMdxJsxAttribute);
Expand Down Expand Up @@ -136,6 +141,7 @@ export function rehypeFiles(
if (estree == null) {
return;
}
// TODO: make this less hacky
walk(estree, {
enter(node) {
if (node.type === "Literal" && typeof node.value === "string") {
Expand All @@ -149,11 +155,27 @@ export function rehypeFiles(
});
});
}

if (isMdxExpression(node)) {
const estree = getEstree(node);
if (estree == null) {
return;
}
walk(estree, {
enter(node) {
if (node.type === "Literal" && typeof node.value === "string") {
node.value = options.replaceSrc?.(node.value)?.src ?? node.value;
}
},
});
}
});
};
}

function getEstree(attr: MdxJsxAttribute | MdxJsxExpressionAttribute) {
function getEstree(
attr: MdxJsxAttribute | MdxJsxExpressionAttribute | MdxExpression
) {
if (
attr.type === "mdxJsxAttribute" &&
attr.value &&
Expand All @@ -164,6 +186,8 @@ function getEstree(attr: MdxJsxAttribute | MdxJsxExpressionAttribute) {
return attr.value.data?.estree;
} else if (attr.type === "mdxJsxExpressionAttribute" && attr.data?.estree) {
return attr.data?.estree;
} else if (isMdxExpression(attr) && attr.data?.estree) {
return attr.data?.estree;
}
return null;
}
52 changes: 52 additions & 0 deletions packages/fern-docs/ui/src/mdx/plugins/rehype-links.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import {
isMdxJsxAttribute,
isMdxJsxElementHast,
mdxJsxAttributeToString,
visit,
type Hast,
} from "@fern-docs/mdx";

export interface RehypeLinksOptions {
replaceHref?(href: string): string | undefined;
}

export function rehypeLinks({ replaceHref }: RehypeLinksOptions = {}): (
ast: Hast.Root
) => void {
return function (ast): void {
if (replaceHref == null) {
return;
}
visit(ast, (node) => {
if (node.type === "element" && node.tagName === "a") {
const href = node.properties.href;
if (typeof href !== "string") {
return;
}
const newHref = replaceHref(href);
if (newHref == null) {
return;
}
node.properties.href = newHref;
}

// TODO handle nested attributes and mdx expressions
if (isMdxJsxElementHast(node)) {
const attributes = node.attributes.filter(isMdxJsxAttribute);
const hrefAttribute = attributes.find((attr) => attr.name === "href");
if (hrefAttribute == null) {
return;
}
const href = mdxJsxAttributeToString(hrefAttribute);
if (!href) {
return;
}
const newHref = replaceHref(href);
if (newHref == null) {
return;
}
hrefAttribute.value = newHref;
}
});
};
}
2 changes: 2 additions & 0 deletions packages/fern-docs/ui/src/mdx/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type * as FernDocs from "@fern-api/fdr-sdk/docs";
import type { Options } from "@mdx-js/esbuild";
import { RehypeFilesOptions } from "./plugins/rehype-files";
import { RehypeLinksOptions } from "./plugins/rehype-links";

export type FernSerializeMdxOptions = {
filename?: string;
Expand All @@ -10,6 +11,7 @@ export type FernSerializeMdxOptions = {
files?: Record<string, string>;
scope?: Record<string, unknown>;
replaceSrc?: RehypeFilesOptions["replaceSrc"];
replaceHref?: RehypeLinksOptions["replaceHref"];
};

export type SerializeMdxFunc =
Expand Down

0 comments on commit f9cd44a

Please sign in to comment.