doccupine 0.0.66 → 0.0.68

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.
@@ -109,6 +109,18 @@ ${hasSections
109
109
 
110
110
  return (
111
111
  <html lang="en">
112
+ <head>
113
+ {/* Prevents dark-mode FOUC on Safari/Firefox. These browsers don't support
114
+ Sec-CH-Prefers-Color-Scheme (handled by middleware for Chrome), so on
115
+ a first visit this blocking script detects prefers-color-scheme, sets
116
+ the theme cookie, and hides the body until router.refresh() re-renders
117
+ with the correct theme (see ClientThemeProvider). */}
118
+ <script
119
+ dangerouslySetInnerHTML={{
120
+ __html: \`(function(){try{var c=document.cookie.split(";").find(function(s){return s.trim().startsWith("theme=")});if(!c){var d=window.matchMedia&&window.matchMedia("(prefers-color-scheme:dark)").matches;document.cookie="theme="+(d?"dark":"light")+";path=/;max-age=31536000;SameSite=Lax";if(d){var s=document.createElement("style");s.id="__theme-init";s.textContent="html{background:#000!important;color-scheme:dark}body{visibility:hidden}";document.head.appendChild(s)}}}catch(e){}})();\`,
121
+ }}
122
+ />
123
+ </head>
112
124
  <body className={font.className}>
113
125
  <StyledComponentsRegistry>
114
126
  <CherryThemeProvider theme={theme} themeDark={themeDark}>
@@ -156,6 +168,18 @@ ${hasSections
156
168
 
157
169
  return (
158
170
  <html lang="en">
171
+ <head>
172
+ {/* Prevents dark-mode FOUC on Safari/Firefox. These browsers don't support
173
+ Sec-CH-Prefers-Color-Scheme (handled by middleware for Chrome), so on
174
+ a first visit this blocking script detects prefers-color-scheme, sets
175
+ the theme cookie, and hides the body until router.refresh() re-renders
176
+ with the correct theme (see ClientThemeProvider). */}
177
+ <script
178
+ dangerouslySetInnerHTML={{
179
+ __html: \`(function(){try{var c=document.cookie.split(";").find(function(s){return s.trim().startsWith("theme=")});if(!c){var d=window.matchMedia&&window.matchMedia("(prefers-color-scheme:dark)").matches;document.cookie="theme="+(d?"dark":"light")+";path=/;max-age=31536000;SameSite=Lax";if(d){var s=document.createElement("style");s.id="__theme-init";s.textContent="html{background:#000!important;color-scheme:dark}body{visibility:hidden}";document.head.appendChild(s)}}}catch(e){}})();\`,
180
+ }}
181
+ />
182
+ </head>
159
183
  <body className={font.className}>
160
184
  <StyledComponentsRegistry>
161
185
  <CherryThemeProvider theme={theme} themeDark={themeDark}>
@@ -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={components}\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,\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,7 +1,9 @@
1
- export const docsTemplate = `import { Flex } from "cherry-styled-components";
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: &lt;{componentName} /&gt;
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={components}
108
+ components={allComponents}
66
109
  />
67
110
  )}
68
111
  </StyledMarkdownContainer>
@@ -1 +1 @@
1
- export declare const clientThemeProviderTemplate = "\"use client\";\nimport React, { useEffect, useMemo, useState, useContext } from \"react\";\nimport { ThemeProvider as StyledThemeProvider } from \"styled-components\";\nimport { Theme } from \"@/app/theme\";\nimport { useRouter } from \"next/navigation\";\nimport { GlobalStyles } from \"@/components/layout/GlobalStyles\";\n\ntype ThemeOverrideContextValue = {\n theme: Theme;\n setTheme: (t: Theme | null) => void;\n};\n\nconst ThemeOverrideContext =\n React.createContext<ThemeOverrideContextValue | null>(null);\n\nfunction useThemeOverride() {\n const ctx = useContext(ThemeOverrideContext);\n if (!ctx) {\n throw new Error(\"useThemeOverride must be used within ClientThemeProvider\");\n }\n return ctx;\n}\n\nfunction ClientThemeProvider({\n children,\n theme,\n}: {\n children: React.ReactNode;\n theme: Theme;\n}) {\n const router = useRouter();\n const [overrideTheme, setOverrideTheme] = useState<Theme | null>(null);\n useEffect(() => {\n try {\n const cookie = document.cookie\n .split(\";\")\n .map((c) => c.trim())\n .find((c) => c.startsWith(\"theme=\"));\n const cookieValue = cookie ? cookie.split(\"=\")[1] : undefined;\n if (!cookieValue) {\n const prefersDark =\n window.matchMedia &&\n window.matchMedia(\"(prefers-color-scheme: dark)\").matches;\n const shouldBe = prefersDark ? \"dark\" : \"light\";\n fetch(\"/api/theme\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ theme: shouldBe }),\n })\n .then(() => {\n router.refresh();\n })\n .catch(() => {\n console.error(\"Error setting theme\");\n });\n }\n } catch {}\n }, [router]);\n\n const effectiveTheme = useMemo(\n () => overrideTheme ?? theme,\n [overrideTheme, theme],\n );\n return (\n <ThemeOverrideContext.Provider\n value={{ theme: effectiveTheme, setTheme: setOverrideTheme }}\n >\n <StyledThemeProvider theme={effectiveTheme}>\n <GlobalStyles />\n {children}\n </StyledThemeProvider>\n </ThemeOverrideContext.Provider>\n );\n}\n\nexport { ClientThemeProvider, useThemeOverride };\n";
1
+ export declare const clientThemeProviderTemplate = "\"use client\";\nimport React, { useEffect, useMemo, useState, useContext } from \"react\";\nimport { ThemeProvider as StyledThemeProvider } from \"styled-components\";\nimport { Theme } from \"@/app/theme\";\nimport { useRouter } from \"next/navigation\";\nimport { GlobalStyles } from \"@/components/layout/GlobalStyles\";\n\ntype ThemeOverrideContextValue = {\n theme: Theme;\n setTheme: (t: Theme | null) => void;\n};\n\nconst ThemeOverrideContext =\n React.createContext<ThemeOverrideContextValue | null>(null);\n\nfunction useThemeOverride() {\n const ctx = useContext(ThemeOverrideContext);\n if (!ctx) {\n throw new Error(\"useThemeOverride must be used within ClientThemeProvider\");\n }\n return ctx;\n}\n\nfunction ClientThemeProvider({\n children,\n theme,\n}: {\n children: React.ReactNode;\n theme: Theme;\n}) {\n const router = useRouter();\n const [overrideTheme, setOverrideTheme] = useState<Theme | null>(null);\n useEffect(() => {\n try {\n const cookie = document.cookie\n .split(\";\")\n .map((c) => c.trim())\n .find((c) => c.startsWith(\"theme=\"));\n const cookieValue = cookie ? cookie.split(\"=\")[1] : undefined;\n if (!cookieValue) {\n // Fallback: blocking script should have set this, but handle edge cases\n const prefersDark =\n window.matchMedia &&\n window.matchMedia(\"(prefers-color-scheme: dark)\").matches;\n const shouldBe = prefersDark ? \"dark\" : \"light\";\n document.cookie = `theme=${shouldBe};path=/;max-age=31536000;SameSite=Lax`;\n router.refresh();\n } else if (theme.isDark !== (cookieValue === \"dark\")) {\n // Server-rendered theme doesn't match cookie (e.g., cookie was set\n // by the blocking script after SSR already committed to light theme)\n router.refresh();\n } else {\n // Theme matches cookie - remove the flash-prevention style injected\n // by the blocking script so styled-components takes over\n const el = document.getElementById(\"__theme-init\");\n if (el) el.remove();\n }\n } catch {}\n }, [router, theme.isDark]);\n\n const effectiveTheme = useMemo(\n () => overrideTheme ?? theme,\n [overrideTheme, theme],\n );\n return (\n <ThemeOverrideContext.Provider\n value={{ theme: effectiveTheme, setTheme: setOverrideTheme }}\n >\n <StyledThemeProvider theme={effectiveTheme}>\n <GlobalStyles />\n {children}\n </StyledThemeProvider>\n </ThemeOverrideContext.Provider>\n );\n}\n\nexport { ClientThemeProvider, useThemeOverride };\n";
@@ -38,24 +38,25 @@ function ClientThemeProvider({
38
38
  .find((c) => c.startsWith("theme="));
39
39
  const cookieValue = cookie ? cookie.split("=")[1] : undefined;
40
40
  if (!cookieValue) {
41
+ // Fallback: blocking script should have set this, but handle edge cases
41
42
  const prefersDark =
42
43
  window.matchMedia &&
43
44
  window.matchMedia("(prefers-color-scheme: dark)").matches;
44
45
  const shouldBe = prefersDark ? "dark" : "light";
45
- fetch("/api/theme", {
46
- method: "POST",
47
- headers: { "Content-Type": "application/json" },
48
- body: JSON.stringify({ theme: shouldBe }),
49
- })
50
- .then(() => {
51
- router.refresh();
52
- })
53
- .catch(() => {
54
- console.error("Error setting theme");
55
- });
46
+ document.cookie = \`theme=\${shouldBe};path=/;max-age=31536000;SameSite=Lax\`;
47
+ router.refresh();
48
+ } else if (theme.isDark !== (cookieValue === "dark")) {
49
+ // Server-rendered theme doesn't match cookie (e.g., cookie was set
50
+ // by the blocking script after SSR already committed to light theme)
51
+ router.refresh();
52
+ } else {
53
+ // Theme matches cookie - remove the flash-prevention style injected
54
+ // by the blocking script so styled-components takes over
55
+ const el = document.getElementById("__theme-init");
56
+ if (el) el.remove();
56
57
  }
57
58
  } catch {}
58
- }, [router]);
59
+ }, [router, theme.isDark]);
59
60
 
60
61
  const effectiveTheme = useMemo(
61
62
  () => overrideTheme ?? theme,
@@ -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 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: { date: string; slug: string; title: string; order: number }[];\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
+ 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: { date: string; slug: string; title: string; order: number }[];
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.66",
3
+ "version": "0.0.68",
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": {