From a131fd49c630de5d549d4c7cc65d726e5c67127e Mon Sep 17 00:00:00 2001 From: Johanan Ottensooser Date: Wed, 29 Oct 2025 13:59:15 -0600 Subject: [PATCH 1/4] shad-cn like docs button --- apps/framework-docs/theme.config.jsx | 140 +++++++++++++++++++++------ 1 file changed, 110 insertions(+), 30 deletions(-) diff --git a/apps/framework-docs/theme.config.jsx b/apps/framework-docs/theme.config.jsx index 8b19491db6..494dabd95d 100644 --- a/apps/framework-docs/theme.config.jsx +++ b/apps/framework-docs/theme.config.jsx @@ -12,10 +12,21 @@ import { BreadcrumbList, BreadcrumbSeparator, } from "@/components/ui/breadcrumb"; +import { Button } from "@/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; import { useRouter } from "next/router"; import { useConfig, useThemeConfig } from "nextra-theme-docs"; import { PathConfig } from "./src/components/ctas"; import { GitHubStarsButton } from "@/components"; +import { Bot, ChevronDown, FileText } from "lucide-react"; // Base text styles that match your typography components const baseTextStyles = { @@ -26,26 +37,50 @@ const baseTextStyles = { heading: "text-primary font-semibold", }; -function buildLlmHref(asPath, suffix) { - if (!suffix) { - return "/"; - } +const DEFAULT_SITE_URL = + process.env.NEXT_PUBLIC_SITE_URL || "https://docs.fiveonefour.com"; +function normalizePath(asPath) { const safePath = asPath || "/"; - const pathWithoutHash = safePath.split("#")[0]; - const pathWithoutQuery = pathWithoutHash.split("?")[0]; + const pathWithoutQuery = pathWithoutHash.split("?")[0] || "/"; if (!pathWithoutQuery || pathWithoutQuery === "/") { - return `/${suffix}`; + return "/"; } - const trimmedPath = - pathWithoutQuery.endsWith("/") ? + return pathWithoutQuery.endsWith("/") ? pathWithoutQuery.slice(0, -1) : pathWithoutQuery; +} - return `${trimmedPath}/${suffix}`; +function resolveAbsoluteUrl(path, origin) { + if (!path) { + return origin; + } + + if (/^https?:\/\//.test(path)) { + return path; + } + + const base = origin.endsWith("/") ? origin.slice(0, -1) : origin; + const normalized = path.startsWith("/") ? path : `/${path}`; + + return `${base}${normalized}`; +} + +function buildLlmHref(asPath, suffix) { + if (!suffix) { + return "/"; + } + + const normalizedPath = normalizePath(asPath); + + if (!normalizedPath || normalizedPath === "/") { + return `/${suffix}`; + } + + return `${normalizedPath}/${suffix}`; } function EditLinks({ filePath, href, className, children }) { @@ -68,6 +103,38 @@ function EditLinks({ filePath, href, className, children }) { const tsHref = buildLlmHref(asPath, "llm-ts.txt"); const pyHref = buildLlmHref(asPath, "llm-py.txt"); + const normalizedPath = normalizePath(asPath); + + const canonicalPageUrl = + !normalizedPath || normalizedPath === "/" ? + DEFAULT_SITE_URL + : resolveAbsoluteUrl(normalizedPath, DEFAULT_SITE_URL); + + const handleOpenDoc = (target) => () => { + if (typeof window === "undefined") { + return; + } + + const absoluteUrl = resolveAbsoluteUrl(target, window.location.origin); + window.open(absoluteUrl, "_blank", "noopener,noreferrer"); + }; + + const handleOpenChatGpt = (languageLabel, docTarget) => () => { + if (typeof window === "undefined") { + return; + } + + const docUrl = resolveAbsoluteUrl(docTarget, DEFAULT_SITE_URL); + const prompt = + `I'm looking at the Moose documentation: ${canonicalPageUrl}. ` + + `Use the ${languageLabel} LLM doc for additional context: ${docUrl}. ` + + "Help me understand how to use it. Be ready to explain concepts, give examples, or help debug based on it."; + + const chatGptUrl = + "https://chatgpt.com/?prompt=" + encodeURIComponent(prompt); + + window.open(chatGptUrl, "_blank", "noopener,noreferrer"); + }; return (
@@ -81,26 +148,39 @@ function EditLinks({ filePath, href, className, children }) { {children} : {children}} - - LLM docs:{" "} - - TS - {" "} - /{" "} - - PY - - + + + + + + LLM docs + + + View TypeScript doc + TS + + + + View Python doc + PY + + + Send to ChatGPT + + + ChatGPT · TypeScript + TS + + + + ChatGPT · Python + PY + + +
); } From af9e1cb8233582688b8e2bafb8ee0be1ec139be4 Mon Sep 17 00:00:00 2001 From: Johanan Ottensooser Date: Wed, 29 Oct 2025 14:25:00 -0600 Subject: [PATCH 2/4] =?UTF-8?q?=E2=80=A2=20LLM=20docs:=20move=20helper=20U?= =?UTF-8?q?I=20beside=20titles=20and=20add=20raw=20Markdown=20copy=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/framework-docs/src/pages/api/docs/raw.ts | 180 +++++++++++ apps/framework-docs/theme.config.jsx | 300 ++++++++++++++---- 2 files changed, 418 insertions(+), 62 deletions(-) create mode 100644 apps/framework-docs/src/pages/api/docs/raw.ts diff --git a/apps/framework-docs/src/pages/api/docs/raw.ts b/apps/framework-docs/src/pages/api/docs/raw.ts new file mode 100644 index 0000000000..25ef603e5e --- /dev/null +++ b/apps/framework-docs/src/pages/api/docs/raw.ts @@ -0,0 +1,180 @@ +import type { NextApiRequest, NextApiResponse } from "next"; +import path from "path"; +import fs from "fs/promises"; +import { extractDecodedParam } from "@/lib/llmHelpers"; + +const DOCS_ROOT = path.join(process.cwd(), "src/pages"); + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse, +) { + if (req.method !== "GET") { + res.setHeader("Allow", ["GET"]); + return res.status(405).end(`Method ${req.method} Not Allowed`); + } + + const scope = extractDecodedParam(req.query.scope); + const file = extractDecodedParam(req.query.file); + + try { + const resolvedPath = await resolveDocPath({ file, scope }); + + if (!resolvedPath) { + return res.status(404).send("Document not found"); + } + + const content = await fs.readFile(resolvedPath, "utf8"); + res.setHeader("Content-Type", "text/markdown; charset=utf-8"); + res.status(200).send(content); + } catch (error) { + console.error("Failed to read documentation file", error); + res.status(500).send("Internal Server Error"); + } +} + +interface ResolveOptions { + file?: string; + scope?: string; +} + +async function resolveDocPath(options: ResolveOptions) { + const byFile = await resolveByFile(options.file); + if (byFile) { + return byFile; + } + + return resolveByScope(options.scope); +} + +async function resolveByFile(file?: string) { + if (!file) { + return null; + } + + const withoutPrefix = stripKnownPrefixes(file); + const normalized = sanitizePath(withoutPrefix); + if (!normalized) { + return null; + } + + const absolutePath = path.resolve(DOCS_ROOT, normalized); + + if (!absolutePath.startsWith(DOCS_ROOT)) { + return null; + } + + return readIfFile(absolutePath); +} + +async function resolveByScope(scope?: string) { + const normalized = sanitizePath(scope); + + const candidates = buildScopeCandidates(normalized); + + for (const candidate of candidates) { + const absolutePath = path.resolve(DOCS_ROOT, candidate); + + if (!absolutePath.startsWith(DOCS_ROOT)) { + continue; + } + + const file = await readIfFile(absolutePath); + if (file) { + return file; + } + } + + return null; +} + +function buildScopeCandidates(scope?: string) { + if (!scope) { + return ["index.mdx", "index.md"]; + } + + const scopedIndex = path.join(scope, "index"); + + return [ + `${scope}.mdx`, + `${scope}.md`, + `${scope}.markdown`, + `${scope}.mdoc`, + `${scopedIndex}.mdx`, + `${scopedIndex}.md`, + `${scopedIndex}.markdown`, + `${scopedIndex}.mdoc`, + ]; +} + +async function readIfFile(targetPath: string) { + try { + const stats = await fs.stat(targetPath); + + if (stats.isFile()) { + return targetPath; + } + } catch { + // Ignore missing files + } + + return null; +} + +function sanitizePath(raw?: string) { + if (!raw) { + return undefined; + } + + const trimmed = raw.trim(); + if (!trimmed) { + return undefined; + } + + const withoutLeadingSlash = trimmed.replace(/^[/\\]+/, ""); + + const segments = withoutLeadingSlash + .split(/[\\/]+/) + .map((segment) => segment.trim()) + .filter(Boolean); + + if ( + segments.length === 0 || + segments.some( + (segment) => + segment === ".." || segment === "." || segment.includes(".."), + ) + ) { + return undefined; + } + + return segments.join(path.sep); +} + +function stripKnownPrefixes(value: string) { + let trimmed = value.trim(); + + const prefixes = [ + "apps/framework-docs/src/pages/", + "src/pages/", + "pages/", + "./", + ".\\", + ]; + + let prefixApplied = true; + + while (prefixApplied) { + prefixApplied = false; + + for (const prefix of prefixes) { + if (trimmed.startsWith(prefix)) { + trimmed = trimmed.slice(prefix.length); + prefixApplied = true; + break; + } + } + } + + return trimmed; +} diff --git a/apps/framework-docs/theme.config.jsx b/apps/framework-docs/theme.config.jsx index 494dabd95d..24d50f6d68 100644 --- a/apps/framework-docs/theme.config.jsx +++ b/apps/framework-docs/theme.config.jsx @@ -1,3 +1,4 @@ +import { createContext, useContext, useMemo, useRef } from "react"; import { Heading, HeadingLevel } from "@/components/typography"; import { cn } from "@/lib/utils"; @@ -26,7 +27,7 @@ import { useRouter } from "next/router"; import { useConfig, useThemeConfig } from "nextra-theme-docs"; import { PathConfig } from "./src/components/ctas"; import { GitHubStarsButton } from "@/components"; -import { Bot, ChevronDown, FileText } from "lucide-react"; +import { Bot, ChevronDown, Copy, FileText, Sparkles } from "lucide-react"; // Base text styles that match your typography components const baseTextStyles = { @@ -40,6 +41,36 @@ const baseTextStyles = { const DEFAULT_SITE_URL = process.env.NEXT_PUBLIC_SITE_URL || "https://docs.fiveonefour.com"; +const HeadingActionContext = createContext(null); + +function HeadingActionProvider({ children }) { + const usedRef = useRef(false); + const value = useMemo(() => ({ usedRef }), []); + + return ( + + {children} + + ); +} + +function usePrimaryHeadingFlag() { + const context = useContext(HeadingActionContext); + + if (!context) { + throw new Error( + "usePrimaryHeadingFlag must be used within HeadingActionProvider", + ); + } + + if (context.usedRef.current) { + return false; + } + + context.usedRef.current = true; + return true; +} + function normalizePath(asPath) { const safePath = asPath || "/"; const pathWithoutHash = safePath.split("#")[0]; @@ -83,24 +114,54 @@ function buildLlmHref(asPath, suffix) { return `${normalizedPath}/${suffix}`; } -function EditLinks({ filePath, href, className, children }) { - const { pageOpts } = useConfig(); - const { docsRepositoryBase } = useThemeConfig(); - const { asPath } = useRouter(); +function buildLlmPrompt(languageLabel, canonicalPageUrl, docUrl) { + return ( + `I'm looking at the Moose documentation: ${canonicalPageUrl}. ` + + `Use the ${languageLabel} LLM doc for additional context: ${docUrl}. ` + + "Help me understand how to use it. Be ready to explain concepts, give examples, or help debug based on it." + ); +} - const resolvedFilePath = filePath || pageOpts?.filePath; +async function copyTextToClipboard(text) { + if ( + typeof navigator !== "undefined" && + navigator.clipboard && + typeof navigator.clipboard.writeText === "function" + ) { + await navigator.clipboard.writeText(text); + return true; + } - const cleanedRepoBase = - docsRepositoryBase && docsRepositoryBase.endsWith("/") ? - docsRepositoryBase.slice(0, -1) - : docsRepositoryBase; + if (typeof document === "undefined") { + throw new Error("Clipboard API unavailable"); + } - const editHref = - href || - (cleanedRepoBase && resolvedFilePath ? - `${cleanedRepoBase}/${resolvedFilePath}` - : undefined); + const textarea = document.createElement("textarea"); + textarea.value = text; + textarea.setAttribute("readonly", "true"); + textarea.style.position = "fixed"; + textarea.style.opacity = "0"; + textarea.style.pointerEvents = "none"; + document.body.appendChild(textarea); + + try { + textarea.focus(); + textarea.select(); + const successful = document.execCommand("copy"); + if (!successful) { + throw new Error("document.execCommand('copy') returned false"); + } + return true; + } finally { + document.body.removeChild(textarea); + } +} +function LlmHelperMenu({ buttonClassName, align = "start" } = {}) { + const { pageOpts } = useConfig(); + const { asPath } = useRouter(); + + const resolvedFilePath = pageOpts?.filePath; const tsHref = buildLlmHref(asPath, "llm-ts.txt"); const pyHref = buildLlmHref(asPath, "llm-py.txt"); const normalizedPath = normalizePath(asPath); @@ -110,6 +171,25 @@ function EditLinks({ filePath, href, className, children }) { DEFAULT_SITE_URL : resolveAbsoluteUrl(normalizedPath, DEFAULT_SITE_URL); + const scopeParam = + normalizedPath && normalizedPath !== "/" ? + normalizedPath.replace(/^\/+/, "") + : undefined; + + const rawDocParams = new URLSearchParams(); + + if (scopeParam) { + rawDocParams.set("scope", scopeParam); + } + + if (resolvedFilePath) { + rawDocParams.set("file", resolvedFilePath); + } + + const rawDocUrl = `/api/docs/raw${ + rawDocParams.size > 0 ? `?${rawDocParams.toString()}` : "" + }`; + const handleOpenDoc = (target) => () => { if (typeof window === "undefined") { return; @@ -125,10 +205,7 @@ function EditLinks({ filePath, href, className, children }) { } const docUrl = resolveAbsoluteUrl(docTarget, DEFAULT_SITE_URL); - const prompt = - `I'm looking at the Moose documentation: ${canonicalPageUrl}. ` + - `Use the ${languageLabel} LLM doc for additional context: ${docUrl}. ` + - "Help me understand how to use it. Be ready to explain concepts, give examples, or help debug based on it."; + const prompt = buildLlmPrompt(languageLabel, canonicalPageUrl, docUrl); const chatGptUrl = "https://chatgpt.com/?prompt=" + encodeURIComponent(prompt); @@ -136,8 +213,122 @@ function EditLinks({ filePath, href, className, children }) { window.open(chatGptUrl, "_blank", "noopener,noreferrer"); }; + const handleOpenClaude = (languageLabel, docTarget) => async () => { + if (typeof window === "undefined") { + return; + } + + const docUrl = resolveAbsoluteUrl(docTarget, DEFAULT_SITE_URL); + const prompt = buildLlmPrompt(languageLabel, canonicalPageUrl, docUrl); + + try { + await copyTextToClipboard(prompt); + } catch (error) { + console.warn("Failed to copy Claude prompt to clipboard", error); + } + + const claudeUrl = + "https://claude.ai/new?prompt=" + encodeURIComponent(prompt); + window.open(claudeUrl, "_blank", "noopener,noreferrer"); + }; + + const handleCopyMarkdown = async () => { + if (typeof window === "undefined") { + return; + } + + try { + const response = await fetch(rawDocUrl); + if (!response.ok) { + throw new Error(`Failed to fetch markdown: ${response.status}`); + } + + const markdown = await response.text(); + await copyTextToClipboard(markdown); + } catch (error) { + console.error("Failed to copy page markdown", error); + } + }; + + return ( + + + + + + Doc utilities + + + Copy page Markdown + MD + + + View as .txt + + + View TypeScript doc + TS + + + + View Python doc + PY + + + Send to Claude + + + Claude · TypeScript + TS + + + + Claude · Python + PY + + + Send to ChatGPT + + + ChatGPT · TypeScript + TS + + + + ChatGPT · Python + PY + + + + ); +} + +function EditLinks({ filePath, href, className, children }) { + const { pageOpts } = useConfig(); + const { docsRepositoryBase } = useThemeConfig(); + + const resolvedFilePath = filePath || pageOpts?.filePath; + + const cleanedRepoBase = + docsRepositoryBase && docsRepositoryBase.endsWith("/") ? + docsRepositoryBase.slice(0, -1) + : docsRepositoryBase; + + const editHref = + href || + (cleanedRepoBase && resolvedFilePath ? + `${cleanedRepoBase}/${resolvedFilePath}` + : undefined); + return ( -
+
{editHref ? : {children}} - - - - - - LLM docs - - - View TypeScript doc - TS - - - - View Python doc - PY - - - Send to ChatGPT - - - ChatGPT · TypeScript - TS - - - - ChatGPT · Python - PY - - - +
+ ); +} + +function PrimaryHeading({ children, className, ...props }) { + const isPrimary = usePrimaryHeadingFlag(); + + const heading = ( + + {children} + + ); + + if (!isPrimary) { + return heading; + } + + return ( +
+ {heading} +
+ +
); } @@ -325,12 +506,9 @@ export default { navbar: { extraContent: () => , }, - // main: ({ children }) => ( - //
- // {children} - // - //
- // ), + main: ({ children }) => ( + {children} + ), navigation: { prev: true, next: true, @@ -342,9 +520,7 @@ export default { components: { // Heading components with stable rendering h1: ({ children, ...props }) => ( - - {children} - + {children} ), h2: ({ children, ...props }) => ( From 4720f9d036b29402a351f58fa100744ad50bf326 Mon Sep 17 00:00:00 2001 From: Johanan Ottensooser Date: Wed, 29 Oct 2025 14:41:43 -0600 Subject: [PATCH 3/4] move llm docs button --- apps/framework-docs/src/styles/globals.css | 45 +++++++- apps/framework-docs/theme.config.jsx | 128 ++++++++++++--------- 2 files changed, 116 insertions(+), 57 deletions(-) diff --git a/apps/framework-docs/src/styles/globals.css b/apps/framework-docs/src/styles/globals.css index 76fa40da2d..46c333c9a6 100644 --- a/apps/framework-docs/src/styles/globals.css +++ b/apps/framework-docs/src/styles/globals.css @@ -199,6 +199,49 @@ main > p { box-shadow: none; } +.moose-heading-with-actions { + display: flex; + flex-wrap: wrap; + gap: 0.75rem; + align-items: center; +} + +.moose-heading-actions-slot { + display: flex; + flex-grow: 1; + width: 100%; +} + +@media (min-width: 640px) { + .moose-heading-actions-slot { + width: auto; + flex-grow: 0; + margin-left: auto; + } +} + +.moose-heading-actions-menu { + display: flex; + width: 100%; + justify-content: flex-start; +} + +@media (min-width: 640px) { + .moose-heading-actions-menu { + justify-content: flex-end; + } +} + +.moose-heading-actions-button { + width: 100%; +} + +@media (min-width: 640px) { + .moose-heading-actions-button { + width: auto; + } +} + [data-theme="dark"]#logo-path { color: white; } @@ -526,4 +569,4 @@ li:not(:first-child) { .sticky[class*="border"] { border-color: hsl(var(--border)) !important; } -} \ No newline at end of file +} diff --git a/apps/framework-docs/theme.config.jsx b/apps/framework-docs/theme.config.jsx index 24d50f6d68..658f8b5486 100644 --- a/apps/framework-docs/theme.config.jsx +++ b/apps/framework-docs/theme.config.jsx @@ -1,4 +1,4 @@ -import { createContext, useContext, useMemo, useRef } from "react"; +import { useEffect, useState } from "react"; import { Heading, HeadingLevel } from "@/components/typography"; import { cn } from "@/lib/utils"; @@ -28,6 +28,7 @@ import { useConfig, useThemeConfig } from "nextra-theme-docs"; import { PathConfig } from "./src/components/ctas"; import { GitHubStarsButton } from "@/components"; import { Bot, ChevronDown, Copy, FileText, Sparkles } from "lucide-react"; +import { createPortal } from "react-dom"; // Base text styles that match your typography components const baseTextStyles = { @@ -41,36 +42,6 @@ const baseTextStyles = { const DEFAULT_SITE_URL = process.env.NEXT_PUBLIC_SITE_URL || "https://docs.fiveonefour.com"; -const HeadingActionContext = createContext(null); - -function HeadingActionProvider({ children }) { - const usedRef = useRef(false); - const value = useMemo(() => ({ usedRef }), []); - - return ( - - {children} - - ); -} - -function usePrimaryHeadingFlag() { - const context = useContext(HeadingActionContext); - - if (!context) { - throw new Error( - "usePrimaryHeadingFlag must be used within HeadingActionProvider", - ); - } - - if (context.usedRef.current) { - return false; - } - - context.usedRef.current = true; - return true; -} - function normalizePath(asPath) { const safePath = asPath || "/"; const pathWithoutHash = safePath.split("#")[0]; @@ -310,6 +281,69 @@ function LlmHelperMenu({ buttonClassName, align = "start" } = {}) { ); } +function HeadingActionPortal() { + const { asPath } = useRouter(); + const [slot, setSlot] = useState(null); + + useEffect(() => { + if (typeof document === "undefined") { + return; + } + + const article = + document.querySelector("article.nextra-content") || + document.querySelector("article .nextra-content"); + + const heading = + article?.querySelector("h1") || document.querySelector("article h1"); + + if (!heading) { + setSlot(null); + return; + } + + heading.classList.add("moose-heading-with-actions"); + + let existingSlot = heading.querySelector("[data-llm-action-slot]"); + + if (!existingSlot) { + existingSlot = document.createElement("span"); + existingSlot.setAttribute("data-llm-action-slot", "true"); + existingSlot.className = "moose-heading-actions-slot"; + heading.appendChild(existingSlot); + } + + setSlot(existingSlot); + + return () => { + heading.classList.remove("moose-heading-with-actions"); + + if ( + existingSlot && + existingSlot.parentElement === heading && + existingSlot.hasAttribute("data-llm-action-slot") + ) { + existingSlot.remove(); + } + setSlot(null); + }; + }, [asPath]); + + if (!slot) { + return null; + } + + return createPortal( +
+ +
, + slot, + ); +} + function EditLinks({ filePath, href, className, children }) { const { pageOpts } = useConfig(); const { docsRepositoryBase } = useThemeConfig(); @@ -343,29 +377,6 @@ function EditLinks({ filePath, href, className, children }) { ); } -function PrimaryHeading({ children, className, ...props }) { - const isPrimary = usePrimaryHeadingFlag(); - - const heading = ( - - {children} - - ); - - if (!isPrimary) { - return heading; - } - - return ( -
- {heading} -
- -
-
- ); -} - export function Logo() { return ( , }, main: ({ children }) => ( - {children} + <> + + {children} + ), navigation: { prev: true, @@ -520,7 +534,9 @@ export default { components: { // Heading components with stable rendering h1: ({ children, ...props }) => ( - {children} + + {children} + ), h2: ({ children, ...props }) => ( From 2a2072a18650d2fec4102ba608eab8c5c7f7e847 Mon Sep 17 00:00:00 2001 From: Johanan Ottensooser Date: Wed, 29 Oct 2025 19:00:56 -0600 Subject: [PATCH 4/4] t "moved button back, relocate didn't work" --- apps/framework-docs/src/styles/globals.css | 43 ------------- apps/framework-docs/theme.config.jsx | 75 +--------------------- 2 files changed, 3 insertions(+), 115 deletions(-) diff --git a/apps/framework-docs/src/styles/globals.css b/apps/framework-docs/src/styles/globals.css index 46c333c9a6..bfea25a9ba 100644 --- a/apps/framework-docs/src/styles/globals.css +++ b/apps/framework-docs/src/styles/globals.css @@ -199,49 +199,6 @@ main > p { box-shadow: none; } -.moose-heading-with-actions { - display: flex; - flex-wrap: wrap; - gap: 0.75rem; - align-items: center; -} - -.moose-heading-actions-slot { - display: flex; - flex-grow: 1; - width: 100%; -} - -@media (min-width: 640px) { - .moose-heading-actions-slot { - width: auto; - flex-grow: 0; - margin-left: auto; - } -} - -.moose-heading-actions-menu { - display: flex; - width: 100%; - justify-content: flex-start; -} - -@media (min-width: 640px) { - .moose-heading-actions-menu { - justify-content: flex-end; - } -} - -.moose-heading-actions-button { - width: 100%; -} - -@media (min-width: 640px) { - .moose-heading-actions-button { - width: auto; - } -} - [data-theme="dark"]#logo-path { color: white; } diff --git a/apps/framework-docs/theme.config.jsx b/apps/framework-docs/theme.config.jsx index 658f8b5486..668a871337 100644 --- a/apps/framework-docs/theme.config.jsx +++ b/apps/framework-docs/theme.config.jsx @@ -1,4 +1,3 @@ -import { useEffect, useState } from "react"; import { Heading, HeadingLevel } from "@/components/typography"; import { cn } from "@/lib/utils"; @@ -28,7 +27,6 @@ import { useConfig, useThemeConfig } from "nextra-theme-docs"; import { PathConfig } from "./src/components/ctas"; import { GitHubStarsButton } from "@/components"; import { Bot, ChevronDown, Copy, FileText, Sparkles } from "lucide-react"; -import { createPortal } from "react-dom"; // Base text styles that match your typography components const baseTextStyles = { @@ -281,69 +279,6 @@ function LlmHelperMenu({ buttonClassName, align = "start" } = {}) { ); } -function HeadingActionPortal() { - const { asPath } = useRouter(); - const [slot, setSlot] = useState(null); - - useEffect(() => { - if (typeof document === "undefined") { - return; - } - - const article = - document.querySelector("article.nextra-content") || - document.querySelector("article .nextra-content"); - - const heading = - article?.querySelector("h1") || document.querySelector("article h1"); - - if (!heading) { - setSlot(null); - return; - } - - heading.classList.add("moose-heading-with-actions"); - - let existingSlot = heading.querySelector("[data-llm-action-slot]"); - - if (!existingSlot) { - existingSlot = document.createElement("span"); - existingSlot.setAttribute("data-llm-action-slot", "true"); - existingSlot.className = "moose-heading-actions-slot"; - heading.appendChild(existingSlot); - } - - setSlot(existingSlot); - - return () => { - heading.classList.remove("moose-heading-with-actions"); - - if ( - existingSlot && - existingSlot.parentElement === heading && - existingSlot.hasAttribute("data-llm-action-slot") - ) { - existingSlot.remove(); - } - setSlot(null); - }; - }, [asPath]); - - if (!slot) { - return null; - } - - return createPortal( -
- -
, - slot, - ); -} - function EditLinks({ filePath, href, className, children }) { const { pageOpts } = useConfig(); const { docsRepositoryBase } = useThemeConfig(); @@ -362,7 +297,7 @@ function EditLinks({ filePath, href, className, children }) { : undefined); return ( -
+
{editHref ? : {children}} +
); } @@ -517,12 +453,7 @@ export default { navbar: { extraContent: () => , }, - main: ({ children }) => ( - <> - - {children} - - ), + main: ({ children }) => <>{children}, navigation: { prev: true, next: true,