doccupine 0.0.67 → 0.0.69
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.
- package/dist/lib/structures.js +0 -2
- package/dist/templates/components/Docs.d.ts +1 -1
- package/dist/templates/components/Docs.js +45 -2
- package/dist/templates/components/layout/Columns.d.ts +1 -1
- package/dist/templates/components/layout/Columns.js +7 -0
- package/dist/templates/components/layout/DocsComponents.d.ts +1 -1
- package/dist/templates/components/layout/DocsComponents.js +13 -0
- package/dist/templates/mdx/media-and-assets.mdx.d.ts +1 -1
- package/dist/templates/mdx/media-and-assets.mdx.js +2 -1
- package/dist/templates/mdx/platform/file-editor.mdx.d.ts +1 -1
- package/dist/templates/mdx/platform/file-editor.mdx.js +7 -1
- package/dist/templates/mdx/platform/navigation-settings.mdx.d.ts +1 -1
- package/dist/templates/mdx/platform/navigation-settings.mdx.js +56 -26
- package/dist/templates/mdx/platform/sections-settings.mdx.d.ts +1 -1
- package/dist/templates/mdx/platform/sections-settings.mdx.js +12 -7
- package/dist/templates/utils/orderNavItems.d.ts +1 -1
- package/dist/templates/utils/orderNavItems.js +7 -2
- package/package.json +1 -1
package/dist/lib/structures.js
CHANGED
|
@@ -93,7 +93,6 @@ import { platformCreatingAProjectMdxTemplate } from "../templates/mdx/platform/c
|
|
|
93
93
|
import { platformSiteSettingsMdxTemplate } from "../templates/mdx/platform/site-settings.mdx.js";
|
|
94
94
|
import { platformThemeSettingsMdxTemplate } from "../templates/mdx/platform/theme-settings.mdx.js";
|
|
95
95
|
import { platformNavigationSettingsMdxTemplate } from "../templates/mdx/platform/navigation-settings.mdx.js";
|
|
96
|
-
import { platformSectionsSettingsMdxTemplate } from "../templates/mdx/platform/sections-settings.mdx.js";
|
|
97
96
|
import { platformFontsSettingsMdxTemplate } from "../templates/mdx/platform/fonts-settings.mdx.js";
|
|
98
97
|
import { platformExternalLinksMdxTemplate } from "../templates/mdx/platform/external-links.mdx.js";
|
|
99
98
|
import { platformAiAssistantMdxTemplate } from "../templates/mdx/platform/ai-assistant.mdx.js";
|
|
@@ -200,7 +199,6 @@ export const startingDocsStructure = {
|
|
|
200
199
|
"platform/site-settings.mdx": platformSiteSettingsMdxTemplate,
|
|
201
200
|
"platform/theme-settings.mdx": platformThemeSettingsMdxTemplate,
|
|
202
201
|
"platform/navigation-settings.mdx": platformNavigationSettingsMdxTemplate,
|
|
203
|
-
"platform/sections-settings.mdx": platformSectionsSettingsMdxTemplate,
|
|
204
202
|
"platform/fonts-settings.mdx": platformFontsSettingsMdxTemplate,
|
|
205
203
|
"platform/external-links.mdx": platformExternalLinksMdxTemplate,
|
|
206
204
|
"platform/ai-assistant.mdx": platformAiAssistantMdxTemplate,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const docsTemplate = "import { Flex } from \"cherry-styled-components\";\nimport {\n DocsContainer,\n StyledMarkdownContainer,\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 Docs({ content }: DocsProps) {\n const headings = extractHeadings(content);\n const components = useMDXComponents({});\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={
|
|
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: <{componentName} />\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,7 +1,9 @@
|
|
|
1
|
-
export const docsTemplate = `import
|
|
1
|
+
export const docsTemplate = `import React from "react";
|
|
2
|
+
import { Flex } from "cherry-styled-components";
|
|
2
3
|
import {
|
|
3
4
|
DocsContainer,
|
|
4
5
|
StyledMarkdownContainer,
|
|
6
|
+
StyledMissingComponent,
|
|
5
7
|
} from "@/components/layout/DocsComponents";
|
|
6
8
|
import { MDXRemote } from "next-mdx-remote/rsc";
|
|
7
9
|
import remarkGfm from "remark-gfm";
|
|
@@ -43,10 +45,51 @@ function extractHeadings(content: string): Heading[] {
|
|
|
43
45
|
return headings;
|
|
44
46
|
}
|
|
45
47
|
|
|
48
|
+
function extractComponentNames(source: string): string[] {
|
|
49
|
+
const stripped = source
|
|
50
|
+
.replace(/\`\`\`[\\s\\S]*?\`\`\`/g, "")
|
|
51
|
+
.replace(/\`[^\`]*\`/g, "");
|
|
52
|
+
const tagRegex = /<([A-Z][a-zA-Z0-9]*)/g;
|
|
53
|
+
const names = new Set<string>();
|
|
54
|
+
let match;
|
|
55
|
+
while ((match = tagRegex.exec(stripped)) !== null) {
|
|
56
|
+
names.add(match[1]);
|
|
57
|
+
}
|
|
58
|
+
return Array.from(names);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function MissingComponent({
|
|
62
|
+
componentName,
|
|
63
|
+
children,
|
|
64
|
+
}: {
|
|
65
|
+
componentName: string;
|
|
66
|
+
children?: React.ReactNode;
|
|
67
|
+
}) {
|
|
68
|
+
return (
|
|
69
|
+
<StyledMissingComponent>
|
|
70
|
+
Missing component: <{componentName} />
|
|
71
|
+
</StyledMissingComponent>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
46
75
|
function Docs({ content }: DocsProps) {
|
|
47
76
|
const headings = extractHeadings(content);
|
|
48
77
|
const components = useMDXComponents({});
|
|
49
78
|
|
|
79
|
+
const knownNames = Object.keys(components);
|
|
80
|
+
const usedNames = extractComponentNames(content);
|
|
81
|
+
const missingNames = usedNames.filter((name) => !knownNames.includes(name));
|
|
82
|
+
|
|
83
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
84
|
+
const stubs: Record<string, React.ComponentType<any>> = {};
|
|
85
|
+
for (const name of missingNames) {
|
|
86
|
+
stubs[name] = ({ children }: { children?: React.ReactNode }) => (
|
|
87
|
+
<MissingComponent componentName={name}>{children}</MissingComponent>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const allComponents = { ...components, ...stubs };
|
|
92
|
+
|
|
50
93
|
return (
|
|
51
94
|
<>
|
|
52
95
|
<DocsContainer>
|
|
@@ -62,7 +105,7 @@ function Docs({ content }: DocsProps) {
|
|
|
62
105
|
remarkPlugins: [remarkGfm],
|
|
63
106
|
},
|
|
64
107
|
}}
|
|
65
|
-
components={
|
|
108
|
+
components={allComponents}
|
|
66
109
|
/>
|
|
67
110
|
)}
|
|
68
111
|
</StyledMarkdownContainer>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const columnsTemplate = "\"use client\";\nimport styled from \"styled-components\";\nimport { mq, Theme } from \"cherry-styled-components\";\n\nconst StyledColumns = styled.div<{ theme: Theme; $columns?: number }>`\n display: flex;\n flex-direction: column;\n gap: 20px;\n\n ${mq(\"lg\")} {\n display: grid;\n grid-template-columns: repeat(${({ $columns }) => $columns ?? 1}, 1fr);\n }\n`;\n\ninterface ColumnsProps extends React.HTMLAttributes<HTMLDivElement> {\n children: React.ReactNode;\n cols?: number;\n}\n\nfunction Columns({ children, cols }: ColumnsProps) {\n return <StyledColumns $columns={cols}>{children}</StyledColumns>;\n}\n\nexport { Columns };\n";
|
|
1
|
+
export declare const columnsTemplate = "\"use client\";\nimport styled from \"styled-components\";\nimport { mq, Theme } from \"cherry-styled-components\";\n\nconst StyledColumns = styled.div<{ theme: Theme; $columns?: number }>`\n display: flex;\n flex-direction: column;\n gap: 20px;\n\n ${mq(\"lg\")} {\n ${({ $columns }) =>\n $columns && $columns >= 3\n ? `display: grid; grid-template-columns: repeat(2, 1fr);`\n : \"\"}\n }\n\n ${mq(\"xl\")} {\n display: grid;\n grid-template-columns: repeat(${({ $columns }) => $columns ?? 1}, 1fr);\n }\n`;\n\ninterface ColumnsProps extends React.HTMLAttributes<HTMLDivElement> {\n children: React.ReactNode;\n cols?: number;\n}\n\nfunction Columns({ children, cols }: ColumnsProps) {\n return <StyledColumns $columns={cols}>{children}</StyledColumns>;\n}\n\nexport { Columns };\n";
|
|
@@ -8,6 +8,13 @@ const StyledColumns = styled.div<{ theme: Theme; $columns?: number }>\`
|
|
|
8
8
|
gap: 20px;
|
|
9
9
|
|
|
10
10
|
\${mq("lg")} {
|
|
11
|
+
\${({ $columns }) =>
|
|
12
|
+
$columns && $columns >= 3
|
|
13
|
+
? \`display: grid; grid-template-columns: repeat(2, 1fr);\`
|
|
14
|
+
: ""}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
\${mq("xl")} {
|
|
11
18
|
display: grid;
|
|
12
19
|
grid-template-columns: repeat(\${({ $columns }) => $columns ?? 1}, 1fr);
|
|
13
20
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const docsComponentsTemplate = "\"use client\";\nimport { darken, lighten, rgba } from \"polished\";\nimport React, { createContext, useContext } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport {\n resetButton,\n styledSmall,\n styledStrong,\n styledText,\n} from \"cherry-styled-components\";\nimport Link from \"next/link\";\nimport { mq, Theme } from \"@/app/theme\";\nimport {\n styledAnchor,\n styledTable,\n stylesLists,\n} from \"@/components/layout/SharedStyled\";\nimport { ChatContext } from \"@/components/Chat\";\n\nconst SectionBarContext = createContext(false);\n\nfunction SectionBarProvider({\n hasSectionBar,\n children,\n}: {\n hasSectionBar: boolean;\n children: React.ReactNode;\n}) {\n return (\n <SectionBarContext.Provider value={hasSectionBar}>\n {children}\n </SectionBarContext.Provider>\n );\n}\n\ninterface DocsProps {\n children: React.ReactNode;\n}\n\nconst StyledDocsWrapper = styled.div<{ theme: Theme }>`\n position: relative;\n`;\n\nconst StyledDocsSidebar = styled.div<{ theme: Theme }>`\n clear: both;\n`;\n\nconst StyledDocsContainer = styled.div<{ theme: Theme; $isChatOpen?: boolean }>`\n position: relative;\n padding: 0 20px 100px 20px;\n width: 100%;\n ${({ theme }) => styledText(theme)};\n transition: all 0.3s ease;\n\n ${mq(\"lg\")} {\n padding: 0 300px 80px 300px;\n\n ${({ $isChatOpen }) =>\n $isChatOpen &&\n css`\n padding: 0 440px 80px 300px;\n `}\n }\n\n & p {\n color: ${({ theme }) => theme.colors.grayDark};\n hyphens: auto;\n }\n\n & pre {\n max-width: 100%;\n }\n\n ${styledAnchor};\n ${stylesLists};\n ${styledTable};\n\n & img,\n & video,\n & iframe {\n max-width: 100%;\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n }\n\n & code:not([class]) {\n background: ${({ theme }) => rgba(theme.colors.primaryLight, 0.2)};\n color: ${({ theme }) => theme.colors.dark};\n padding: 2px 4px;\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n white-space: pre;\n }\n\n & .lucide {\n color: ${({ theme }) => theme.colors.primary};\n }\n\n & .aspect-video {\n aspect-ratio: 16 / 9;\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n }\n`;\n\nexport const StyledMarkdownContainer = styled.div`\n display: flex;\n flex-direction: column;\n gap: 20px;\n flex-wrap: wrap;\n flex: 1;\n max-width: 640px;\n margin: auto;\n`;\n\ninterface Props {\n theme?: Theme;\n $isActive?: boolean;\n $hasSectionBar?: boolean;\n}\n\nexport const StyledSidebar = styled.nav<Props>`\n position: fixed;\n overflow-y: auto;\n max-height: calc(\n 100dvh - ${({ $hasSectionBar }) => ($hasSectionBar ? 104 : 62)}px\n );\n width: 100%;\n z-index: 99;\n top: ${({ $hasSectionBar }) => ($hasSectionBar ? 104 : 62)}px;\n height: 100%;\n padding: 20px 20px 80px 20px;\n opacity: 0;\n pointer-events: none;\n transition: all 0.3s ease;\n transform: translateY(30px);\n left: 0;\n background: ${({ theme }) => theme.colors.light};\n -webkit-overflow-scrolling: touch;\n\n &::-webkit-scrollbar {\n display: none;\n }\n\n ${mq(\"lg\")} {\n border-right: solid 1px ${({ theme }) => theme.colors.grayLight};\n transition: none;\n max-height: 100dvh;\n width: 220px;\n background: transparent;\n padding: 82px 20px 20px 20px;\n opacity: 1;\n pointer-events: all;\n transform: translateY(0);\n background: ${({ theme }) => rgba(theme.colors.primaryLight, 0.05)};\n top: 0;\n width: 280px;\n }\n\n ${({ $isActive }) =>\n $isActive &&\n css`\n transform: translateY(0);\n opacity: 1;\n pointer-events: all;\n `}\n`;\n\nexport const StyledIndexSidebar = styled.ul<{ theme: Theme }>`\n display: none;\n list-style: none;\n margin: 0;\n padding: 0;\n position: fixed;\n top: 0;\n right: 0;\n width: 280px;\n height: 100dvh;\n overflow-y: auto;\n z-index: 1;\n padding: 82px 20px 20px 20px;\n background: ${({ theme }) => theme.colors.light};\n border-left: solid 1px ${({ theme }) => theme.colors.grayLight};\n -webkit-overflow-scrolling: touch;\n\n &::-webkit-scrollbar {\n display: none;\n }\n\n ${mq(\"lg\")} {\n display: block;\n }\n\n & li {\n padding: 5px 0;\n }\n`;\n\nexport const StyledIndexSidebarLabel = styled.span<{ theme: Theme }>`\n ${({ theme }) => styledSmall(theme)};\n color: ${({ theme }) => theme.colors.grayDark};\n`;\n\nexport const StyledIndexSidebarLi = styled.li<{\n theme: Theme;\n $isActive: boolean;\n}>`\n &::before {\n content: \"\";\n display: block;\n position: absolute;\n left: 0;\n height: 20px;\n width: 1px;\n background: transparent;\n transition: all 0.3s ease;\n }\n\n ${({ $isActive, theme }) =>\n $isActive &&\n css`\n &::before {\n background: ${theme.colors.primary};\n }\n `}\n`;\n\nexport const StyledIndexSidebarLink = styled.a<{\n theme: Theme;\n $isActive: boolean;\n}>`\n ${({ theme }) => styledSmall(theme)};\n color: ${({ theme, $isActive }) =>\n $isActive ? theme.colors.primary : theme.colors.dark};\n font-weight: ${({ $isActive }) => ($isActive ? \"600\" : \"400\")};\n text-decoration: none;\n transition: all 0.3s ease;\n\n &:hover {\n color: ${({ theme }) => theme.colors.primary};\n }\n`;\n\nexport const StyledSidebarList = styled.ul`\n list-style: none;\n margin: 0;\n padding: 0;\n`;\n\nexport const StyledStrong = styled.strong<{ theme: Theme }>`\n font-weight: 600;\n ${({ theme }) => styledStrong(theme)};\n color: ${({ theme }) =>\n theme.isDark\n ? lighten(0.1, theme.colors.primaryLight)\n : darken(0.1, theme.colors.primaryDark)};\n`;\n\nexport const StyledSidebarListItem = styled.li`\n display: flex;\n gap: 10px;\n clear: both;\n`;\n\nexport const StyledSidebarListItemLink = styled(Link)<Props>`\n text-decoration: none;\n font-size: ${({ theme }) => theme.fontSizes.small.lg};\n line-height: 1.6;\n color: ${({ theme }) =>\n theme.isDark ? theme.colors.grayDark : theme.colors.primary};\n padding: 5px 0 5px 20px;\n display: flex;\n transition: all 0.3s ease;\n border-left: solid 1px ${({ theme }) => theme.colors.grayLight};\n\n &:hover {\n color: ${({ theme }) =>\n theme.isDark ? theme.colors.primaryLight : theme.colors.primaryDark};\n border-color: ${({ theme }) => theme.colors.primary};\n }\n\n ${({ $isActive, theme }) =>\n $isActive &&\n `\n\t\t\tcolor: ${theme.isDark ? lighten(0.1, theme.colors.primaryLight) : darken(0.1, theme.colors.primaryDark)};\n\t\t\tborder-color: ${theme.colors.primary};\n\t\t\tfont-weight: 600;\n\t`};\n`;\n\nexport const StyleMobileBar = styled.button<Props>`\n ${resetButton};\n position: fixed;\n z-index: 999;\n bottom: 0;\n right: 20px;\n font-size: ${({ theme }) => theme.fontSizes.strong.lg};\n line-height: ${({ theme }) => theme.fontSizes.strong.lg};\n box-shadow: ${({ theme }) => theme.shadows.sm};\n background: ${({ theme }) =>\n theme.isDark\n ? rgba(theme.colors.grayLight, 0.7)\n : rgba(theme.colors.light, 0.7)};\n color: ${({ theme }) =>\n theme.isDark ? theme.colors.dark : theme.colors.primary};\n backdrop-filter: blur(10px);\n -webkit-backdrop-filter: blur(10px);\n padding: 20px;\n border-radius: 100px;\n margin: 0 0 20px 0;\n font-weight: 600;\n display: flex;\n justify-content: flex-start;\n width: auto;\n\n ${mq(\"lg\")} {\n display: none;\n }\n\n ${({ $isActive }) => $isActive && `position: fixed;`};\n`;\n\nexport const StyledMobileBurger = styled.span<Props>`\n display: block;\n margin: auto 0;\n width: 18px;\n height: 18px;\n position: relative;\n overflow: hidden;\n background: transparent;\n position: relative;\n\n &::before,\n &::after {\n content: \"\";\n display: block;\n position: absolute;\n width: 18px;\n height: 3px;\n border-radius: 3px;\n background: ${({ theme }) =>\n theme.isDark ? theme.colors.dark : theme.colors.primary};\n transition: all 0.3s ease;\n }\n\n &::before {\n top: 3px;\n }\n\n &::after {\n bottom: 3px;\n }\n\n ${({ $isActive }) =>\n $isActive &&\n css`\n &::before {\n transform: translateY(5px) rotate(45deg);\n }\n\n &::after {\n transform: translateY(-4px) rotate(-45deg);\n }\n `};\n`;\n\ninterface DocsWrapperProps {\n children: React.ReactNode;\n}\n\nfunction DocsWrapper({ children }: DocsWrapperProps) {\n return <StyledDocsWrapper>{children}</StyledDocsWrapper>;\n}\n\nfunction DocsSidebar({ children }: DocsProps) {\n return <StyledDocsSidebar>{children}</StyledDocsSidebar>;\n}\n\nfunction DocsContainer({ children }: DocsProps) {\n const { isOpen } = useContext(ChatContext);\n\n return (\n <StyledDocsContainer $isChatOpen={isOpen}>{children}</StyledDocsContainer>\n );\n}\n\nexport {\n DocsWrapper,\n DocsSidebar,\n DocsContainer,\n SectionBarContext,\n SectionBarProvider,\n};\n";
|
|
1
|
+
export declare const docsComponentsTemplate = "\"use client\";\nimport { darken, lighten, rgba } from \"polished\";\nimport React, { createContext, useContext } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport {\n resetButton,\n styledSmall,\n styledStrong,\n styledText,\n} from \"cherry-styled-components\";\nimport Link from \"next/link\";\nimport { mq, Theme } from \"@/app/theme\";\nimport {\n styledAnchor,\n styledTable,\n stylesLists,\n} from \"@/components/layout/SharedStyled\";\nimport { ChatContext } from \"@/components/Chat\";\n\nconst SectionBarContext = createContext(false);\n\nfunction SectionBarProvider({\n hasSectionBar,\n children,\n}: {\n hasSectionBar: boolean;\n children: React.ReactNode;\n}) {\n return (\n <SectionBarContext.Provider value={hasSectionBar}>\n {children}\n </SectionBarContext.Provider>\n );\n}\n\ninterface DocsProps {\n children: React.ReactNode;\n}\n\nconst StyledDocsWrapper = styled.div<{ theme: Theme }>`\n position: relative;\n`;\n\nconst StyledDocsSidebar = styled.div<{ theme: Theme }>`\n clear: both;\n`;\n\nconst StyledDocsContainer = styled.div<{ theme: Theme; $isChatOpen?: boolean }>`\n position: relative;\n padding: 0 20px 100px 20px;\n width: 100%;\n ${({ theme }) => styledText(theme)};\n transition: all 0.3s ease;\n\n ${mq(\"lg\")} {\n padding: 0 300px 80px 300px;\n\n ${({ $isChatOpen }) =>\n $isChatOpen &&\n css`\n padding: 0 440px 80px 300px;\n `}\n }\n\n & p {\n color: ${({ theme }) => theme.colors.grayDark};\n hyphens: auto;\n }\n\n & pre {\n max-width: 100%;\n }\n\n ${styledAnchor};\n ${stylesLists};\n ${styledTable};\n\n & img,\n & video,\n & iframe {\n max-width: 100%;\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n }\n\n & code:not([class]) {\n background: ${({ theme }) => rgba(theme.colors.primaryLight, 0.2)};\n color: ${({ theme }) => theme.colors.dark};\n padding: 2px 4px;\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n white-space: pre;\n }\n\n & .lucide {\n color: ${({ theme }) => theme.colors.primary};\n }\n\n & .aspect-video {\n aspect-ratio: 16 / 9;\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n }\n`;\n\nexport const StyledMarkdownContainer = styled.div`\n display: flex;\n flex-direction: column;\n gap: 20px;\n flex-wrap: wrap;\n flex: 1;\n max-width: 640px;\n margin: auto;\n`;\n\ninterface Props {\n theme?: Theme;\n $isActive?: boolean;\n $hasSectionBar?: boolean;\n}\n\nexport const StyledSidebar = styled.nav<Props>`\n position: fixed;\n overflow-y: auto;\n max-height: calc(\n 100dvh - ${({ $hasSectionBar }) => ($hasSectionBar ? 104 : 62)}px\n );\n width: 100%;\n z-index: 99;\n top: ${({ $hasSectionBar }) => ($hasSectionBar ? 104 : 62)}px;\n height: 100%;\n padding: 20px 20px 80px 20px;\n opacity: 0;\n pointer-events: none;\n transition: all 0.3s ease;\n transform: translateY(30px);\n left: 0;\n background: ${({ theme }) => theme.colors.light};\n -webkit-overflow-scrolling: touch;\n\n &::-webkit-scrollbar {\n display: none;\n }\n\n ${mq(\"lg\")} {\n border-right: solid 1px ${({ theme }) => theme.colors.grayLight};\n transition: none;\n max-height: 100dvh;\n width: 220px;\n background: transparent;\n padding: 82px 20px 20px 20px;\n opacity: 1;\n pointer-events: all;\n transform: translateY(0);\n background: ${({ theme }) => rgba(theme.colors.primaryLight, 0.05)};\n top: 0;\n width: 280px;\n }\n\n ${({ $isActive }) =>\n $isActive &&\n css`\n transform: translateY(0);\n opacity: 1;\n pointer-events: all;\n `}\n`;\n\nexport const StyledIndexSidebar = styled.ul<{ theme: Theme }>`\n display: none;\n list-style: none;\n margin: 0;\n padding: 0;\n position: fixed;\n top: 0;\n right: 0;\n width: 280px;\n height: 100dvh;\n overflow-y: auto;\n z-index: 1;\n padding: 82px 20px 20px 20px;\n background: ${({ theme }) => theme.colors.light};\n border-left: solid 1px ${({ theme }) => theme.colors.grayLight};\n -webkit-overflow-scrolling: touch;\n\n &::-webkit-scrollbar {\n display: none;\n }\n\n ${mq(\"lg\")} {\n display: block;\n }\n\n & li {\n padding: 5px 0;\n }\n`;\n\nexport const StyledIndexSidebarLabel = styled.span<{ theme: Theme }>`\n ${({ theme }) => styledSmall(theme)};\n color: ${({ theme }) => theme.colors.grayDark};\n`;\n\nexport const StyledIndexSidebarLi = styled.li<{\n theme: Theme;\n $isActive: boolean;\n}>`\n &::before {\n content: \"\";\n display: block;\n position: absolute;\n left: 0;\n height: 20px;\n width: 1px;\n background: transparent;\n transition: all 0.3s ease;\n }\n\n ${({ $isActive, theme }) =>\n $isActive &&\n css`\n &::before {\n background: ${theme.colors.primary};\n }\n `}\n`;\n\nexport const StyledIndexSidebarLink = styled.a<{\n theme: Theme;\n $isActive: boolean;\n}>`\n ${({ theme }) => styledSmall(theme)};\n color: ${({ theme, $isActive }) =>\n $isActive ? theme.colors.primary : theme.colors.dark};\n font-weight: ${({ $isActive }) => ($isActive ? \"600\" : \"400\")};\n text-decoration: none;\n transition: all 0.3s ease;\n\n &:hover {\n color: ${({ theme }) => theme.colors.primary};\n }\n`;\n\nexport const StyledSidebarList = styled.ul`\n list-style: none;\n margin: 0;\n padding: 0;\n`;\n\nexport const StyledStrong = styled.strong<{ theme: Theme }>`\n font-weight: 600;\n ${({ theme }) => styledStrong(theme)};\n color: ${({ theme }) =>\n theme.isDark\n ? lighten(0.1, theme.colors.primaryLight)\n : darken(0.1, theme.colors.primaryDark)};\n`;\n\nexport const StyledSidebarListItem = styled.li`\n display: flex;\n gap: 10px;\n clear: both;\n`;\n\nexport const StyledSidebarListItemLink = styled(Link)<Props>`\n text-decoration: none;\n font-size: ${({ theme }) => theme.fontSizes.small.lg};\n line-height: 1.6;\n color: ${({ theme }) =>\n theme.isDark ? theme.colors.grayDark : theme.colors.primary};\n padding: 5px 0 5px 20px;\n display: flex;\n transition: all 0.3s ease;\n border-left: solid 1px ${({ theme }) => theme.colors.grayLight};\n\n &:hover {\n color: ${({ theme }) =>\n theme.isDark ? theme.colors.primaryLight : theme.colors.primaryDark};\n border-color: ${({ theme }) => theme.colors.primary};\n }\n\n ${({ $isActive, theme }) =>\n $isActive &&\n `\n\t\t\tcolor: ${theme.isDark ? lighten(0.1, theme.colors.primaryLight) : darken(0.1, theme.colors.primaryDark)};\n\t\t\tborder-color: ${theme.colors.primary};\n\t\t\tfont-weight: 600;\n\t`};\n`;\n\nexport const StyleMobileBar = styled.button<Props>`\n ${resetButton};\n position: fixed;\n z-index: 999;\n bottom: 0;\n right: 20px;\n font-size: ${({ theme }) => theme.fontSizes.strong.lg};\n line-height: ${({ theme }) => theme.fontSizes.strong.lg};\n box-shadow: ${({ theme }) => theme.shadows.sm};\n background: ${({ theme }) =>\n theme.isDark\n ? rgba(theme.colors.grayLight, 0.7)\n : rgba(theme.colors.light, 0.7)};\n color: ${({ theme }) =>\n theme.isDark ? theme.colors.dark : theme.colors.primary};\n backdrop-filter: blur(10px);\n -webkit-backdrop-filter: blur(10px);\n padding: 20px;\n border-radius: 100px;\n margin: 0 0 20px 0;\n font-weight: 600;\n display: flex;\n justify-content: flex-start;\n width: auto;\n\n ${mq(\"lg\")} {\n display: none;\n }\n\n ${({ $isActive }) => $isActive && `position: fixed;`};\n`;\n\nexport const StyledMobileBurger = styled.span<Props>`\n display: block;\n margin: auto 0;\n width: 18px;\n height: 18px;\n position: relative;\n overflow: hidden;\n background: transparent;\n position: relative;\n\n &::before,\n &::after {\n content: \"\";\n display: block;\n position: absolute;\n width: 18px;\n height: 3px;\n border-radius: 3px;\n background: ${({ theme }) =>\n theme.isDark ? theme.colors.dark : theme.colors.primary};\n transition: all 0.3s ease;\n }\n\n &::before {\n top: 3px;\n }\n\n &::after {\n bottom: 3px;\n }\n\n ${({ $isActive }) =>\n $isActive &&\n css`\n &::before {\n transform: translateY(5px) rotate(45deg);\n }\n\n &::after {\n transform: translateY(-4px) rotate(-45deg);\n }\n `};\n`;\n\nexport const StyledMissingComponent = styled.div`\n background: ${({ theme }) => theme.colors.error};\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n padding: 20px;\n font-size: ${({ theme }) => theme.fontSizes.small.lg};\n color: ${({ theme }) =>\n theme.isDark ? theme.colors.dark : theme.colors.light};\n font-weight: 600;\n display: flex;\n gap: 10px;\n align-items: center;\n`;\n\ninterface DocsWrapperProps {\n children: React.ReactNode;\n}\n\nfunction DocsWrapper({ children }: DocsWrapperProps) {\n return <StyledDocsWrapper>{children}</StyledDocsWrapper>;\n}\n\nfunction DocsSidebar({ children }: DocsProps) {\n return <StyledDocsSidebar>{children}</StyledDocsSidebar>;\n}\n\nfunction DocsContainer({ children }: DocsProps) {\n const { isOpen } = useContext(ChatContext);\n\n return (\n <StyledDocsContainer $isChatOpen={isOpen}>{children}</StyledDocsContainer>\n );\n}\n\nexport {\n DocsWrapper,\n DocsSidebar,\n DocsContainer,\n SectionBarContext,\n SectionBarProvider,\n};\n";
|
|
@@ -362,6 +362,19 @@ export const StyledMobileBurger = styled.span<Props>\`
|
|
|
362
362
|
\`};
|
|
363
363
|
\`;
|
|
364
364
|
|
|
365
|
+
export const StyledMissingComponent = styled.div\`
|
|
366
|
+
background: \${({ theme }) => theme.colors.error};
|
|
367
|
+
border-radius: \${({ theme }) => theme.spacing.radius.lg};
|
|
368
|
+
padding: 20px;
|
|
369
|
+
font-size: \${({ theme }) => theme.fontSizes.small.lg};
|
|
370
|
+
color: \${({ theme }) =>
|
|
371
|
+
theme.isDark ? theme.colors.dark : theme.colors.light};
|
|
372
|
+
font-weight: 600;
|
|
373
|
+
display: flex;
|
|
374
|
+
gap: 10px;
|
|
375
|
+
align-items: center;
|
|
376
|
+
\`;
|
|
377
|
+
|
|
365
378
|
interface DocsWrapperProps {
|
|
366
379
|
children: React.ReactNode;
|
|
367
380
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const mediaAndAssetsMdxTemplate = "---\ntitle: \"Media and assets\"\ndescription: \"Serve static files like images, favicons, fonts, and Open Graph previews from the public directory.\"\ndate: \"2026-02-19\"\ncategory: \"Configuration\"\ncategoryOrder: 3\norder: 6\n---\n# Media and assets\nDoccupine watches a `public` directory in your project root (the same folder where you execute `npx doccupine`) and copies its contents into the generated Next.js `public` folder. Use it to serve static files such as favicons, Open Graph preview images, custom fonts, or any other media your documentation needs.\n\n## The public directory\nCreate a `public` folder at your project root. Any file you place inside is automatically synced to the generated site and served from the root URL path.\n\n```text\nmy-docs/\n\u251C\u2500\u2500 public/\n\u2502 \u251C\u2500\u2500 favicon.ico\n\u2502 \u251C\u2500\u2500 og-image.png\n\u2502 \u251C\u2500\u2500 logo.svg\n\u2502 \u2514\u2500\u2500 fonts/\n\u2502 \u2514\u2500\u2500 custom-font.woff2\n\u251C\u2500\u2500 index.mdx\n\u251C\u2500\u2500 config.json\n\u2514\u2500\u2500 theme.json\n```\n\n## How assets are served\nFiles in the `public` directory are available at the root of your deployed domain. The path inside `public` maps directly to the URL path.\n\n| File | URL |\n|-------------------------------|--------------------------------------------|\n| `public/favicon.ico` | `https://your-domain.com/favicon.ico` |\n| `public/og-image.png` | `https://your-domain.com/og-image.png` |\n| `public/logo.svg` | `https://your-domain.com/logo.svg` |\n| `public/fonts/custom-font.woff2` | `https://your-domain.com/fonts/custom-font.woff2` |\n\n## Common use cases\n\n### Favicon\nDrop a `favicon.ico` into the `public` folder. Browsers pick it up automatically from the root path.\n\n```text\npublic/favicon.ico \u2192 https://your-domain.com/favicon.ico\n```\n\n### Open Graph preview image\nAdd an image for link previews on social media. Reference it in your `config.json` so Doccupine sets the correct meta tags.\n\n```text\npublic/og-image.png \u2192 https://your-domain.com/og-image.png\n```\n\n### Custom fonts\nPlace font files in `public` and reference them from your `fonts.json`. See the **Fonts** page for full configuration details.\n\n```text\npublic/fonts/custom-font.woff2 \u2192 https://your-domain.com/fonts/custom-font.woff2\n```\n\n### Images and other media\nAny image or file you want to reference in your MDX pages can live in `public`. Use a root-relative path in your content:\n\n```mdx\n\n```\n\n## Live syncing\n\n<Callout type=\"info\">\n Doccupine watches the `public` directory for changes while running. When you add, update, or remove a file, the generated site is updated automatically.\n</Callout>\n\n## Tips\n- **Keep it flat**: For a small number of files, placing them directly in `public` keeps paths short and simple.\n- **Use subfolders for organization**: For larger projects, group assets into folders like `public/fonts`, `public/images`, or `public/icons`.\n- **Mind file size**: Optimize images before adding them to keep deployment size and load times low.\n- **Consistent naming**: Use lowercase, hyphen-separated filenames (e.g., `og-image.png`) for predictable URLs.";
|
|
1
|
+
export declare const mediaAndAssetsMdxTemplate = "---\ntitle: \"Media and assets\"\ndescription: \"Serve static files like images, favicons, fonts, and Open Graph previews from the public directory.\"\ndate: \"2026-02-19\"\ncategory: \"Configuration\"\ncategoryOrder: 3\norder: 6\n---\n# Media and assets\nDoccupine watches a `public` directory in your project root (the same folder where you execute `npx doccupine`) and copies its contents into the generated Next.js `public` folder. Use it to serve static files such as favicons, Open Graph preview images, custom fonts, or any other media your documentation needs.\n\n## The public directory\nCreate a `public` folder at your project root. Any file you place inside is automatically synced to the generated site and served from the root URL path.\n\n```text\nmy-docs/\n\u251C\u2500\u2500 public/\n\u2502 \u251C\u2500\u2500 favicon.ico\n\u2502 \u251C\u2500\u2500 og-image.png\n\u2502 \u251C\u2500\u2500 logo.svg\n\u2502 \u2514\u2500\u2500 fonts/\n\u2502 \u2514\u2500\u2500 custom-font.woff2\n\u251C\u2500\u2500 docs/\n\u2502 \u2514\u2500\u2500 index.mdx\n\u251C\u2500\u2500 config.json\n\u2514\u2500\u2500 theme.json\n```\n\n## How assets are served\nFiles in the `public` directory are available at the root of your deployed domain. The path inside `public` maps directly to the URL path.\n\n| File | URL |\n|-------------------------------|--------------------------------------------|\n| `public/favicon.ico` | `https://your-domain.com/favicon.ico` |\n| `public/og-image.png` | `https://your-domain.com/og-image.png` |\n| `public/logo.svg` | `https://your-domain.com/logo.svg` |\n| `public/fonts/custom-font.woff2` | `https://your-domain.com/fonts/custom-font.woff2` |\n\n## Common use cases\n\n### Favicon\nDrop a `favicon.ico` into the `public` folder. Browsers pick it up automatically from the root path.\n\n```text\npublic/favicon.ico \u2192 https://your-domain.com/favicon.ico\n```\n\n### Open Graph preview image\nAdd an image for link previews on social media. Reference it in your `config.json` so Doccupine sets the correct meta tags.\n\n```text\npublic/og-image.png \u2192 https://your-domain.com/og-image.png\n```\n\n### Custom fonts\nPlace font files in `public` and reference them from your `fonts.json`. See the **Fonts** page for full configuration details.\n\n```text\npublic/fonts/custom-font.woff2 \u2192 https://your-domain.com/fonts/custom-font.woff2\n```\n\n### Images and other media\nAny image or file you want to reference in your MDX pages can live in `public`. Use a root-relative path in your content:\n\n```mdx\n\n```\n\n## Live syncing\n\n<Callout type=\"info\">\n Doccupine watches the `public` directory for changes while running. When you add, update, or remove a file, the generated site is updated automatically.\n</Callout>\n\n## Tips\n- **Keep it flat**: For a small number of files, placing them directly in `public` keeps paths short and simple.\n- **Use subfolders for organization**: For larger projects, group assets into folders like `public/fonts`, `public/images`, or `public/icons`.\n- **Mind file size**: Optimize images before adding them to keep deployment size and load times low.\n- **Consistent naming**: Use lowercase, hyphen-separated filenames (e.g., `og-image.png`) for predictable URLs.";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const platformFileEditorMdxTemplate = "---\ntitle: \"File Editor\"\ndescription: \"Browse, create, and edit your documentation files directly in the browser.\"\ndate: \"2026-02-19\"\ncategory: \"Editing\"\ncategoryOrder: 1\norder: 0\nsection: \"Platform\"\n---\n# File Editor\nThe file editor is the main workspace for your documentation project. It provides a browser-based file explorer and editor for working with your MDX files and assets.\n\n## File explorer\nThe left panel
|
|
1
|
+
export declare const platformFileEditorMdxTemplate = "---\ntitle: \"File Editor\"\ndescription: \"Browse, create, and edit your documentation files directly in the browser.\"\ndate: \"2026-02-19\"\ncategory: \"Editing\"\ncategoryOrder: 1\norder: 0\nsection: \"Platform\"\n---\n# File Editor\nThe file editor is the main workspace for your documentation project. It provides a browser-based file explorer and editor for working with your MDX files and assets.\n\n## File explorer\nThe left panel has three tabs:\n\n- **Files** - browse your repository's file tree, create and manage files and folders\n- **Media** - manage uploaded images and binary assets\n- **Navigation** - open the [Navigation Builder](/platform/navigation-settings) to configure your sidebar structure with drag-and-drop\n\nIn the Files tab, you can:\n\n- **Browse** directories and files\n- **Create** new files and folders\n- **Rename** and **delete** existing files\n- **Upload** binary assets like images, favicons, and font files\n\nClick any file to open it in the editor panel.\n\n## Editing files\nThe editor supports MDX files with full syntax highlighting. Changes you make are saved as **pending changes** - they aren't committed to your repository until you publish.\n\n<Callout type=\"note\">\n Pending changes are stored in Doccupine's database, not in your Git repository. This means you can make edits across multiple sessions before publishing.\n</Callout>\n\n## Version history\nFor any file, you can view its commit history to see how it has changed over time. This lets you:\n\n- See when changes were made and what the commit messages were\n- View the file's content at any previous commit\n- Compare past versions to understand what changed\n\n## Binary files\nYou can upload images and other binary assets (PNG, JPG, SVG, WOFF2, etc.) directly through the file explorer. These are stored temporarily in Doccupine's storage and committed to your repository when you publish.\n\n## Read-only mode\nTeam members with the **Viewer** or **Billing** role can browse files but cannot make edits. The editor will display content in read-only mode for these users.";
|
|
@@ -11,7 +11,13 @@ section: "Platform"
|
|
|
11
11
|
The file editor is the main workspace for your documentation project. It provides a browser-based file explorer and editor for working with your MDX files and assets.
|
|
12
12
|
|
|
13
13
|
## File explorer
|
|
14
|
-
The left panel
|
|
14
|
+
The left panel has three tabs:
|
|
15
|
+
|
|
16
|
+
- **Files** - browse your repository's file tree, create and manage files and folders
|
|
17
|
+
- **Media** - manage uploaded images and binary assets
|
|
18
|
+
- **Navigation** - open the [Navigation Builder](/platform/navigation-settings) to configure your sidebar structure with drag-and-drop
|
|
19
|
+
|
|
20
|
+
In the Files tab, you can:
|
|
15
21
|
|
|
16
22
|
- **Browse** directories and files
|
|
17
23
|
- **Create** new files and folders
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const platformNavigationSettingsMdxTemplate = "---\ntitle: \"Navigation Settings\"\ndescription: \"Define the sidebar structure for your documentation site
|
|
1
|
+
export declare const platformNavigationSettingsMdxTemplate = "---\ntitle: \"Navigation Settings\"\ndescription: \"Define the sidebar structure for your documentation site using the drag-and-drop Navigation Builder.\"\ndate: \"2026-02-24\"\ncategory: \"Configuration\"\ncategoryOrder: 2\norder: 2\nsection: \"Platform\"\n---\n# Navigation Settings\nThe Navigation Builder lets you define your sidebar structure through a visual, drag-and-drop interface. It lives inside the **File Explorer** as a dedicated **Navigation** tab, alongside the Files and Media tabs.\n\n## Structure\nNavigation is organized into three levels:\n\n- **Section** - a top-level area of your site (e.g., \"Docs\", \"API Reference\"). Each section appears as a tab below the site header and has its own sidebar. Sections are defined by a label, URL slug, and docs directory.\n- **Category** - a group label within a section's sidebar (e.g., \"Getting Started\")\n- **Link** - an individual page entry within a category, defined by a slug and title\n\n## Drag-and-drop reordering\nYou can reorder items at every level by dragging their handles:\n\n- **Drag categories** between sections or within the same section to change their position\n- **Drag links** between categories or within the same category to reorder them\n\n## Managing sections\n- Click **Add Section** in the toolbar to create a new section. Fill in the label, slug, and directory fields.\n- Click the **edit** button on a section header to open its edit modal, where you can update the label, slug, and directory.\n- Delete a section from its edit modal using the delete button. The root section cannot be deleted.\n\n### The default section\nOne section should have an empty slug (`\"\"`). This is the default/root section that serves pages at the root URL. Pages not assigned to any other section belong here.\n\n### Frontmatter-based sections\nYou can also define sections purely through page frontmatter without using the Navigation Builder. Add a `section` field to your MDX frontmatter and Doccupine will create sections automatically. See the [Sections documentation](/sections) for details.\n\n## Managing categories\n- Click **Add Category** within a section to create a new group\n- Click the **edit** button on a category to open its edit modal, where you can rename it or delete it\n\n## Adding links\nWithin each category, click **Add files** to open a popover that lists available MDX files not yet included in the navigation. You can:\n\n- **Search** by file name to filter the list\n- **Select** one or more files using checkboxes\n- Click **Add** to insert them as links in the category\n\nTo remove a link, click the **delete** button next to it.\n\n## Regenerate from files\nThe toolbar includes a **Regenerate** button that rebuilds the entire navigation tree from your MDX files' frontmatter (`category`, `categoryOrder`, and `order` fields). A confirmation modal appears before any existing manual navigation is replaced.\n\n<Callout type=\"warning\">\n Regenerating from files replaces your current navigation structure. This cannot be undone.\n</Callout>\n\n## Auto-generated vs. manual\nIf you don't configure navigation at all, Doccupine automatically builds your sidebar from page frontmatter. The Navigation Builder is only needed when you want explicit control over the order and grouping.\n\n## How it works\nWhen you save, the Navigation Builder writes two files to your repository as pending changes:\n\n- `navigation.json` - the category and link structure for each section\n- `sections.json` - the list of sections with their labels, slugs, and directories\n\n**Array format** for single-section sites (`navigation.json`):\n```json\n[\n {\n \"label\": \"Getting Started\",\n \"links\": [\n { \"slug\": \"getting-started\", \"title\": \"Quick Start\" }\n ]\n }\n]\n```\n\n**Object format** for multi-section sites (`navigation.json`):\n```json\n{\n \"\": [\n { \"label\": \"General\", \"links\": [{ \"slug\": \"\", \"title\": \"Introduction\" }] }\n ],\n \"api\": [\n { \"label\": \"Auth\", \"links\": [{ \"slug\": \"api/auth\", \"title\": \"Authentication\" }] }\n ]\n}\n```\n\n**Sections** (`sections.json`):\n```json\n[\n { \"label\": \"Docs\", \"slug\": \"\" },\n { \"label\": \"API Reference\", \"slug\": \"api\" }\n]\n```";
|
|
@@ -1,47 +1,69 @@
|
|
|
1
1
|
export const platformNavigationSettingsMdxTemplate = `---
|
|
2
2
|
title: "Navigation Settings"
|
|
3
|
-
description: "Define the sidebar structure for your documentation site
|
|
4
|
-
date: "2026-02-
|
|
3
|
+
description: "Define the sidebar structure for your documentation site using the drag-and-drop Navigation Builder."
|
|
4
|
+
date: "2026-02-24"
|
|
5
5
|
category: "Configuration"
|
|
6
6
|
categoryOrder: 2
|
|
7
7
|
order: 2
|
|
8
8
|
section: "Platform"
|
|
9
9
|
---
|
|
10
10
|
# Navigation Settings
|
|
11
|
-
The Navigation
|
|
11
|
+
The Navigation Builder lets you define your sidebar structure through a visual, drag-and-drop interface. It lives inside the **File Explorer** as a dedicated **Navigation** tab, alongside the Files and Media tabs.
|
|
12
12
|
|
|
13
13
|
## Structure
|
|
14
|
-
Navigation is organized into
|
|
14
|
+
Navigation is organized into three levels:
|
|
15
15
|
|
|
16
|
-
- **
|
|
17
|
-
- **
|
|
16
|
+
- **Section** - a top-level area of your site (e.g., "Docs", "API Reference"). Each section appears as a tab below the site header and has its own sidebar. Sections are defined by a label, URL slug, and docs directory.
|
|
17
|
+
- **Category** - a group label within a section's sidebar (e.g., "Getting Started")
|
|
18
|
+
- **Link** - an individual page entry within a category, defined by a slug and title
|
|
19
|
+
|
|
20
|
+
## Drag-and-drop reordering
|
|
21
|
+
You can reorder items at every level by dragging their handles:
|
|
22
|
+
|
|
23
|
+
- **Drag categories** between sections or within the same section to change their position
|
|
24
|
+
- **Drag links** between categories or within the same category to reorder them
|
|
25
|
+
|
|
26
|
+
## Managing sections
|
|
27
|
+
- Click **Add Section** in the toolbar to create a new section. Fill in the label, slug, and directory fields.
|
|
28
|
+
- Click the **edit** button on a section header to open its edit modal, where you can update the label, slug, and directory.
|
|
29
|
+
- Delete a section from its edit modal using the delete button. The root section cannot be deleted.
|
|
30
|
+
|
|
31
|
+
### The default section
|
|
32
|
+
One section should have an empty slug (\`""\`). This is the default/root section that serves pages at the root URL. Pages not assigned to any other section belong here.
|
|
33
|
+
|
|
34
|
+
### Frontmatter-based sections
|
|
35
|
+
You can also define sections purely through page frontmatter without using the Navigation Builder. Add a \`section\` field to your MDX frontmatter and Doccupine will create sections automatically. See the [Sections documentation](/sections) for details.
|
|
18
36
|
|
|
19
37
|
## Managing categories
|
|
20
|
-
- Click **Add Category** to create a new group
|
|
21
|
-
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
38
|
+
- Click **Add Category** within a section to create a new group
|
|
39
|
+
- Click the **edit** button on a category to open its edit modal, where you can rename it or delete it
|
|
40
|
+
|
|
41
|
+
## Adding links
|
|
42
|
+
Within each category, click **Add files** to open a popover that lists available MDX files not yet included in the navigation. You can:
|
|
43
|
+
|
|
44
|
+
- **Search** by file name to filter the list
|
|
45
|
+
- **Select** one or more files using checkboxes
|
|
46
|
+
- Click **Add** to insert them as links in the category
|
|
47
|
+
|
|
48
|
+
To remove a link, click the **delete** button next to it.
|
|
49
|
+
|
|
50
|
+
## Regenerate from files
|
|
51
|
+
The toolbar includes a **Regenerate** button that rebuilds the entire navigation tree from your MDX files' frontmatter (\`category\`, \`categoryOrder\`, and \`order\` fields). A confirmation modal appears before any existing manual navigation is replaced.
|
|
52
|
+
|
|
53
|
+
<Callout type="warning">
|
|
54
|
+
Regenerating from files replaces your current navigation structure. This cannot be undone.
|
|
36
55
|
</Callout>
|
|
37
56
|
|
|
38
57
|
## Auto-generated vs. manual
|
|
39
|
-
If you don't configure navigation at all, Doccupine automatically builds your sidebar from page frontmatter. The Navigation
|
|
58
|
+
If you don't configure navigation at all, Doccupine automatically builds your sidebar from page frontmatter. The Navigation Builder is only needed when you want explicit control over the order and grouping.
|
|
40
59
|
|
|
41
60
|
## How it works
|
|
42
|
-
|
|
61
|
+
When you save, the Navigation Builder writes two files to your repository as pending changes:
|
|
43
62
|
|
|
44
|
-
|
|
63
|
+
- \`navigation.json\` - the category and link structure for each section
|
|
64
|
+
- \`sections.json\` - the list of sections with their labels, slugs, and directories
|
|
65
|
+
|
|
66
|
+
**Array format** for single-section sites (\`navigation.json\`):
|
|
45
67
|
\`\`\`json
|
|
46
68
|
[
|
|
47
69
|
{
|
|
@@ -53,7 +75,7 @@ Navigation settings are stored in \`navigation.json\` at the root of your reposi
|
|
|
53
75
|
]
|
|
54
76
|
\`\`\`
|
|
55
77
|
|
|
56
|
-
**Object format** for multi-section sites:
|
|
78
|
+
**Object format** for multi-section sites (\`navigation.json\`):
|
|
57
79
|
\`\`\`json
|
|
58
80
|
{
|
|
59
81
|
"": [
|
|
@@ -63,4 +85,12 @@ Navigation settings are stored in \`navigation.json\` at the root of your reposi
|
|
|
63
85
|
{ "label": "Auth", "links": [{ "slug": "api/auth", "title": "Authentication" }] }
|
|
64
86
|
]
|
|
65
87
|
}
|
|
88
|
+
\`\`\`
|
|
89
|
+
|
|
90
|
+
**Sections** (\`sections.json\`):
|
|
91
|
+
\`\`\`json
|
|
92
|
+
[
|
|
93
|
+
{ "label": "Docs", "slug": "" },
|
|
94
|
+
{ "label": "API Reference", "slug": "api" }
|
|
95
|
+
]
|
|
66
96
|
\`\`\``;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const platformSectionsSettingsMdxTemplate = "---\ntitle: \"Sections Settings\"\ndescription: \"Split your documentation into top-level areas with independent sidebars using the
|
|
1
|
+
export declare const platformSectionsSettingsMdxTemplate = "---\ntitle: \"Sections Settings\"\ndescription: \"Split your documentation into top-level areas with independent sidebars using the Navigation Builder.\"\ndate: \"2026-02-24\"\ncategory: \"Configuration\"\ncategoryOrder: 2\norder: 3\nsection: \"Platform\"\n---\n# Sections Settings\nSections let you divide your documentation into separate top-level areas. Each section appears as a tab below the site header and has its own sidebar navigation.\n\nSections are managed inside the [Navigation Builder](/navigation-settings), which is available as the **Navigation** tab in the File Explorer.\n\n## Adding a section\nIn the Navigation Builder toolbar, click **Add Section** and fill in the fields:\n\n- **Label** - the display name shown in the tab bar (e.g., \"API Reference\")\n- **Slug** - the URL prefix for pages in this section (e.g., `api`). Use an empty string for the default/root section.\n- **Directory** - the subdirectory containing this section's files. Only needed when the directory name differs from the slug.\n\n## Editing and deleting sections\nClick the **edit** button on a section header to open its edit modal, where you can update the label, slug, and directory. The delete button is available inside the modal for non-root sections.\n\n## Reordering sections\nDrag sections by their handle to reorder them. The first section in the list appears on the left in the tab bar.\n\n## The default section\nOne section should have an empty slug (`\"\"`). This is the default section that serves pages at the root URL. Pages not assigned to any other section belong here.\n\n<Callout type=\"note\">\n For a deeper explanation of how sections work, including frontmatter-based sections and URL routing, see the [Sections documentation](/sections).\n</Callout>\n\n## How it works\nSection settings are stored in `sections.json` at the root of your repository:\n\n```json\n[\n { \"label\": \"Docs\", \"slug\": \"\" },\n { \"label\": \"API Reference\", \"slug\": \"api\" },\n { \"label\": \"SDKs\", \"slug\": \"sdks\" }\n]\n```\n\nYou can also define sections purely through page frontmatter without using the Navigation Builder. See the [Sections documentation](/sections) for details on both approaches.";
|
|
@@ -1,24 +1,29 @@
|
|
|
1
1
|
export const platformSectionsSettingsMdxTemplate = `---
|
|
2
2
|
title: "Sections Settings"
|
|
3
|
-
description: "Split your documentation into top-level areas with independent sidebars using the
|
|
4
|
-
date: "2026-02-
|
|
3
|
+
description: "Split your documentation into top-level areas with independent sidebars using the Navigation Builder."
|
|
4
|
+
date: "2026-02-24"
|
|
5
5
|
category: "Configuration"
|
|
6
6
|
categoryOrder: 2
|
|
7
7
|
order: 3
|
|
8
8
|
section: "Platform"
|
|
9
9
|
---
|
|
10
10
|
# Sections Settings
|
|
11
|
-
|
|
11
|
+
Sections let you divide your documentation into separate top-level areas. Each section appears as a tab below the site header and has its own sidebar navigation.
|
|
12
|
+
|
|
13
|
+
Sections are managed inside the [Navigation Builder](/navigation-settings), which is available as the **Navigation** tab in the File Explorer.
|
|
12
14
|
|
|
13
15
|
## Adding a section
|
|
14
|
-
|
|
16
|
+
In the Navigation Builder toolbar, click **Add Section** and fill in the fields:
|
|
15
17
|
|
|
16
18
|
- **Label** - the display name shown in the tab bar (e.g., "API Reference")
|
|
17
19
|
- **Slug** - the URL prefix for pages in this section (e.g., \`api\`). Use an empty string for the default/root section.
|
|
18
|
-
- **Directory**
|
|
20
|
+
- **Directory** - the subdirectory containing this section's files. Only needed when the directory name differs from the slug.
|
|
21
|
+
|
|
22
|
+
## Editing and deleting sections
|
|
23
|
+
Click the **edit** button on a section header to open its edit modal, where you can update the label, slug, and directory. The delete button is available inside the modal for non-root sections.
|
|
19
24
|
|
|
20
25
|
## Reordering sections
|
|
21
|
-
|
|
26
|
+
Drag sections by their handle to reorder them. The first section in the list appears on the left in the tab bar.
|
|
22
27
|
|
|
23
28
|
## The default section
|
|
24
29
|
One section should have an empty slug (\`""\`). This is the default section that serves pages at the root URL. Pages not assigned to any other section belong here.
|
|
@@ -38,4 +43,4 @@ Section settings are stored in \`sections.json\` at the root of your repository:
|
|
|
38
43
|
]
|
|
39
44
|
\`\`\`
|
|
40
45
|
|
|
41
|
-
You can also define sections purely through page frontmatter without using
|
|
46
|
+
You can also define sections purely through page frontmatter without using the Navigation Builder. See the [Sections documentation](/sections) for details on both approaches.`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const orderNavItemsTemplate = "export interface PagesProps {\n slug: string;\n title: string;\n date: string;\n category: string;\n description?: string;\n path?: string;\n categoryOrder?: number;\n order?: number;\n section?: string;\n}\n\ninterface AccProps {\n [key: string]: {\n categoryOrder: number;\n pages: {
|
|
1
|
+
export declare const orderNavItemsTemplate = "export interface PagesProps {\n slug: string;\n title: string;\n date: string | null;\n category: string;\n description?: string;\n path?: string;\n categoryOrder?: number;\n order?: number;\n section?: string;\n}\n\ninterface AccProps {\n [key: string]: {\n categoryOrder: number;\n pages: {\n date: string | null;\n slug: string;\n title: string;\n order: number;\n }[];\n };\n}\n\nfunction transformPagesToGroupedStructure(pages: PagesProps[]) {\n const grouped = pages.reduce((acc: AccProps, page: PagesProps) => {\n const category = page.category || \"Uncategorized\";\n\n if (!acc[category]) {\n acc[category] = {\n categoryOrder: page.categoryOrder || 0,\n pages: [],\n };\n }\n\n acc[category].pages.push({\n date: page.date,\n slug: page.slug,\n title: page.title,\n order: page.order || 0,\n });\n\n return acc;\n }, {});\n\n return Object.entries(grouped)\n .sort(([, a], [, b]) => a.categoryOrder - b.categoryOrder)\n .map(([categoryName, categoryData], index) => ({\n slug: index === 0 ? \"\" : categoryName.toLowerCase().replace(/s+/g, \"-\"),\n label: categoryName,\n links: categoryData.pages.sort((a, b) => a.order - b.order),\n }));\n}\n\nexport { transformPagesToGroupedStructure };\n";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export const orderNavItemsTemplate = `export interface PagesProps {
|
|
2
2
|
slug: string;
|
|
3
3
|
title: string;
|
|
4
|
-
date: string;
|
|
4
|
+
date: string | null;
|
|
5
5
|
category: string;
|
|
6
6
|
description?: string;
|
|
7
7
|
path?: string;
|
|
@@ -13,7 +13,12 @@ export const orderNavItemsTemplate = `export interface PagesProps {
|
|
|
13
13
|
interface AccProps {
|
|
14
14
|
[key: string]: {
|
|
15
15
|
categoryOrder: number;
|
|
16
|
-
pages: {
|
|
16
|
+
pages: {
|
|
17
|
+
date: string | null;
|
|
18
|
+
slug: string;
|
|
19
|
+
title: string;
|
|
20
|
+
order: number;
|
|
21
|
+
}[];
|
|
17
22
|
};
|
|
18
23
|
}
|
|
19
24
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "doccupine",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.69",
|
|
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": {
|