diff --git a/next.config.ts b/next.config.ts
index 53433574..623e54da 100644
--- a/next.config.ts
+++ b/next.config.ts
@@ -2,8 +2,8 @@ import type { NextConfig } from "next";
const nextConfig: NextConfig = {
output: "export",
- // Optional: Change links `/me` -> `/me/` and emit `/me.html` -> `/me/index.html`
- // trailingSlash: true,
+ // Change links `/me` -> `/me/` and emit `/me.html` -> `/me/index.html`
+ trailingSlash: true,
// Optional: Prevent automatic `/me` -> `/me/`, instead preserve `href`
// skipTrailingSlashRedirect: true,
// Optional: Change the output directory `out` -> `dist`
diff --git a/src/app/blog/[year]/[month]/[day]/[slug]/page.tsx b/src/app/blog/[year]/[month]/[day]/[slug]/page.tsx
index 44006126..7b18ca77 100644
--- a/src/app/blog/[year]/[month]/[day]/[slug]/page.tsx
+++ b/src/app/blog/[year]/[month]/[day]/[slug]/page.tsx
@@ -13,6 +13,7 @@ export async function generateMetadata({
}: {
params: Promise<{ year: string; month: string; day: string; slug: string }>;
}) {
+ const { year, month, day, slug } = await params;
const { frontmatter } = getPost(await params);
const excerpt = frontmatter.excerpt
? frontmatter.excerpt.length > 160
@@ -23,6 +24,7 @@ export async function generateMetadata({
return getPageMetadata({
pageTitle: `${frontmatter.title}`,
pageDescription: excerpt,
+ pagePath: `/blog/${year}/${month}/${day}/${slug}/`,
});
}
@@ -34,7 +36,7 @@ export default async function BlogPostPage({
const { frontmatter, content } = getPost(await params);
return (
-
+
{frontmatter.title}
diff --git a/src/app/blog/page.tsx b/src/app/blog/page.tsx
index f5805e27..dde36583 100644
--- a/src/app/blog/page.tsx
+++ b/src/app/blog/page.tsx
@@ -2,16 +2,7 @@ import { getAllPosts } from "@/blog-helpers";
import PromMarkdown from "@/components/PromMarkdown";
import TOC from "@/components/TOC";
import { getPageMetadata } from "@/page-metadata";
-import {
- Anchor,
- Title,
- Text,
- Card,
- Stack,
- Button,
- Box,
- Group,
-} from "@mantine/core";
+import { Anchor, Title, Text, Card, Stack, Button, Group } from "@mantine/core";
import dayjs from "dayjs";
import { Metadata } from "next";
import Link from "next/link";
@@ -20,6 +11,7 @@ export const metadata: Metadata = getPageMetadata({
pageTitle: "Blog",
pageDescription:
"The Prometheus blog contains articles about the project, its components, and the ecosystem.",
+ pagePath: "/blog/",
});
function headingSlug({ year, month, day, slug }) {
@@ -52,9 +44,7 @@ export default function BlogPage() {
{dayjs(frontmatter.created_at).format("MMMM D, YYYY")} by{" "}
{frontmatter.author_name}
-
- {excerpt}
-
+ {excerpt}
+ )}
+
+ }
+ fw="normal"
+ >
+ Edit this page
+
+
+ {currentPage.next && (
+ }
+ ta="left"
+ bd="1px solid var(--mantine-color-gray-5)"
+ >
+
+
+ Next
+
+
+ {currentPage.next.title}
+
+
+
+ )}
+
+
+ );
+}
diff --git a/src/app/docs/[...slug]/VersionWarning.tsx b/src/app/docs/[...slug]/VersionWarning.tsx
new file mode 100644
index 00000000..f2365520
--- /dev/null
+++ b/src/app/docs/[...slug]/VersionWarning.tsx
@@ -0,0 +1,58 @@
+import { Alert } from "@mantine/core";
+import Link from "next/link";
+import { IconInfoCircle } from "@tabler/icons-react";
+import { DocMetadata } from "@/docs-collection-types";
+
+function compareVersions(a: string, b: string): number {
+ const [majorA, minorA] = a.split(".").map(Number);
+ const [majorB, minorB] = b.split(".").map(Number);
+
+ if (majorA !== majorB) {
+ return Math.sign(majorA - majorB);
+ }
+ return Math.sign(minorA - minorB);
+}
+
+export default function VersionWarning({
+ currentPage,
+}: {
+ currentPage: DocMetadata;
+}) {
+ if (currentPage.type === "repo-doc") {
+ // TODO: Clean this up, the version could theoretically appear in the path
+ // in unintended places as well.
+ const latestSlug = currentPage.slug.replace(currentPage.version, "latest");
+
+ switch (compareVersions(currentPage.version, currentPage.latestVersion)) {
+ case -1:
+ return (
+ }
+ title="Outdated version"
+ color="yellow"
+ mb="xl"
+ >
+ This page documents version {currentPage.version}, which is
+ outdated. Check out the{" "}
+ latest stable version.
+
+ );
+ case 1:
+ return (
+ }
+ title="Pre-release version"
+ color="yellow"
+ mb="xl"
+ >
+ This page documents a pre-release version ({currentPage.version}).
+ Check out the{" "}
+ latest stable version (
+ {currentPage.latestVersion}).
+
+ );
+ }
+ }
+
+ return null;
+}
diff --git a/src/app/docs/[...slug]/page.tsx b/src/app/docs/[...slug]/page.tsx
index 9594c9f5..df353733 100644
--- a/src/app/docs/[...slug]/page.tsx
+++ b/src/app/docs/[...slug]/page.tsx
@@ -1,15 +1,19 @@
import fs from "fs/promises";
-// import { CodeHighlight } from "@mantine/code-highlight";
import { docsCollection } from "@/docs-collection";
import PromMarkdown, { isAbsoluteUrl } from "@/components/PromMarkdown";
import docsConfig from "../../../../docs-config";
import { getPageMetadata } from "@/page-metadata";
+import VersionWarning from "./VersionWarning";
+import { Divider } from "@mantine/core";
+import PrevNextEditButtons from "./PrevNextEditButtons";
+import { HTMLAttributes } from "react";
+import path from "path";
// Next.js uses this function at build time to figure out which
// docs pages it should statically generate.
export async function generateStaticParams() {
const params = Object.keys(docsCollection).map((slug: string) => ({
- slug: slug.split("/"),
+ slug: slug.split("/").concat(""),
}));
return params;
}
@@ -19,65 +23,101 @@ export async function generateMetadata({
}: {
params: Promise<{ slug: string[] }>;
}) {
- const slug = (await params).slug;
- const docMeta = docsCollection[slug.join("/")];
+ const slugArray = (await params).slug;
+ const slug = slugArray.join("/");
+
+ const docMeta = docsCollection[slug];
+ if (!docMeta) {
+ throw new Error(`Page not found for slug: ${slug}`);
+ }
return getPageMetadata({
pageTitle: docMeta.title,
pageDescription: `Prometheus project documentation for ${docMeta.title}`,
- pagePath: `/docs/${slug.join("/")}`,
+ pagePath: `/docs/${slug}/`,
});
}
+function resolveRelativeUrl(currentPath: string, relativeUrl: string): string {
+ const [pathAndQuery, hash = ""] = relativeUrl.split("#");
+ const [relativePath, query = ""] = pathAndQuery.split("?");
+
+ const baseDir = currentPath.endsWith("/")
+ ? currentPath
+ : path.posix.dirname(currentPath) + "/";
+
+ const resolvedPath = path.posix.resolve(
+ baseDir,
+ relativePath.replace(/\.md$/, "/")
+ );
+
+ return resolvedPath + (query ? `?${query}` : "") + (hash ? `#${hash}` : "");
+}
+
export default async function DocsPage({
params,
}: {
params: Promise<{ slug: string[] }>;
}) {
- const slug = (await params).slug;
+ const slugArray = (await params).slug;
+ const slug = slugArray.join("/");
+
+ const docMeta = docsCollection[slug];
+ if (!docMeta) {
+ throw new Error(`Page not found for slug: ${slug}`);
+ }
- const docMeta = docsCollection[slug.join("/")];
const markdown = await fs.readFile(docMeta.filePath, "utf-8");
+ const pagefindShouldIndex =
+ docMeta.type === "local-doc" ||
+ (docMeta.version === docMeta.latestVersion &&
+ !docMeta.slug.startsWith(docMeta.versionRoot));
+
return (
- {
- if (!href) {
- return href;
+ <>
+
+
}
+ normalizeHref={(href: string | undefined) => {
+ if (!href) {
+ return href;
+ }
- // Do some postprocessing on the hrefs to make sure they point to the right place.
- if (href.startsWith(docsConfig.siteUrl)) {
- // Remove the "https://prometheus.io" from links that start with it.
- return href.slice(docsConfig.siteUrl.length);
- } else if (href.startsWith("/") && docMeta.type === "repo-doc") {
- // Turn "/" into e.g. "https://github.com/prometheus/prometheus/blob/release-3.3/"
- return `https://github.com/${docMeta.owner}/${docMeta.repo}/blob/release-${docMeta.version}${href}`;
- } else if (href.includes(".md") && !isAbsoluteUrl(href)) {
- // Turn "foo/bar/baz.md" into "foo/bar/baz" for relative links between Markdown pages.
- return `${href.replace(/\.md($|#)/, "$1")}`;
- }
-
- return href;
- }}
- normalizeImgSrc={(src: string | Blob | undefined) => {
- // Leave anything alone that doesn't look like a normal relative URL.
- if (!src || typeof src !== "string" || isAbsoluteUrl(src)) {
- return src;
- }
-
- switch (docMeta.type) {
- case "local-doc":
- // TODO: Fix this in the old Markdown files instead?
- return src.replace(/^\/assets\//, "/assets/docs/");
- case "repo-doc":
+ // Do some postprocessing on the hrefs to make sure they point to the right place.
+ if (href.startsWith(docsConfig.siteUrl)) {
+ // Remove the "https://prometheus.io" from links that start with it.
+ return href.slice(docsConfig.siteUrl.length);
+ } else if (href.startsWith("/") && docMeta.type === "repo-doc") {
+ // Turn "/" into e.g. "https://github.com/prometheus/prometheus/blob/release-3.3/"
+ return `https://github.com/${docMeta.owner}/${docMeta.repo}/blob/release-${docMeta.version}${href}`;
+ } else if (href.includes(".md") && !isAbsoluteUrl(href)) {
+ // Turn relative links like "d.md" in "docs/a/b/c.md" into full paths like "/docs/a/b/d/".
+ return resolveRelativeUrl(`/docs/${docMeta.slug}`, href);
+ }
+ }}
+ normalizeImgSrc={(src: string | Blob | undefined) => {
+ // Leave anything alone that doesn't look like a normal relative URL.
+ if (
+ src &&
+ typeof src === "string" &&
+ !isAbsoluteUrl(src) &&
+ docMeta.type === "repo-doc"
+ ) {
return `${docMeta.assetsRoot}/${src}`;
- default:
- throw new Error(`Unknown doc type`);
- }
- }}
- >
- {markdown}
-
+ }
+
+ return src;
+ }}
+ >
+ {markdown}
+
+
+
+ >
);
}
diff --git a/src/app/docs/layout.tsx b/src/app/docs/layout.tsx
index 26a5d49c..b27addb7 100644
--- a/src/app/docs/layout.tsx
+++ b/src/app/docs/layout.tsx
@@ -1,298 +1,27 @@
-"use client";
-
-import {
- docsCollection,
- allRepoVersions,
- getDocsRoots,
-} from "@/docs-collection";
-import { DocMetadata } from "@/docs-collection-types";
import {
Group,
Box,
- Select,
- NavLink,
- Alert,
- ScrollAreaAutosize,
+ ScrollArea,
Button,
Popover,
- Text,
- ScrollArea,
- Stack,
- Divider,
+ ScrollAreaAutosize,
+ PopoverDropdown,
+ PopoverTarget,
} from "@mantine/core";
-import Link from "next/link";
-import { usePathname, useRouter } from "next/navigation";
-import {
- IconFlask,
- IconServer,
- IconCode,
- IconThumbUp,
- IconBell,
- IconBook,
- IconSettings,
- IconHandFingerRight,
- IconChartLine,
- IconMap,
- IconFileDescription,
- IconProps,
- IconInfoCircle,
- IconTag,
- IconMenu2,
- IconArrowRight,
- IconArrowLeft,
- IconPencil,
-} from "@tabler/icons-react";
-import { ReactElement, useEffect, useRef } from "react";
import TOC from "@/components/TOC";
-
-const iconMap: Record> = {
- flask: IconFlask,
- server: IconServer,
- code: IconCode,
- "thumb-up": IconThumbUp,
- bell: IconBell,
- book: IconBook,
- settings: IconSettings,
- "hand-finger-right": IconHandFingerRight,
- "chart-line": IconChartLine,
- map: IconMap,
- "file-description": IconFileDescription,
-};
-
-function NavIcon({ iconName, ...props }: { iconName: string } & IconProps) {
- const Icon = iconMap[iconName];
- if (!Icon) {
- throw new Error(`Unknown icon name: ${iconName}`);
- }
- return ;
-}
-
-// Return a navigation tree UI for the DocsTree.
-function buildRecursiveNav(
- docsTree: DocMetadata[],
- currentPageSlug: string,
- router: ReturnType,
- level = 0
-) {
- return docsTree.map((doc) => {
- if (doc.children.length > 0) {
- // Node is a "directory".
- const fc = doc.children[0];
- const repoVersions =
- fc && fc.type === "repo-doc"
- ? allRepoVersions[fc.owner][fc.repo]
- : null;
-
- const currentPage = docsCollection[currentPageSlug];
- const currentPageVersion =
- currentPage.type === "repo-doc" &&
- fc.type === "repo-doc" &&
- currentPage.owner === fc.owner &&
- currentPage.repo === fc.repo
- ? currentPage.version
- : null;
-
- const shownChildren = doc.children.filter((child) => {
- if (child.hideInNav) {
- return false;
- }
-
- // Always show unversioned local docs in the nav.
- if (child.type === "local-doc") {
- return true;
- }
-
- // Always show latest version docs if we're not looking at a different version of the same repo.
- if (
- !currentPageVersion &&
- child.version === repoVersions?.latestVersion
- ) {
- if (child.slug.startsWith(child.versionRoot)) {
- // Don't show "3.4", even if it is the latest.
- return false;
- } else {
- // Show "latest".
- return true;
- }
- }
-
- // If we're looking at a specific version and it's not the latest version,
- // show all children with that same version.
- if (child.version === currentPageVersion) {
- if (currentPageVersion !== repoVersions?.latestVersion) {
- return true;
- } else {
- if (child.slug.startsWith(child.versionRoot)) {
- return false;
- } else {
- return true;
- }
- }
- }
- });
-
- const navIcon = doc.type === "local-doc" && doc.navIcon;
- const active = currentPageSlug.startsWith(doc.slug);
-
- return (
-
- ) : undefined
- }
- ff={level === 0 ? "var(--font-inter)" : undefined}
- fw={level === 0 ? 500 : undefined}
- style={{ borderRadius: 2.5 }}
- >
-
- {level === 0 && repoVersions && (
- }
- title="Select version"
- value={currentPageVersion || repoVersions.latestVersion}
- data={repoVersions.versions.map((version) => ({
- value: version,
- label:
- version === repoVersions.latestVersion
- ? `${version} (latest)`
- : repoVersions.ltsVersions.includes(version)
- ? `${version} (LTS)`
- : version,
- }))}
- onChange={(version) => {
- const newPageNode = doc.children.filter(
- (child) =>
- child.type === "repo-doc" && child.version === version
- )[0];
- if (newPageNode) {
- router.push(`/docs/${newPageNode.slug}`);
- }
- }}
- />
- )}
- {buildRecursiveNav(
- shownChildren,
- currentPageSlug,
- router,
- level + 1
- )}
-
-
- );
- }
-
- const active = currentPageSlug === doc.slug;
-
- // Node is a "file" (document).
- return (
-
- );
- });
-}
-
-function compareVersions(a: string, b: string): number {
- const [majorA, minorA] = a.split(".").map(Number);
- const [majorB, minorB] = b.split(".").map(Number);
-
- if (majorA !== majorB) {
- return Math.sign(majorA - majorB);
- }
- return Math.sign(minorA - minorB);
-}
+import LeftNav from "./LeftNav";
+import { IconMenu2 } from "@tabler/icons-react";
export default function DocsLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
- const router = useRouter();
- const pageSlug = usePathname().replace(/^\/docs\//, "");
- const currentPage = docsCollection[pageSlug];
- const reinitializeTOCRef = useRef(() => {});
- const pagefindShouldIndex =
- currentPage.type === "local-doc" ||
- (currentPage.version === currentPage.latestVersion &&
- !currentPage.slug.startsWith(currentPage.versionRoot));
-
- useEffect(() => {
- reinitializeTOCRef.current();
- }, [pageSlug]);
-
- let alert: ReactElement | null = null;
- if (currentPage.type === "repo-doc") {
- // TODO: Clean this up, the version could theoretically appear in the path
- // in unintended places as well.
- const latestSlug = pageSlug.replace(currentPage.version, "latest");
-
- switch (compareVersions(currentPage.version, currentPage.latestVersion)) {
- case -1:
- alert = (
- }
- title="Outdated version"
- color="yellow"
- mb="xl"
- >
- This page documents version {currentPage.version}, which is
- outdated. Check out the{" "}
- latest stable version.
-
- );
- break;
- case 1:
- alert = (
- }
- title="Pre-release version"
- color="yellow"
- mb="xl"
- >
- This page documents a pre-release version ({currentPage.version}).
- Check out the{" "}
- latest stable version (
- {currentPage.latestVersion}).
-
- );
- break;
- }
- }
-
- const nav = buildRecursiveNav(getDocsRoots(), pageSlug, router);
-
return (
<>
{/* The mobile main nav */}
-
+
Show nav
-
-
+
+
- {nav}
+
-
+
{/* The left-hand side main docs nav */}
@@ -336,110 +65,20 @@ export default function DocsLayout({
h="calc(100vh - var(--header-height) - var(--header-to-content-margin))"
type="never"
>
- {nav}
+
+
+
{/* The main docs page content */}
-
- {alert}
- {children}
-
- {/* Previous / next sibling page navigation */}
-
-
-
- {currentPage.prev && (
- }
- ta="right"
- bd="1px solid var(--mantine-color-gray-5)"
- >
-
-
- Previous
-
-
- {currentPage.prev.title}
-
-
-
- )}
-
- }
- fw="normal"
- >
- Edit this page
-
-
- {currentPage.next && (
- }
- ta="left"
- bd="1px solid var(--mantine-color-gray-5)"
- >
-
-
- Next
-
-
- {currentPage.next.title}
-
-
-
- )}
-
-
-
+
{children}
{/* The right-hand-side table of contents for headings
within the current document */}
-
+
Governance
{content}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 166dac49..e0155612 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,11 +1,10 @@
-"use client";
-
// import type { Metadata } from "next";
import { Inter } from "next/font/google";
import { Lato } from "next/font/google";
import {
Anchor,
AppShell,
+ AppShellMain,
ColorSchemeScript,
Container,
Group,
@@ -24,8 +23,6 @@ import "./globals.css";
import { Header } from "@/components/Header";
import { theme } from "@/theme";
-import "@docsearch/css";
-
const interFont = Inter({
variable: "--font-inter",
subsets: ["latin"],
@@ -58,12 +55,12 @@ export default function RootLayout({
-
+
{children}
-
+