doccupine 0.0.82 → 0.0.83

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -57,23 +57,23 @@ ${hasSections
57
57
  ? ""
58
58
  : `import { Footer } from "@/components/layout/Footer";
59
59
  `}import { Header } from "@/components/layout/Header";
60
- import {
61
- DocsWrapper,
62
- SectionBarProvider,
63
- } from "@/components/layout/DocsComponents";
60
+ import { DocsWrapper } from "@/components/layout/DocsComponents";
64
61
  ${hasSections
65
62
  ? ""
66
- : `import { SideBar } from "@/components/SideBar";
63
+ : `import { SectionBarProvider } from "@/components/layout/DocsComponents";
64
+ import { SideBar } from "@/components/SideBar";
67
65
  import { DocsNavigation } from "@/components/layout/DocsNavigation";
68
- `}import {
69
- transformPagesToGroupedStructure,
70
- type PagesProps,
71
- } from "@/utils/orderNavItems";
66
+ `}import { type PagesProps } from "@/utils/orderNavItems";
67
+ ${hasSections
68
+ ? ""
69
+ : `import { transformPagesToGroupedStructure } from "@/utils/orderNavItems";
70
+ `}
72
71
  ${hasSections
73
72
  ? ""
74
73
  : `import { StaticLinks } from "@/components/layout/StaticLinks";
75
74
  `}import { config } from "@/utils/config";
76
75
  import { verifyBrandingKey } from "@/utils/branding";
76
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
77
77
  import navigation from "@/navigation.json";
78
78
  ${hasSections
79
79
  ? `import { SectionBar } from "@/components/layout/SectionBar";
@@ -84,7 +84,7 @@ import { iconsMdxTemplate } from "../templates/mdx/icons.mdx.js";
84
84
  import { imageAndEmbedsMdxTemplate } from "../templates/mdx/image-and-embeds.mdx.js";
85
85
  import { indexMdxTemplate } from "../templates/mdx/index.mdx.js";
86
86
  import { footerLinksMdxTemplate } from "../templates/mdx/footer-links.mdx.js";
87
- import { listAndTablesMdxTemplate } from "../templates/mdx/list-and-tables.mdx.js";
87
+ import { listsAndTablesMdxTemplate } from "../templates/mdx/lists-and-tables.mdx.js";
88
88
  import { mediaAndAssetsMdxTemplate } from "../templates/mdx/media-and-assets.mdx.js";
89
89
  import { mcpMdxTemplate } from "../templates/mdx/model-context-protocol.mdx.js";
90
90
  import { navigationMdxTemplate } from "../templates/mdx/navigation.mdx.js";
@@ -198,7 +198,7 @@ export const startingDocsStructure = {
198
198
  "image-and-embeds.mdx": imageAndEmbedsMdxTemplate,
199
199
  "index.mdx": indexMdxTemplate,
200
200
  "footer-links.mdx": footerLinksMdxTemplate,
201
- "lists-and-tables.mdx": listAndTablesMdxTemplate,
201
+ "lists-and-tables.mdx": listsAndTablesMdxTemplate,
202
202
  "media-and-assets.mdx": mediaAndAssetsMdxTemplate,
203
203
  "model-context-protocol.mdx": mcpMdxTemplate,
204
204
  "navigation.mdx": navigationMdxTemplate,
@@ -1 +1 @@
1
- export declare const docsTemplate = "import React from \"react\";\nimport { Flex } from \"cherry-styled-components\";\nimport {\n DocsContainer,\n StyledMarkdownContainer,\n StyledMissingComponent,\n} from \"@/components/layout/DocsComponents\";\nimport { MDXRemote } from \"next-mdx-remote/rsc\";\nimport remarkGfm from \"remark-gfm\";\nimport { useMDXComponents } from \"@/components/MDXComponents\";\nimport { DocsSideBar } from \"@/components/DocsSideBar\";\nimport { ActionBar } from \"@/components/layout/ActionBar\";\n\ninterface DocsProps {\n content: string;\n}\n\ninterface Heading {\n id: string;\n text: string;\n level: number;\n}\n\nfunction generateId(text: string): string {\n return text\n .toLowerCase()\n .replace(/[^\\w\\s-]/g, \"\")\n .replace(/\\s+/g, \"-\")\n .trim();\n}\n\nfunction extractHeadings(content: string): Heading[] {\n const contentWithoutCodeBlocks = content.replace(/```[\\s\\S]*?```/g, \"\");\n const headingRegex = /^(#{1,6})\\s+(.+)$/gm;\n const headings: Heading[] = [];\n let match;\n\n while ((match = headingRegex.exec(contentWithoutCodeBlocks)) !== null) {\n const level = match[1].length;\n const text = match[2].trim();\n const id = generateId(text);\n headings.push({ id, text, level });\n }\n\n return headings;\n}\n\nfunction extractComponentNames(source: string): string[] {\n const stripped = source\n .replace(/```[\\s\\S]*?```/g, \"\")\n .replace(/`[^`]*`/g, \"\");\n const tagRegex = /<([A-Z][a-zA-Z0-9]*)/g;\n const names = new Set<string>();\n let match;\n while ((match = tagRegex.exec(stripped)) !== null) {\n names.add(match[1]);\n }\n return Array.from(names);\n}\n\nfunction MissingComponent({\n componentName,\n children,\n}: {\n componentName: string;\n children?: React.ReactNode;\n}) {\n return (\n <StyledMissingComponent>\n Missing component: &lt;{componentName} /&gt;\n </StyledMissingComponent>\n );\n}\n\nfunction Docs({ content }: DocsProps) {\n const headings = extractHeadings(content);\n const components = useMDXComponents({});\n\n const knownNames = Object.keys(components);\n const usedNames = extractComponentNames(content);\n const missingNames = usedNames.filter((name) => !knownNames.includes(name));\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const stubs: Record<string, React.ComponentType<any>> = {};\n for (const name of missingNames) {\n stubs[name] = ({ children }: { children?: React.ReactNode }) => (\n <MissingComponent componentName={name}>{children}</MissingComponent>\n );\n }\n\n const allComponents = { ...components, ...stubs };\n\n return (\n <>\n <DocsContainer>\n <ActionBar content={content}>\n <Flex $gap={20}>\n <StyledMarkdownContainer>\n {content && (\n <MDXRemote\n source={content}\n options={{\n blockJS: false,\n mdxOptions: {\n remarkPlugins: [remarkGfm],\n },\n }}\n components={allComponents}\n />\n )}\n </StyledMarkdownContainer>\n </Flex>\n </ActionBar>\n </DocsContainer>\n <DocsSideBar headings={headings} />\n </>\n );\n}\n\nexport { Docs };\n";
1
+ export declare const docsTemplate = "import React from \"react\";\nimport { Flex } from \"cherry-styled-components\";\nimport {\n DocsContainer,\n StyledMarkdownContainer,\n StyledMissingComponent,\n} from \"@/components/layout/DocsComponents\";\nimport { MDXRemote } from \"next-mdx-remote/rsc\";\nimport remarkGfm from \"remark-gfm\";\nimport { useMDXComponents } from \"@/components/MDXComponents\";\nimport { DocsSideBar } from \"@/components/DocsSideBar\";\nimport { ActionBar } from \"@/components/layout/ActionBar\";\n\ninterface DocsProps {\n content: string;\n}\n\ninterface Heading {\n id: string;\n text: string;\n level: number;\n}\n\nfunction generateId(text: string): string {\n return text\n .toLowerCase()\n .replace(/[^\\w\\s-]/g, \"\")\n .replace(/\\s+/g, \"-\")\n .trim();\n}\n\nfunction extractHeadings(content: string): Heading[] {\n const contentWithoutCodeBlocks = content.replace(/```[\\s\\S]*?```/g, \"\");\n const headingRegex = /^(#{1,6})\\s+(.+)$/gm;\n const headings: Heading[] = [];\n let match;\n\n while ((match = headingRegex.exec(contentWithoutCodeBlocks)) !== null) {\n const level = match[1].length;\n const text = match[2].trim();\n const id = generateId(text);\n headings.push({ id, text, level });\n }\n\n return headings;\n}\n\nfunction extractComponentNames(source: string): string[] {\n const stripped = source\n .replace(/```[\\s\\S]*?```/g, \"\")\n .replace(/`[^`]*`/g, \"\");\n const tagRegex = /<([A-Z][a-zA-Z0-9]*)/g;\n const names = new Set<string>();\n let match;\n while ((match = tagRegex.exec(stripped)) !== null) {\n names.add(match[1]);\n }\n return Array.from(names);\n}\n\nfunction MissingComponent({\n componentName,\n children: _children,\n}: {\n componentName: string;\n children?: React.ReactNode;\n}) {\n return (\n <StyledMissingComponent>\n Missing component: &lt;{componentName} /&gt;\n </StyledMissingComponent>\n );\n}\n\nfunction Docs({ content }: DocsProps) {\n const headings = extractHeadings(content);\n const components = useMDXComponents({});\n\n const knownNames = Object.keys(components);\n const usedNames = extractComponentNames(content);\n const missingNames = usedNames.filter((name) => !knownNames.includes(name));\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const stubs: Record<string, React.ComponentType<any>> = {};\n for (const name of missingNames) {\n stubs[name] = ({ children }: { children?: React.ReactNode }) => (\n <MissingComponent componentName={name}>{children}</MissingComponent>\n );\n }\n\n const allComponents = { ...components, ...stubs };\n\n return (\n <>\n <DocsContainer>\n <ActionBar content={content}>\n <Flex $gap={20}>\n <StyledMarkdownContainer>\n {content && (\n <MDXRemote\n source={content}\n options={{\n blockJS: false,\n mdxOptions: {\n remarkPlugins: [remarkGfm],\n },\n }}\n components={allComponents}\n />\n )}\n </StyledMarkdownContainer>\n </Flex>\n </ActionBar>\n </DocsContainer>\n <DocsSideBar headings={headings} />\n </>\n );\n}\n\nexport { Docs };\n";
@@ -60,7 +60,7 @@ function extractComponentNames(source: string): string[] {
60
60
 
61
61
  function MissingComponent({
62
62
  componentName,
63
- children,
63
+ children: _children,
64
64
  }: {
65
65
  componentName: string;
66
66
  children?: React.ReactNode;
@@ -1 +1 @@
1
- export declare const docsSideBarTemplate = "\"use client\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { Space } from \"cherry-styled-components\";\nimport {\n StyledIndexSidebar,\n StyledIndexSidebarLink,\n StyledIndexSidebarLabel,\n StyledIndexSidebarLi,\n} from \"@/components/layout/DocsComponents\";\n\ninterface Heading {\n id: string;\n text: string;\n level: number;\n}\n\nconst FALLBACK_OFFSET = 60;\n\nfunction getOffset() {\n const header = document.getElementById(\"header\");\n return (header ? header.offsetHeight : FALLBACK_OFFSET) + 20;\n}\n\nexport function DocsSideBar({ headings }: { headings: Heading[] }) {\n const [activeId, setActiveId] = useState<string>(\"\");\n const activeRef = useRef<HTMLLIElement>(null);\n\n const handleScroll = useCallback(() => {\n if (headings.length === 0) return;\n\n const headingElements = headings\n .map((heading) => document.getElementById(heading.id))\n .filter((el): el is HTMLElement => el !== null);\n\n if (headingElements.length === 0) return;\n\n const windowHeight = window.innerHeight;\n\n const visibleHeadings = headingElements.filter((element) => {\n const rect = element.getBoundingClientRect();\n const elementTop = rect.top;\n const elementBottom = rect.bottom;\n return elementTop < windowHeight && elementBottom > -50;\n });\n\n if (visibleHeadings.length > 0) {\n let closestHeading = visibleHeadings[0];\n let closestDistance = Math.abs(\n closestHeading.getBoundingClientRect().top - getOffset(),\n );\n for (const heading of visibleHeadings) {\n const distance = Math.abs(\n heading.getBoundingClientRect().top - getOffset(),\n );\n if (\n distance < closestDistance &&\n heading.getBoundingClientRect().top <= windowHeight * 0.3\n ) {\n closestDistance = distance;\n closestHeading = heading;\n }\n }\n setActiveId(closestHeading.id);\n return;\n }\n\n let currentActiveId = headings[0].id;\n for (const element of headingElements) {\n const rect = element.getBoundingClientRect();\n if (rect.top <= getOffset()) {\n currentActiveId = element.id;\n } else {\n break;\n }\n }\n setActiveId(currentActiveId);\n }, [headings]);\n\n useEffect(() => {\n if (headings.length === 0) return;\n // Set active heading from URL hash immediately on mount\n if (window.location.hash) {\n setActiveId(window.location.hash.slice(1));\n }\n // Run initial scroll check on next frame to avoid synchronous setState in effect\n const rafId = requestAnimationFrame(handleScroll);\n // Re-check after browser finishes scrolling to hash target on new tab/page load\n const delayedId = setTimeout(handleScroll, 300);\n let timeoutId: NodeJS.Timeout;\n const throttledHandleScroll = () => {\n clearTimeout(timeoutId);\n timeoutId = setTimeout(handleScroll, 50);\n };\n window.addEventListener(\"scroll\", throttledHandleScroll);\n window.addEventListener(\"resize\", handleScroll);\n return () => {\n window.removeEventListener(\"scroll\", throttledHandleScroll);\n window.removeEventListener(\"resize\", handleScroll);\n cancelAnimationFrame(rafId);\n clearTimeout(delayedId);\n clearTimeout(timeoutId);\n };\n }, [handleScroll, headings]);\n\n useEffect(() => {\n const el = activeRef.current;\n const container = el?.closest(\"[data-sidebar]\") as HTMLElement | null;\n if (!el || !container) return;\n const elRect = el.getBoundingClientRect();\n const cRect = container.getBoundingClientRect();\n const pad = 140;\n if (elRect.bottom + pad > cRect.bottom) {\n container.scrollBy({\n top: elRect.bottom - cRect.bottom + pad,\n behavior: \"smooth\",\n });\n } else if (elRect.top - pad < cRect.top) {\n container.scrollBy({\n top: elRect.top - cRect.top - pad,\n behavior: \"smooth\",\n });\n }\n }, [activeId]);\n\n const handleHeadingClick = (headingId: string) => {\n const element = document.getElementById(headingId);\n if (element) {\n const elementPosition =\n element.getBoundingClientRect().top + window.scrollY;\n window.scrollTo({\n top: elementPosition - getOffset(),\n behavior: \"smooth\",\n });\n }\n };\n\n return (\n <StyledIndexSidebar data-sidebar>\n {headings?.length > 0 && (\n <>\n <StyledIndexSidebarLabel>On this page</StyledIndexSidebarLabel>\n <Space $size={15} />\n </>\n )}\n {headings.map((heading, index) => (\n <StyledIndexSidebarLi\n key={index}\n ref={activeId === heading.id ? activeRef : null}\n $isActive={activeId === heading.id}\n style={{ paddingLeft: `${(heading.level - 1) * 16}px` }}\n >\n <StyledIndexSidebarLink\n href={`#${heading.id}`}\n onClick={(e) => {\n e.preventDefault();\n handleHeadingClick(heading.id);\n }}\n $isActive={activeId === heading.id}\n >\n {heading.text}\n </StyledIndexSidebarLink>\n </StyledIndexSidebarLi>\n ))}\n </StyledIndexSidebar>\n );\n}\n";
1
+ export declare const docsSideBarTemplate = "\"use client\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { Space } from \"cherry-styled-components\";\nimport {\n StyledIndexSidebar,\n StyledIndexSidebarLink,\n StyledIndexSidebarLabel,\n StyledIndexSidebarLi,\n} from \"@/components/layout/DocsComponents\";\n\ninterface Heading {\n id: string;\n text: string;\n level: number;\n}\n\nconst FALLBACK_OFFSET = 60;\n\nfunction getOffset() {\n const header = document.getElementById(\"header\");\n return (header ? header.offsetHeight : FALLBACK_OFFSET) + 20;\n}\n\nexport function DocsSideBar({ headings }: { headings: Heading[] }) {\n const [activeId, setActiveId] = useState<string>(\"\");\n const activeRef = useRef<HTMLLIElement>(null);\n\n const handleScroll = useCallback(() => {\n if (headings.length === 0) return;\n\n const headingElements = headings\n .map((heading) => document.getElementById(heading.id))\n .filter((el): el is HTMLElement => el !== null);\n\n if (headingElements.length === 0) return;\n\n const windowHeight = window.innerHeight;\n\n const visibleHeadings = headingElements.filter((element) => {\n const rect = element.getBoundingClientRect();\n const elementTop = rect.top;\n const elementBottom = rect.bottom;\n return elementTop < windowHeight && elementBottom > -50;\n });\n\n if (visibleHeadings.length > 0) {\n let closestHeading = visibleHeadings[0];\n let closestDistance = Math.abs(\n closestHeading.getBoundingClientRect().top - getOffset(),\n );\n for (const heading of visibleHeadings) {\n const distance = Math.abs(\n heading.getBoundingClientRect().top - getOffset(),\n );\n if (\n distance < closestDistance &&\n heading.getBoundingClientRect().top <= windowHeight * 0.3\n ) {\n closestDistance = distance;\n closestHeading = heading;\n }\n }\n setActiveId(closestHeading.id);\n return;\n }\n\n let currentActiveId = headings[0].id;\n for (const element of headingElements) {\n const rect = element.getBoundingClientRect();\n if (rect.top <= getOffset()) {\n currentActiveId = element.id;\n } else {\n break;\n }\n }\n setActiveId(currentActiveId);\n }, [headings]);\n\n useEffect(() => {\n if (headings.length === 0) return;\n // Set active heading from URL hash immediately on mount (deferred to next frame)\n const hashId = window.location.hash ? window.location.hash.slice(1) : null;\n // Run initial scroll check on next frame to avoid synchronous setState in effect\n const rafId = requestAnimationFrame(() => {\n if (hashId) {\n setActiveId(hashId);\n }\n handleScroll();\n });\n // Re-check after browser finishes scrolling to hash target on new page load\n const delayedId = setTimeout(handleScroll, 300);\n let timeoutId: NodeJS.Timeout;\n const throttledHandleScroll = () => {\n clearTimeout(timeoutId);\n timeoutId = setTimeout(handleScroll, 50);\n };\n window.addEventListener(\"scroll\", throttledHandleScroll);\n window.addEventListener(\"resize\", handleScroll);\n return () => {\n window.removeEventListener(\"scroll\", throttledHandleScroll);\n window.removeEventListener(\"resize\", handleScroll);\n cancelAnimationFrame(rafId);\n clearTimeout(delayedId);\n clearTimeout(timeoutId);\n };\n }, [handleScroll, headings]);\n\n useEffect(() => {\n const el = activeRef.current;\n const container = el?.closest(\"[data-sidebar]\") as HTMLElement | null;\n if (!el || !container) return;\n const elRect = el.getBoundingClientRect();\n const cRect = container.getBoundingClientRect();\n const pad = 140;\n if (elRect.bottom + pad > cRect.bottom) {\n container.scrollBy({\n top: elRect.bottom - cRect.bottom + pad,\n behavior: \"smooth\",\n });\n } else if (elRect.top - pad < cRect.top) {\n container.scrollBy({\n top: elRect.top - cRect.top - pad,\n behavior: \"smooth\",\n });\n }\n }, [activeId]);\n\n const handleHeadingClick = (headingId: string) => {\n const element = document.getElementById(headingId);\n if (element) {\n const elementPosition =\n element.getBoundingClientRect().top + window.scrollY;\n window.scrollTo({\n top: elementPosition - getOffset(),\n behavior: \"smooth\",\n });\n }\n };\n\n return (\n <StyledIndexSidebar data-sidebar>\n {headings?.length > 0 && (\n <>\n <StyledIndexSidebarLabel>On this page</StyledIndexSidebarLabel>\n <Space $size={15} />\n </>\n )}\n {headings.map((heading, index) => (\n <StyledIndexSidebarLi\n key={index}\n ref={activeId === heading.id ? activeRef : null}\n $isActive={activeId === heading.id}\n style={{ paddingLeft: `${(heading.level - 1) * 16}px` }}\n >\n <StyledIndexSidebarLink\n href={`#${heading.id}`}\n onClick={(e) => {\n e.preventDefault();\n handleHeadingClick(heading.id);\n }}\n $isActive={activeId === heading.id}\n >\n {heading.text}\n </StyledIndexSidebarLink>\n </StyledIndexSidebarLi>\n ))}\n </StyledIndexSidebar>\n );\n}\n";
@@ -78,13 +78,16 @@ export function DocsSideBar({ headings }: { headings: Heading[] }) {
78
78
 
79
79
  useEffect(() => {
80
80
  if (headings.length === 0) return;
81
- // Set active heading from URL hash immediately on mount
82
- if (window.location.hash) {
83
- setActiveId(window.location.hash.slice(1));
84
- }
81
+ // Set active heading from URL hash immediately on mount (deferred to next frame)
82
+ const hashId = window.location.hash ? window.location.hash.slice(1) : null;
85
83
  // Run initial scroll check on next frame to avoid synchronous setState in effect
86
- const rafId = requestAnimationFrame(handleScroll);
87
- // Re-check after browser finishes scrolling to hash target on new tab/page load
84
+ const rafId = requestAnimationFrame(() => {
85
+ if (hashId) {
86
+ setActiveId(hashId);
87
+ }
88
+ handleScroll();
89
+ });
90
+ // Re-check after browser finishes scrolling to hash target on new page load
88
91
  const delayedId = setTimeout(handleScroll, 300);
89
92
  let timeoutId: NodeJS.Timeout;
90
93
  const throttledHandleScroll = () => {
@@ -1 +1 @@
1
- export declare const staticLinksTemplate = "\"use client\";\nimport styled, { css } from \"styled-components\";\nimport { rgba } from \"polished\";\nimport { useContext } from \"react\";\nimport { ChatContext } from \"@/components/Chat\";\nimport { mq, Theme } from \"@/app/theme\";\nimport { interactiveStyles } from \"@/components/layout/SharedStyled\";\nimport { Icon } from \"@/components/layout/Icon\";\nimport linksData from \"@/links.json\";\n\ninterface LinkProps {\n title: string;\n url: string;\n icon?: string;\n}\n\nconst links = linksData as LinkProps[];\n\nconst StyledStaticLinks = styled.div<{ theme: Theme; $isChatOpen: boolean }>`\n display: flex;\n margin: 0 auto;\n transition: all 0.3s ease;\n padding: 0 20px;\n margin-bottom: 20px;\n\n ${mq(\"lg\")} {\n margin: 0;\n padding: 0 300px 20px 300px;\n\n ${({ $isChatOpen }) =>\n $isChatOpen &&\n css`\n padding: 0 440px 20px 300px;\n `}\n }\n`;\n\nconst StyledStaticLinksContent = styled.div`\n max-width: 640px;\n margin: auto 0;\n display: flex;\n gap: 16px;\n flex-wrap: wrap;\n width: 100%;\n margin: 0 auto;\n`;\n\nconst StyledLink = styled.a<{ theme: Theme; $hasIcon?: boolean }>`\n position: relative;\n text-decoration: none;\n font-size: ${({ theme }) => theme.fontSizes.small.lg};\n line-height: 1;\n color: ${({ theme }) =>\n theme.isDark ? theme.colors.primary : theme.colors.primary};\n padding: 0;\n display: flex;\n gap: 6px;\n transition: all 0.3s ease;\n font-weight: 600;\n white-space: nowrap;\n min-width: fit-content;\n background: ${({ theme }) => rgba(theme.colors.primaryLight, 0.1)};\n padding: 6px 8px;\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n ${interactiveStyles};\n\n ${({ $hasIcon }) =>\n $hasIcon &&\n css`\n padding-left: 30px;\n `}\n\n & * {\n margin: auto 0;\n }\n\n & svg {\n position: absolute;\n top: 50%;\n left: 8px;\n transform: translateY(-50%);\n }\n\n &:hover {\n color: ${({ theme }) =>\n theme.isDark ? theme.colors.primaryLight : theme.colors.primaryDark};\n }\n`;\n\nfunction StaticLinks() {\n if (links.length === 0) {\n return null;\n }\n\n const { isOpen } = useContext(ChatContext);\n\n return (\n <>\n <StyledStaticLinks $isChatOpen={isOpen}>\n <StyledStaticLinksContent>\n {links.map((link, index) => (\n <StyledLink\n key={index}\n href={link.url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n $hasIcon={link.icon ? true : false}\n >\n {link.icon && <Icon name={link.icon} size={16} />}\n <span>{link.title}</span>\n </StyledLink>\n ))}\n </StyledStaticLinksContent>\n </StyledStaticLinks>\n </>\n );\n}\n\nexport { StaticLinks };\n";
1
+ export declare const staticLinksTemplate = "\"use client\";\nimport styled, { css } from \"styled-components\";\nimport { rgba } from \"polished\";\nimport { useContext } from \"react\";\nimport { ChatContext } from \"@/components/Chat\";\nimport { mq, Theme } from \"@/app/theme\";\nimport { interactiveStyles } from \"@/components/layout/SharedStyled\";\nimport { Icon } from \"@/components/layout/Icon\";\nimport linksData from \"@/links.json\";\n\ninterface LinkProps {\n title: string;\n url: string;\n icon?: string;\n}\n\nconst links = linksData as LinkProps[];\n\nconst StyledStaticLinks = styled.div<{ theme: Theme; $isChatOpen: boolean }>`\n display: flex;\n margin: 0 auto;\n transition: all 0.3s ease;\n padding: 0 20px;\n margin-bottom: 20px;\n\n ${mq(\"lg\")} {\n margin: 0;\n padding: 0 300px 20px 300px;\n\n ${({ $isChatOpen }) =>\n $isChatOpen &&\n css`\n padding: 0 440px 20px 300px;\n `}\n }\n`;\n\nconst StyledStaticLinksContent = styled.div`\n max-width: 640px;\n margin: auto 0;\n display: flex;\n gap: 16px;\n flex-wrap: wrap;\n width: 100%;\n margin: 0 auto;\n`;\n\nconst StyledLink = styled.a<{ theme: Theme; $hasIcon?: boolean }>`\n position: relative;\n text-decoration: none;\n font-size: ${({ theme }) => theme.fontSizes.small.lg};\n line-height: 1;\n color: ${({ theme }) =>\n theme.isDark ? theme.colors.primary : theme.colors.primary};\n padding: 0;\n display: flex;\n gap: 6px;\n transition: all 0.3s ease;\n font-weight: 600;\n white-space: nowrap;\n min-width: fit-content;\n background: ${({ theme }) => rgba(theme.colors.primaryLight, 0.1)};\n padding: 6px 8px;\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n ${interactiveStyles};\n\n ${({ $hasIcon }) =>\n $hasIcon &&\n css`\n padding-left: 30px;\n `}\n\n & * {\n margin: auto 0;\n }\n\n & svg {\n position: absolute;\n top: 50%;\n left: 8px;\n transform: translateY(-50%);\n }\n\n &:hover {\n color: ${({ theme }) =>\n theme.isDark ? theme.colors.primaryLight : theme.colors.primaryDark};\n }\n`;\n\nfunction StaticLinks() {\n const { isOpen } = useContext(ChatContext);\n\n if (links.length === 0) {\n return null;\n }\n\n return (\n <>\n <StyledStaticLinks $isChatOpen={isOpen}>\n <StyledStaticLinksContent>\n {links.map((link, index) => (\n <StyledLink\n key={index}\n href={link.url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n $hasIcon={link.icon ? true : false}\n >\n {link.icon && <Icon name={link.icon} size={16} />}\n <span>{link.title}</span>\n </StyledLink>\n ))}\n </StyledStaticLinksContent>\n </StyledStaticLinks>\n </>\n );\n}\n\nexport { StaticLinks };\n";
@@ -89,12 +89,12 @@ const StyledLink = styled.a<{ theme: Theme; $hasIcon?: boolean }>\`
89
89
  \`;
90
90
 
91
91
  function StaticLinks() {
92
+ const { isOpen } = useContext(ChatContext);
93
+
92
94
  if (links.length === 0) {
93
95
  return null;
94
96
  }
95
97
 
96
- const { isOpen } = useContext(ChatContext);
97
-
98
98
  return (
99
99
  <>
100
100
  <StyledStaticLinks $isChatOpen={isOpen}>
@@ -0,0 +1 @@
1
+ export declare const listsAndTablesMdxTemplate = "---\ntitle: \"Lists and tables\"\ndescription: \"Present structured information using lists or tables.\"\ndate: \"2026-02-19\"\ncategory: \"Components\"\ncategoryOrder: 1\norder: 2\n---\n# Lists and Tables\nPresent structured information using lists or tables.\n\n## Lists\nMarkdown supports both *ordered* and *unordered* lists, as well as nested list structures.\n\n### Ordered List\nStart each item with a number followed by a period to create an ordered list.\n\n```md\n1. First item\n2. Second item\n3. Third item\n4. Fourth item\n```\n\n1. First item\n2. Second item\n3. Third item\n4. Fourth item\n\n\n### Unordered List\nUse dashes (`-`), asterisks (`*`), or plus signs (`+`) before each item for unordered lists.\n\n```md\n- First item\n- Second item\n- Third item\n- Fourth item\n```\n\n- First item\n- Second item\n- Third item\n- Fourth item\n\n### Nested List\nIndent items under another to create nested lists.\n\n```md\n- First item\n- Second item\n - Additional item\n - Additional item\n- Third item\n```\n\n- First item\n- Second item\n - Additional item\n - Additional item\n- Third item\n\n## Tables\nMarkdown tables use pipes (`|`) to separate columns and hyphens (`---`) to define the header row. Place a pipe at the start and end of each row for better compatibility.\n\n```md\n| Property | Description |\n| -------- | -------------------------------------- |\n| Name | Full name of the user |\n| Age | Age in years |\n| Joined | Indicates if user joined the community |\n```\n\n| Property | Description |\n| -------- | -------------------------------------- |\n| Name | Full name of the user |\n| Age | Age in years |\n| Joined | Indicates if user joined the community |";
@@ -0,0 +1,78 @@
1
+ export const listsAndTablesMdxTemplate = `---
2
+ title: "Lists and tables"
3
+ description: "Present structured information using lists or tables."
4
+ date: "2026-02-19"
5
+ category: "Components"
6
+ categoryOrder: 1
7
+ order: 2
8
+ ---
9
+ # Lists and Tables
10
+ Present structured information using lists or tables.
11
+
12
+ ## Lists
13
+ Markdown supports both *ordered* and *unordered* lists, as well as nested list structures.
14
+
15
+ ### Ordered List
16
+ Start each item with a number followed by a period to create an ordered list.
17
+
18
+ \`\`\`md
19
+ 1. First item
20
+ 2. Second item
21
+ 3. Third item
22
+ 4. Fourth item
23
+ \`\`\`
24
+
25
+ 1. First item
26
+ 2. Second item
27
+ 3. Third item
28
+ 4. Fourth item
29
+
30
+
31
+ ### Unordered List
32
+ Use dashes (\`-\`), asterisks (\`*\`), or plus signs (\`+\`) before each item for unordered lists.
33
+
34
+ \`\`\`md
35
+ - First item
36
+ - Second item
37
+ - Third item
38
+ - Fourth item
39
+ \`\`\`
40
+
41
+ - First item
42
+ - Second item
43
+ - Third item
44
+ - Fourth item
45
+
46
+ ### Nested List
47
+ Indent items under another to create nested lists.
48
+
49
+ \`\`\`md
50
+ - First item
51
+ - Second item
52
+ - Additional item
53
+ - Additional item
54
+ - Third item
55
+ \`\`\`
56
+
57
+ - First item
58
+ - Second item
59
+ - Additional item
60
+ - Additional item
61
+ - Third item
62
+
63
+ ## Tables
64
+ Markdown tables use pipes (\`|\`) to separate columns and hyphens (\`---\`) to define the header row. Place a pipe at the start and end of each row for better compatibility.
65
+
66
+ \`\`\`md
67
+ | Property | Description |
68
+ | -------- | -------------------------------------- |
69
+ | Name | Full name of the user |
70
+ | Age | Age in years |
71
+ | Joined | Indicates if user joined the community |
72
+ \`\`\`
73
+
74
+ | Property | Description |
75
+ | -------- | -------------------------------------- |
76
+ | Name | Full name of the user |
77
+ | Age | Age in years |
78
+ | Joined | Indicates if user joined the community |`;
@@ -1 +1 @@
1
- export declare const navigationMdxTemplate = "---\ntitle: \"Navigation\"\ndescription: \"Organize and structure your navigation.\"\ndate: \"2026-02-19\"\ncategory: \"Configuration\"\ncategoryOrder: 3\norder: 2\n---\n# Navigation\nDoccupine builds your sidebar automatically from your MDX pages. By default, it reads the page frontmatter and groups pages into categories in the order you define. For larger docs, you can take full control with a `navigation.json` file.\n\n## Automatic navigation (default)\nWhen no custom navigation is provided, Doccupine generates a structure based on each page's frontmatter.\n\n### Frontmatter fields\n- **category**: The category name that groups the page in the sidebar.\n- **categoryOrder**: The position of the category within the sidebar. Lower numbers appear first.\n- **order**: The position of the page within its category. Lower numbers appear first.\n\n### Example frontmatter\n\n```text\n---\ntitle: \"Navigation\"\ndescription: \"Organize and structure your navigation.\"\ndate: \"2025-01-15\"\ncategory: \"Configuration\"\ncategoryOrder: 3\norder: 2\n---\n```\n\nThis approach is great for small sets of documents. For larger projects, setting these fields on every page can become repetitive.\n\n## Custom navigation with navigation.json\nTo centrally define the entire sidebar, create a `navigation.json` at your project root (the same folder where you execute `npx doccupine`). When present, it takes priority over page frontmatter and fully controls the navigation structure.\n\n### Array format\nThe simplest format is an array of categories. When using [sections](/sections), this applies to the root section only.\n\n```json\n[\n {\n \"label\": \"General\",\n \"links\": [\n { \"slug\": \"\", \"title\": \"Getting Started\" },\n { \"slug\": \"commands\", \"title\": \"Commands\" }\n ]\n },\n {\n \"label\": \"Components\",\n \"links\": [\n { \"slug\": \"headers-and-text\", \"title\": \"Headers and Text\" },\n { \"slug\": \"lists-and-tables\", \"title\": \"Lists and tables\" },\n { \"slug\": \"code\", \"title\": \"Code\" },\n { \"slug\": \"accordion\", \"title\": \"Accordion\" },\n { \"slug\": \"tabs\", \"title\": \"Tabs\" },\n { \"slug\": \"cards\", \"title\": \"Cards\" },\n { \"slug\": \"buttons\", \"title\": \"Buttons\" },\n { \"slug\": \"callouts\", \"title\": \"Callouts\" },\n { \"slug\": \"image-and-embeds\", \"title\": \"Images and embeds\" },\n { \"slug\": \"icons\", \"title\": \"Icons\" },\n { \"slug\": \"fields\", \"title\": \"Fields\" },\n { \"slug\": \"update\", \"title\": \"Update\" },\n { \"slug\": \"columns\", \"title\": \"Columns\" },\n { \"slug\": \"steps\", \"title\": \"Steps\" }\n ]\n },\n {\n \"label\": \"Configuration\",\n \"links\": [\n { \"slug\": \"globals\", \"title\": \"Globals\" },\n { \"slug\": \"navigation\", \"title\": \"Navigation\" },\n { \"slug\": \"sections\", \"title\": \"Sections\" },\n { \"slug\": \"footer-links\", \"title\": \"Footer Links\" },\n { \"slug\": \"theme\", \"title\": \"Theme\" },\n { \"slug\": \"media-and-assets\", \"title\": \"Media and assets\" },\n { \"slug\": \"fonts\", \"title\": \"Fonts\" },\n { \"slug\": \"ai-assistant\", \"title\": \"AI Assistant\" },\n { \"slug\": \"model-context-protocol\", \"title\": \"Model Context Protocol\" },\n { \"slug\": \"deployment\", \"title\": \"Deployment\" }\n ]\n }\n]\n```\n\n### Object format (per-section)\nWhen using [sections](/sections), you can define navigation for each section by using an object keyed by section slug. Sections without a key fall back to auto-generated navigation from frontmatter.\n\n```json\n{\n \"\": [\n {\n \"label\": \"General\",\n \"links\": [\n { \"slug\": \"\", \"title\": \"Getting Started\" },\n { \"slug\": \"commands\", \"title\": \"Commands\" }\n ]\n }\n ],\n \"platform\": [\n {\n \"label\": \"Overview\",\n \"links\": [\n { \"slug\": \"platform/auth\", \"title\": \"Authentication\" },\n { \"slug\": \"platform/users\", \"title\": \"Users\" }\n ]\n }\n ]\n}\n```\n\nThe key `\"\"` controls the root section. Other keys match section slugs defined in `sections.json` or derived from frontmatter. See [Sections](/sections) for details on configuring sections.\n\n### Fields\n- **label**: The section header shown in the sidebar.\n- **links**: An array of page entries for that section.\n - **slug**: The MDX file slug (filename without extension). Use an empty string `\"\"` for `index.mdx`.\n - **title**: The display title in the navigation. This can differ from the page's `title` frontmatter.\n\n## Precedence and behavior\n\n<Callout type=\"note\">\n `navigation.json` takes priority over frontmatter. If present, it fully controls the sidebar structure for the sections it covers.\n</Callout>\n\n- Without `navigation.json`, the sidebar is built from page frontmatter: `category` -> grouped; `categoryOrder` -> category position; `order` -> page position.\n- When using the object format, sections not listed in `navigation.json` fall back to frontmatter-based navigation.\n- Pages without a `category` appear at the top level.\n\n## Tips\n- **Start simple**: Use frontmatter for small docs. Switch to `navigation.json` as the structure grows.\n- **Keep slugs consistent**: `slug` must match the MDX filename (e.g., `text.mdx` -> `text`).\n- **Control titles**: Use `title` in `navigation.json` to customize sidebar labels without changing page frontmatter.\n- **Per-section navigation**: Use the object format to define different sidebars for each section. Mix and match - define some sections explicitly and let others auto-generate.";
1
+ export declare const navigationMdxTemplate = "---\ntitle: \"Navigation\"\ndescription: \"Organize and structure your navigation.\"\ndate: \"2026-02-19\"\ncategory: \"Configuration\"\ncategoryOrder: 3\norder: 2\n---\n# Navigation\nDoccupine builds your sidebar automatically from your MDX pages. By default, it reads the page frontmatter and groups pages into categories in the order you define. For larger docs, you can take full control with a `navigation.json` file.\n\n## Automatic navigation (default)\nWhen no custom navigation is provided, Doccupine generates a structure based on each page's frontmatter.\n\n### Frontmatter fields\n- **category**: The category name that groups the page in the sidebar.\n- **categoryOrder**: The position of the category within the sidebar. Lower numbers appear first.\n- **order**: The position of the page within its category. Lower numbers appear first.\n\n### Example frontmatter\n\n```text\n---\ntitle: \"Navigation\"\ndescription: \"Organize and structure your navigation.\"\ndate: \"2025-01-15\"\ncategory: \"Configuration\"\ncategoryOrder: 3\norder: 2\n---\n```\n\nThis approach is great for small sets of documents. For larger projects, setting these fields on every page can become repetitive.\n\n## Custom navigation with navigation.json\nTo centrally define the entire sidebar, create a `navigation.json` at your project root (the same folder where you execute `npx doccupine`). When present, it takes priority over page frontmatter and fully controls the navigation structure.\n\n### Array format\nThe simplest format is an array of categories. When using [sections](/sections), this applies to the root section only.\n\n```json\n[\n {\n \"label\": \"Getting Started\",\n \"links\": [\n { \"slug\": \"\", \"title\": \"Introduction\" },\n { \"slug\": \"commands\", \"title\": \"Commands\" }\n ]\n },\n {\n \"label\": \"Components\",\n \"links\": [\n { \"slug\": \"components\", \"title\": \"Components\" },\n { \"slug\": \"headers-and-text\", \"title\": \"Headers and Text\" },\n { \"slug\": \"lists-and-tables\", \"title\": \"Lists and tables\" },\n { \"slug\": \"code\", \"title\": \"Code\" },\n { \"slug\": \"accordion\", \"title\": \"Accordion\" },\n { \"slug\": \"tabs\", \"title\": \"Tabs\" },\n { \"slug\": \"cards\", \"title\": \"Cards\" },\n { \"slug\": \"buttons\", \"title\": \"Buttons\" },\n { \"slug\": \"callouts\", \"title\": \"Callouts\" },\n { \"slug\": \"image-and-embeds\", \"title\": \"Images and embeds\" },\n { \"slug\": \"icons\", \"title\": \"Icons\" },\n { \"slug\": \"fields\", \"title\": \"Fields\" },\n { \"slug\": \"update\", \"title\": \"Update\" },\n { \"slug\": \"columns\", \"title\": \"Columns\" },\n { \"slug\": \"steps\", \"title\": \"Steps\" },\n { \"slug\": \"color-swatches\", \"title\": \"Color Swatches\" }\n ]\n },\n {\n \"label\": \"Configuration\",\n \"links\": [\n { \"slug\": \"globals\", \"title\": \"Globals\" },\n { \"slug\": \"navigation\", \"title\": \"Navigation\" },\n { \"slug\": \"sections\", \"title\": \"Sections\" },\n { \"slug\": \"footer-links\", \"title\": \"Footer Links\" },\n { \"slug\": \"theme\", \"title\": \"Theme\" },\n { \"slug\": \"media-and-assets\", \"title\": \"Media and assets\" },\n { \"slug\": \"fonts\", \"title\": \"Fonts\" },\n { \"slug\": \"ai-assistant\", \"title\": \"AI Assistant\" },\n { \"slug\": \"model-context-protocol\", \"title\": \"Model Context Protocol\" },\n { \"slug\": \"analytics\", \"title\": \"Analytics\" },\n { \"slug\": \"deployment-and-hosting\", \"title\": \"Deployment & Hosting\" }\n ]\n }\n]\n```\n\n### Object format (per-section)\nWhen using [sections](/sections), you can define navigation for each section by using an object keyed by section slug. Sections without a key fall back to auto-generated navigation from frontmatter.\n\n```json\n{\n \"\": [\n {\n \"label\": \"General\",\n \"links\": [\n { \"slug\": \"\", \"title\": \"Getting Started\" },\n { \"slug\": \"commands\", \"title\": \"Commands\" }\n ]\n }\n ],\n \"platform\": [\n {\n \"label\": \"Overview\",\n \"links\": [\n { \"slug\": \"platform/auth\", \"title\": \"Authentication\" },\n { \"slug\": \"platform/users\", \"title\": \"Users\" }\n ]\n }\n ]\n}\n```\n\nThe key `\"\"` controls the root section. Other keys match section slugs defined in `sections.json` or derived from frontmatter. See [Sections](/sections) for details on configuring sections.\n\n### Fields\n- **label**: The section header shown in the sidebar.\n- **links**: An array of page entries for that section.\n - **slug**: The MDX file slug (filename without extension). Use an empty string `\"\"` for `index.mdx`.\n - **title**: The display title in the navigation. This can differ from the page's `title` frontmatter.\n\n## Precedence and behavior\n\n<Callout type=\"note\">\n `navigation.json` takes priority over frontmatter. If present, it fully controls the sidebar structure for the sections it covers.\n</Callout>\n\n- Without `navigation.json`, the sidebar is built from page frontmatter: `category` -> grouped; `categoryOrder` -> category position; `order` -> page position.\n- When using the object format, sections not listed in `navigation.json` fall back to frontmatter-based navigation.\n- Pages without a `category` appear at the top level.\n\n## Tips\n- **Start simple**: Use frontmatter for small docs. Switch to `navigation.json` as the structure grows.\n- **Keep slugs consistent**: `slug` must match the MDX filename (e.g., `text.mdx` -> `text`).\n- **Control titles**: Use `title` in `navigation.json` to customize sidebar labels without changing page frontmatter.\n- **Per-section navigation**: Use the object format to define different sidebars for each section. Mix and match - define some sections explicitly and let others auto-generate.";
@@ -41,15 +41,16 @@ The simplest format is an array of categories. When using [sections](/sections),
41
41
  \`\`\`json
42
42
  [
43
43
  {
44
- "label": "General",
44
+ "label": "Getting Started",
45
45
  "links": [
46
- { "slug": "", "title": "Getting Started" },
46
+ { "slug": "", "title": "Introduction" },
47
47
  { "slug": "commands", "title": "Commands" }
48
48
  ]
49
49
  },
50
50
  {
51
51
  "label": "Components",
52
52
  "links": [
53
+ { "slug": "components", "title": "Components" },
53
54
  { "slug": "headers-and-text", "title": "Headers and Text" },
54
55
  { "slug": "lists-and-tables", "title": "Lists and tables" },
55
56
  { "slug": "code", "title": "Code" },
@@ -63,7 +64,8 @@ The simplest format is an array of categories. When using [sections](/sections),
63
64
  { "slug": "fields", "title": "Fields" },
64
65
  { "slug": "update", "title": "Update" },
65
66
  { "slug": "columns", "title": "Columns" },
66
- { "slug": "steps", "title": "Steps" }
67
+ { "slug": "steps", "title": "Steps" },
68
+ { "slug": "color-swatches", "title": "Color Swatches" }
67
69
  ]
68
70
  },
69
71
  {
@@ -78,7 +80,8 @@ The simplest format is an array of categories. When using [sections](/sections),
78
80
  { "slug": "fonts", "title": "Fonts" },
79
81
  { "slug": "ai-assistant", "title": "AI Assistant" },
80
82
  { "slug": "model-context-protocol", "title": "Model Context Protocol" },
81
- { "slug": "deployment", "title": "Deployment" }
83
+ { "slug": "analytics", "title": "Analytics" },
84
+ { "slug": "deployment-and-hosting", "title": "Deployment & Hosting" }
82
85
  ]
83
86
  }
84
87
  ]
@@ -21,11 +21,11 @@ export const packageJsonTemplate = JSON.stringify({
21
21
  langchain: "^1.2.35",
22
22
  "lucide-react": "^0.577.0",
23
23
  minisearch: "^7.2.0",
24
- next: "16.2.0",
24
+ next: "16.2.1",
25
25
  "next-mdx-remote": "^6.0.0",
26
26
  polished: "^4.3.1",
27
- "posthog-js": "^1.362.0",
28
- "posthog-node": "^5.28.4",
27
+ "posthog-js": "^1.363.1",
28
+ "posthog-node": "^5.28.5",
29
29
  react: "19.2.4",
30
30
  "react-dom": "19.2.4",
31
31
  "rehype-highlight": "^7.0.2",
@@ -42,7 +42,7 @@ export const packageJsonTemplate = JSON.stringify({
42
42
  "@types/react-dom": "^19",
43
43
  "baseline-browser-mapping": "^2.10.9",
44
44
  eslint: "^9",
45
- "eslint-config-next": "16.2.0",
45
+ "eslint-config-next": "16.2.1",
46
46
  prettier: "^3.8.1",
47
47
  typescript: "^5",
48
48
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "doccupine",
3
- "version": "0.0.82",
3
+ "version": "0.0.83",
4
4
  "description": "Free and open-source documentation platform. Write MDX, get a production-ready site with AI chat, built-in components, and an MCP server - in one command.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -39,7 +39,7 @@
39
39
  "commander": "^14.0.3",
40
40
  "fs-extra": "^11.3.4",
41
41
  "gray-matter": "^4.0.3",
42
- "next": "^16.2.0",
42
+ "next": "^16.2.1",
43
43
  "prompts": "^2.4.2",
44
44
  "react": "^19.2.4",
45
45
  "react-dom": "^19.2.4"