doccupine 0.0.98 → 0.0.99
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/index.js +0 -0
- package/dist/templates/components/MDXComponents.d.ts +1 -1
- package/dist/templates/components/MDXComponents.js +3 -1
- package/dist/templates/components/layout/Code.d.ts +1 -1
- package/dist/templates/components/layout/Code.js +228 -3
- package/dist/templates/mdx/code.mdx.d.ts +1 -1
- package/dist/templates/mdx/code.mdx.js +43 -1
- package/dist/templates/package.js +1 -1
- package/package.json +1 -1
- package/dist/index.test.d.ts +0 -1
- package/dist/index.test.js +0 -48
- package/dist/metadata.d.ts +0 -11
- package/dist/metadata.js +0 -52
- package/dist/templates/accordion.mdx.d.ts +0 -1
- package/dist/templates/accordion.mdx.js +0 -41
- package/dist/templates/api/theme/routes.d.ts +0 -1
- package/dist/templates/api/theme/routes.js +0 -28
- package/dist/templates/app/api/theme/routes.d.ts +0 -1
- package/dist/templates/app/api/theme/routes.js +0 -29
- package/dist/templates/app/error.d.ts +0 -1
- package/dist/templates/app/error.js +0 -45
- package/dist/templates/app/layout.d.ts +0 -34
- package/dist/templates/app/layout.js +0 -184
- package/dist/templates/callouts.mdx.d.ts +0 -1
- package/dist/templates/callouts.mdx.js +0 -57
- package/dist/templates/cards.mdx.d.ts +0 -1
- package/dist/templates/cards.mdx.js +0 -25
- package/dist/templates/click-outside.d.ts +0 -1
- package/dist/templates/click-outside.js +0 -28
- package/dist/templates/code.d.ts +0 -1
- package/dist/templates/code.js +0 -202
- package/dist/templates/code.mdx.d.ts +0 -1
- package/dist/templates/code.mdx.js +0 -46
- package/dist/templates/commands.mdx.d.ts +0 -1
- package/dist/templates/commands.mdx.js +0 -48
- package/dist/templates/components/ClickOutside.d.ts +0 -1
- package/dist/templates/components/ClickOutside.js +0 -29
- package/dist/templates/components/layout/ClientThemeProvider.d.ts +0 -1
- package/dist/templates/components/layout/ClientThemeProvider.js +0 -85
- package/dist/templates/components/layout/CopyButton.d.ts +0 -1
- package/dist/templates/components/layout/CopyButton.js +0 -85
- package/dist/templates/components/layout/RenderMarkdown.d.ts +0 -1
- package/dist/templates/components/layout/RenderMarkdown.js +0 -61
- package/dist/templates/components/layout/Stepts.d.ts +0 -1
- package/dist/templates/components/layout/Stepts.js +0 -114
- package/dist/templates/components/layout/ThemeToggle.d.ts +0 -1
- package/dist/templates/components/layout/ThemeToggle.js +0 -107
- package/dist/templates/constants.d.ts +0 -5
- package/dist/templates/constants.js +0 -5
- package/dist/templates/docs-components.d.ts +0 -1
- package/dist/templates/docs-components.js +0 -441
- package/dist/templates/docs.d.ts +0 -1
- package/dist/templates/docs.js +0 -48
- package/dist/templates/fields.mdx.d.ts +0 -1
- package/dist/templates/fields.mdx.js +0 -42
- package/dist/templates/footer.d.ts +0 -1
- package/dist/templates/footer.js +0 -9
- package/dist/templates/header.d.ts +0 -1
- package/dist/templates/header.js +0 -275
- package/dist/templates/home.d.ts +0 -1
- package/dist/templates/home.js +0 -74
- package/dist/templates/home.mdx.d.ts +0 -1
- package/dist/templates/home.mdx.js +0 -44
- package/dist/templates/homemdx.d.ts +0 -1
- package/dist/templates/homemdx.js +0 -59
- package/dist/templates/icon.d.ts +0 -1
- package/dist/templates/icon.js +0 -20
- package/dist/templates/icons.mdx.d.ts +0 -1
- package/dist/templates/icons.mdx.js +0 -44
- package/dist/templates/image-embeds.mdx.d.ts +0 -1
- package/dist/templates/image-embeds.mdx.js +0 -103
- package/dist/templates/layout.d.ts +0 -1
- package/dist/templates/layout.js +0 -69
- package/dist/templates/list-table.mdx.d.ts +0 -1
- package/dist/templates/list-table.mdx.js +0 -85
- package/dist/templates/mdx/config.mdx.d.ts +0 -1
- package/dist/templates/mdx/config.mdx.js +0 -47
- package/dist/templates/mdx/deployment.mdx.d.ts +0 -1
- package/dist/templates/mdx/deployment.mdx.js +0 -61
- package/dist/templates/mdx/image-embeds.mdx.d.ts +0 -1
- package/dist/templates/mdx/image-embeds.mdx.js +0 -95
- package/dist/templates/mdx/links.mdx.d.ts +0 -1
- package/dist/templates/mdx/links.mdx.js +0 -45
- package/dist/templates/mdx/list-and-tables.mdx.d.ts +0 -1
- package/dist/templates/mdx/list-and-tables.mdx.js +0 -78
- package/dist/templates/mdx/list-table.mdx.d.ts +0 -1
- package/dist/templates/mdx/list-table.mdx.js +0 -78
- package/dist/templates/mdx/platform/deployments.mdx.d.ts +0 -1
- package/dist/templates/mdx/platform/deployments.mdx.js +0 -36
- package/dist/templates/mdx/platform/sections-settings.mdx.d.ts +0 -1
- package/dist/templates/mdx/platform/sections-settings.mdx.js +0 -46
- package/dist/templates/mdx/stepts.mdx.d.ts +0 -1
- package/dist/templates/mdx/stepts.mdx.js +0 -60
- package/dist/templates/mdx/text.mdx.d.ts +0 -1
- package/dist/templates/mdx/text.mdx.js +0 -139
- package/dist/templates/middleware.d.ts +0 -1
- package/dist/templates/middleware.js +0 -28
- package/dist/templates/not-found.d.ts +0 -1
- package/dist/templates/not-found.js +0 -22
- package/dist/templates/pictograms.d.ts +0 -1
- package/dist/templates/pictograms.js +0 -67
- package/dist/templates/shared-styles.d.ts +0 -1
- package/dist/templates/shared-styles.js +0 -792
- package/dist/templates/structures.d.ts +0 -2
- package/dist/templates/structures.js +0 -208
- package/dist/templates/tabs.mdx.d.ts +0 -1
- package/dist/templates/tabs.mdx.js +0 -59
- package/dist/templates/text.mdx.d.ts +0 -1
- package/dist/templates/text.mdx.js +0 -142
- package/dist/templates/theme-toggle.d.ts +0 -1
- package/dist/templates/theme-toggle.js +0 -111
- package/dist/templates/theme.d.ts +0 -1
- package/dist/templates/theme.js +0 -307
- package/dist/templates/typography.d.ts +0 -1
- package/dist/templates/typography.js +0 -52
- package/dist/templates/update.mdx.d.ts +0 -1
- package/dist/templates/update.mdx.js +0 -58
package/dist/index.js
CHANGED
|
File without changes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const mdxComponentsTemplate = "import React from \"react\";\nimport Link from \"next/link\";\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype MDXComponents = Record<string, React.ComponentType<any>>;\nimport { Space } from \"cherry-styled-components\";\nimport { Code as CodeBlock } from \"@/components/layout/Code\";\nimport { Card } from \"@/components/layout/Card\";\nimport { Accordion } from \"@/components/layout/Accordion\";\nimport { Tabs, TabContent } from \"@/components/layout/Tabs\";\nimport { Callout } from \"@/components/layout/Callout\";\nimport { Icon } from \"@/components/layout/Icon\";\nimport { Columns } from \"@/components/layout/Columns\";\nimport { Field } from \"@/components/layout/Field\";\nimport { Update } from \"@/components/layout/Update\";\nimport { Steps, Step } from \"@/components/layout/Steps\";\nimport { Button } from \"@/components/layout/Button\";\nimport { ColorSwatch, ColorSwatchGroup } from \"@/components/layout/ColorSwatch\";\nimport { DemoTheme } from \"@/components/layout/DemoTheme\";\n\ninterface HeadingProps extends React.HTMLAttributes<HTMLHeadingElement> {\n children?: React.ReactNode;\n}\n\ninterface PreProps extends React.HTMLAttributes<HTMLPreElement> {\n children?: React.ReactNode;\n}\n\nfunction extractAllTextFromChildren(children: React.ReactNode): string {\n if (children == null) return \"\";\n if (typeof children === \"string\") return children;\n if (typeof children === \"number\") return String(children);\n if (typeof children === \"boolean\") return \"\";\n if (Array.isArray(children))\n return children.map(extractAllTextFromChildren).join(\"\");\n if (React.isValidElement(children)) {\n const element = children as React.ReactElement<{\n children?: React.ReactNode;\n }>;\n return extractAllTextFromChildren(element.props.children);\n }\n return \"\";\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\n// Map <pre><code class=\"language-xyz\"> to our <Code /> component\nfunction Pre(props: PreProps) {\n const child = React.Children.only(props.children) as React.ReactElement<{\n className?: string;\n children?: React.ReactNode;\n }> | null;\n if (child && child.type === \"code\") {\n const className = child.props.className || \"\";\n const match = /language-(\\w+)/.exec(className);\n const language = match ? match[1] : undefined;\n const code = extractAllTextFromChildren(child.props.children).replace(\n /\\n$/,\n \"\",\n );\n if (language) {\n return (\n <CodeBlock className={className} code={code} language={language} />\n );\n }\n }\n return <pre {...props} />;\n}\n\nexport function useMDXComponents(components: MDXComponents): MDXComponents {\n return {\n // Headings with auto-generated ids for TOC and deep links\n h1: ({ children, ...props }: HeadingProps) => {\n const id = generateId(extractAllTextFromChildren(children));\n return (\n <h1 id={id} {...props}>\n {children}\n </h1>\n );\n },\n h2: ({ children, ...props }: HeadingProps) => {\n const id = generateId(extractAllTextFromChildren(children));\n return (\n <h2 id={id} {...props}>\n {children}\n </h2>\n );\n },\n h3: ({ children, ...props }: HeadingProps) => {\n const id = generateId(extractAllTextFromChildren(children));\n return (\n <h3 id={id} {...props}>\n {children}\n </h3>\n );\n },\n h4: ({ children, ...props }: HeadingProps) => {\n const id = generateId(extractAllTextFromChildren(children));\n return (\n <h4 id={id} {...props}>\n {children}\n </h4>\n );\n },\n h5: ({ children, ...props }: HeadingProps) => {\n const id = generateId(extractAllTextFromChildren(children));\n return (\n <h5 id={id} {...props}>\n {children}\n </h5>\n );\n },\n h6: ({ children, ...props }: HeadingProps) => {\n const id = generateId(extractAllTextFromChildren(children));\n return (\n <h6 id={id} {...props}>\n {children}\n </h6>\n );\n },\n\n // Links - use Next.js Link for internal paths\n a: ({\n href,\n children,\n ...props\n }: React.AnchorHTMLAttributes<HTMLAnchorElement>) => {\n if (href && href.startsWith(\"/\")) {\n return (\n <Link href={href} {...props}>\n {children}\n </Link>\n );\n }\n return (\n <a href={href} target=\"_blank\" rel=\"noopener noreferrer\" {...props}>\n {children}\n </a>\n );\n },\n\n // Tables - wrap in a div for responsive overflow\n table: (props: React.TableHTMLAttributes<HTMLTableElement>) => (\n <div className=\"table-wrapper\">\n <table {...props} />\n </div>\n ),\n\n // Code blocks\n pre: Pre,\n\n // Expose your custom components for MDX usage\n Card,\n Accordion,\n Tabs,\n TabContent,\n Callout,\n Icon,\n Columns,\n Field,\n Update,\n Steps,\n Step,\n Button,\n ColorSwatch,\n ColorSwatchGroup,\n DemoTheme,\n Space,\n ...components,\n };\n}\n";
|
|
1
|
+
export declare const mdxComponentsTemplate = "import React from \"react\";\nimport Link from \"next/link\";\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype MDXComponents = Record<string, React.ComponentType<any>>;\nimport { Space } from \"cherry-styled-components\";\nimport { Code as CodeBlock, CodeTabs } from \"@/components/layout/Code\";\nimport { Card } from \"@/components/layout/Card\";\nimport { Accordion } from \"@/components/layout/Accordion\";\nimport { Tabs, TabContent } from \"@/components/layout/Tabs\";\nimport { Callout } from \"@/components/layout/Callout\";\nimport { Icon } from \"@/components/layout/Icon\";\nimport { Columns } from \"@/components/layout/Columns\";\nimport { Field } from \"@/components/layout/Field\";\nimport { Update } from \"@/components/layout/Update\";\nimport { Steps, Step } from \"@/components/layout/Steps\";\nimport { Button } from \"@/components/layout/Button\";\nimport { ColorSwatch, ColorSwatchGroup } from \"@/components/layout/ColorSwatch\";\nimport { DemoTheme } from \"@/components/layout/DemoTheme\";\n\ninterface HeadingProps extends React.HTMLAttributes<HTMLHeadingElement> {\n children?: React.ReactNode;\n}\n\ninterface PreProps extends React.HTMLAttributes<HTMLPreElement> {\n children?: React.ReactNode;\n}\n\nfunction extractAllTextFromChildren(children: React.ReactNode): string {\n if (children == null) return \"\";\n if (typeof children === \"string\") return children;\n if (typeof children === \"number\") return String(children);\n if (typeof children === \"boolean\") return \"\";\n if (Array.isArray(children))\n return children.map(extractAllTextFromChildren).join(\"\");\n if (React.isValidElement(children)) {\n const element = children as React.ReactElement<{\n children?: React.ReactNode;\n }>;\n return extractAllTextFromChildren(element.props.children);\n }\n return \"\";\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\n// Map <pre><code class=\"language-xyz\"> to our <Code /> component\nfunction Pre(props: PreProps) {\n const child = React.Children.only(props.children) as React.ReactElement<{\n className?: string;\n children?: React.ReactNode;\n }> | null;\n if (child && child.type === \"code\") {\n const className = child.props.className || \"\";\n const match = /language-(\\w+)/.exec(className);\n const language = match ? match[1] : undefined;\n const code = extractAllTextFromChildren(child.props.children).replace(\n /\\n$/,\n \"\",\n );\n if (language) {\n return (\n <CodeBlock className={className} code={code} language={language} />\n );\n }\n }\n return <pre {...props} />;\n}\n\nexport function useMDXComponents(components: MDXComponents): MDXComponents {\n return {\n // Headings with auto-generated ids for TOC and deep links\n h1: ({ children, ...props }: HeadingProps) => {\n const id = generateId(extractAllTextFromChildren(children));\n return (\n <h1 id={id} {...props}>\n {children}\n </h1>\n );\n },\n h2: ({ children, ...props }: HeadingProps) => {\n const id = generateId(extractAllTextFromChildren(children));\n return (\n <h2 id={id} {...props}>\n {children}\n </h2>\n );\n },\n h3: ({ children, ...props }: HeadingProps) => {\n const id = generateId(extractAllTextFromChildren(children));\n return (\n <h3 id={id} {...props}>\n {children}\n </h3>\n );\n },\n h4: ({ children, ...props }: HeadingProps) => {\n const id = generateId(extractAllTextFromChildren(children));\n return (\n <h4 id={id} {...props}>\n {children}\n </h4>\n );\n },\n h5: ({ children, ...props }: HeadingProps) => {\n const id = generateId(extractAllTextFromChildren(children));\n return (\n <h5 id={id} {...props}>\n {children}\n </h5>\n );\n },\n h6: ({ children, ...props }: HeadingProps) => {\n const id = generateId(extractAllTextFromChildren(children));\n return (\n <h6 id={id} {...props}>\n {children}\n </h6>\n );\n },\n\n // Links - use Next.js Link for internal paths\n a: ({\n href,\n children,\n ...props\n }: React.AnchorHTMLAttributes<HTMLAnchorElement>) => {\n if (href && href.startsWith(\"/\")) {\n return (\n <Link href={href} {...props}>\n {children}\n </Link>\n );\n }\n return (\n <a href={href} target=\"_blank\" rel=\"noopener noreferrer\" {...props}>\n {children}\n </a>\n );\n },\n\n // Tables - wrap in a div for responsive overflow\n table: (props: React.TableHTMLAttributes<HTMLTableElement>) => (\n <div className=\"table-wrapper\">\n <table {...props} />\n </div>\n ),\n\n // Code blocks\n pre: Pre,\n\n // Expose your custom components for MDX usage\n Code: CodeBlock,\n CodeTabs,\n Card,\n Accordion,\n Tabs,\n TabContent,\n Callout,\n Icon,\n Columns,\n Field,\n Update,\n Steps,\n Step,\n Button,\n ColorSwatch,\n ColorSwatchGroup,\n DemoTheme,\n Space,\n ...components,\n };\n}\n";
|
|
@@ -4,7 +4,7 @@ import Link from "next/link";
|
|
|
4
4
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5
5
|
type MDXComponents = Record<string, React.ComponentType<any>>;
|
|
6
6
|
import { Space } from "cherry-styled-components";
|
|
7
|
-
import { Code as CodeBlock } from "@/components/layout/Code";
|
|
7
|
+
import { Code as CodeBlock, CodeTabs } from "@/components/layout/Code";
|
|
8
8
|
import { Card } from "@/components/layout/Card";
|
|
9
9
|
import { Accordion } from "@/components/layout/Accordion";
|
|
10
10
|
import { Tabs, TabContent } from "@/components/layout/Tabs";
|
|
@@ -156,6 +156,8 @@ export function useMDXComponents(components: MDXComponents): MDXComponents {
|
|
|
156
156
|
pre: Pre,
|
|
157
157
|
|
|
158
158
|
// Expose your custom components for MDX usage
|
|
159
|
+
Code: CodeBlock,
|
|
160
|
+
CodeTabs,
|
|
159
161
|
Card,
|
|
160
162
|
Accordion,
|
|
161
163
|
Tabs,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const codeTemplate = "\"use client\";\nimport { useState, useCallback, useMemo } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport {\n interactiveStyles,\n resetButton,\n styledCode,\n} from \"cherry-styled-components\";\nimport { Theme } from \"@/app/theme\";\nimport { unified } from \"unified\";\nimport rehypeParse from \"rehype-parse\";\nimport rehypeHighlight from \"rehype-highlight\";\nimport rehypeStringify from \"rehype-stringify\";\nimport { Icon } from \"@/components/layout/Icon\";\nimport { thinScrollbar } from \"@/components/layout/SharedStyled\";\n\ninterface CodeProps extends Omit<\n React.HTMLAttributes<HTMLDivElement>,\n \"theme\"\n> {\n code: string;\n language?: string;\n theme?: Theme;\n}\n\nconst CodeWrapper = styled.span<{ theme: Theme }>`\n position: relative;\n z-index: 2;\n display: block;\n width: 100%;\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n border: solid 1px rgba(0, 0, 0, 0.1);\n\n :root.dark & {\n border-color: rgba(255, 255, 255, 0.2);\n }\n`;\n\n/* Code block uses a fixed GitHub-style palette in both modes. Independent of\n theme.json so syntax highlighting stays legible regardless of brand colors.\n Dark variants live in :root.dark & blocks so the swap happens via the\n active <html> class with no re-render. */\nconst TopBar = styled.div<{ theme: Theme }>`\n background: #f6f8fa;\n border-top-left-radius: ${({ theme }) => theme.spacing.radius.lg};\n border-top-right-radius: ${({ theme }) => theme.spacing.radius.lg};\n border-bottom: solid 1px rgba(0, 0, 0, 0.1);\n height: 33px;\n width: 100%;\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: 5px;\n padding: 0 10px;\n\n :root.dark & {\n background: #0d1117;\n border-bottom-color: rgba(255, 255, 255, 0.1);\n }\n`;\n\nconst DotsContainer = styled.div`\n display: flex;\n gap: 5px;\n`;\n\nconst Dot = styled.span<{ theme: Theme }>`\n width: 10px;\n height: 10px;\n border-radius: 50%;\n background: rgba(0, 0, 0, 0.1);\n\n :root.dark & {\n background: rgba(255, 255, 255, 0.1);\n }\n`;\n\n/* Icon-only copy button. interactiveStyles supplies the border highlight on\n hover plus the focus/active rings (no scale effect); the GitHub-style\n copied/base colors stay fixed like the rest of the code block. The dark\n block re-declares the hover border because its higher-specificity\n :root.dark & border-color would otherwise override the mixin's hover. */\nconst CopyButton = styled.button<{ theme: Theme; $copied: boolean }>`\n ${resetButton}\n ${interactiveStyles}\n background: ${({ $copied }) =>\n $copied ? \"rgba(45, 164, 78, 0.1)\" : \"transparent\"};\n border-color: ${({ $copied }) => ($copied ? \"#2da44e\" : \"rgba(0, 0, 0, 0.1)\")};\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n padding: 4px;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-right: -6px;\n\n & svg.lucide {\n color: ${({ $copied }) => ($copied ? \"#2da44e\" : \"#57606a\")};\n }\n\n :root.dark & {\n background: ${({ $copied }) =>\n $copied ? \"rgba(126, 231, 135, 0.2)\" : \"transparent\"};\n border-color: ${({ $copied }) =>\n $copied ? \"#7ee787\" : \"rgba(255, 255, 255, 0.1)\"};\n\n & svg.lucide {\n color: ${({ $copied }) => ($copied ? \"#7ee787\" : \"#c9d1d9\")};\n }\n\n &:hover {\n border-color: ${({ theme }) => theme.colors.primary};\n }\n }\n`;\n\n/* GitHub Light syntax highlighting by default; GitHub Dark in :root.dark.\n Browser resolves which rule wins based on the active <html> class with no\n JS or React re-render involved. */\nconst lightSyntaxHighlight = css`\n & .hljs {\n color: #24292f;\n background: #ffffff;\n }\n & .hljs-doctag,\n & .hljs-keyword,\n & .hljs-meta .hljs-keyword,\n & .hljs-template-tag,\n & .hljs-template-variable,\n & .hljs-type,\n & .hljs-variable.language_ {\n color: #cf222e;\n }\n & .hljs-title,\n & .hljs-title.class_,\n & .hljs-title.class_.inherited__,\n & .hljs-title.function_ {\n color: #8250df;\n }\n & .hljs-attr,\n & .hljs-attribute,\n & .hljs-literal,\n & .hljs-meta,\n & .hljs-number,\n & .hljs-operator,\n & .hljs-selector-attr,\n & .hljs-selector-class,\n & .hljs-selector-id,\n & .hljs-variable {\n color: #0550ae;\n }\n & .hljs-meta .hljs-string,\n & .hljs-regexp,\n & .hljs-string {\n color: #0a3069;\n }\n & .hljs-built_in,\n & .hljs-symbol {\n color: #953800;\n }\n & .hljs-code,\n & .hljs-comment,\n & .hljs-formula {\n color: #6e7781;\n }\n & .hljs-name,\n & .hljs-quote,\n & .hljs-selector-pseudo,\n & .hljs-selector-tag {\n color: #116329;\n }\n & .hljs-subst {\n color: #24292f;\n }\n & .hljs-section {\n color: #0550ae;\n font-weight: 700;\n }\n & .hljs-bullet {\n color: #953800;\n }\n & .hljs-emphasis {\n color: #24292f;\n font-style: italic;\n }\n & .hljs-strong {\n color: #24292f;\n font-weight: 700;\n }\n & .hljs-addition {\n color: #116329;\n background-color: #dafbe1;\n }\n & .hljs-deletion {\n color: #82071e;\n background-color: #ffebe9;\n }\n`;\n\nconst darkSyntaxHighlight = css`\n & .hljs {\n color: #c9d1d9;\n background: #0d1117;\n }\n & .hljs-doctag,\n & .hljs-keyword,\n & .hljs-meta .hljs-keyword,\n & .hljs-template-tag,\n & .hljs-template-variable,\n & .hljs-type,\n & .hljs-variable.language_ {\n color: #ff7b72;\n }\n & .hljs-title,\n & .hljs-title.class_,\n & .hljs-title.class_.inherited__,\n & .hljs-title.function_ {\n color: #d2a8ff;\n }\n & .hljs-attr,\n & .hljs-attribute,\n & .hljs-literal,\n & .hljs-meta,\n & .hljs-number,\n & .hljs-operator,\n & .hljs-selector-attr,\n & .hljs-selector-class,\n & .hljs-selector-id,\n & .hljs-variable {\n color: #79c0ff;\n }\n & .hljs-meta .hljs-string,\n & .hljs-regexp,\n & .hljs-string {\n color: #a5d6ff;\n }\n & .hljs-built_in,\n & .hljs-symbol {\n color: #ffa657;\n }\n & .hljs-code,\n & .hljs-comment,\n & .hljs-formula {\n color: #8b949e;\n }\n & .hljs-name,\n & .hljs-quote,\n & .hljs-selector-pseudo,\n & .hljs-selector-tag {\n color: #7ee787;\n }\n & .hljs-subst {\n color: #c9d1d9;\n }\n & .hljs-section {\n color: #1f6feb;\n font-weight: 700;\n }\n & .hljs-bullet {\n color: #f2cc60;\n }\n & .hljs-emphasis {\n color: #c9d1d9;\n font-style: italic;\n }\n & .hljs-strong {\n color: #c9d1d9;\n font-weight: 700;\n }\n & .hljs-addition {\n color: #aff5b4;\n background-color: #033a16;\n }\n & .hljs-deletion {\n color: #ffdcd7;\n background-color: #67060c;\n }\n`;\n\nconst Body = styled.div<{ theme: Theme }>`\n background: #ffffff;\n border-bottom-left-radius: ${({ theme }) => theme.spacing.radius.lg};\n border-bottom-right-radius: ${({ theme }) => theme.spacing.radius.lg};\n color: #24292f;\n padding: 20px;\n font-family: ${({ theme }) => theme.fonts.mono};\n text-align: left;\n overflow-x: auto;\n overflow-y: auto;\n max-height: calc(100dvh - 400px);\n ${thinScrollbar};\n ${({ theme }) => styledCode(theme)};\n ${lightSyntaxHighlight};\n\n :root.dark & {\n background: #0d1117;\n color: #ffffff;\n ${darkSyntaxHighlight};\n }\n`;\n\nconst escapeHtml = (unsafe: string): string => {\n return unsafe\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n};\n\nconst sanitizeLanguage = (lang: string): string =>\n lang.replace(/[^a-zA-Z0-9_-]/g, \"\");\n\nconst highlightCode = (code: string, language: string): string => {\n const escapedCode = escapeHtml(code);\n const safeLang = sanitizeLanguage(language);\n const result = unified()\n .use(rehypeParse, { fragment: true })\n .use(rehypeHighlight, {\n detect: true,\n ignoreMissing: true,\n })\n .use(rehypeStringify)\n .processSync(\n `<pre><code class=\"language-${safeLang}\">${escapedCode}</code></pre>`,\n );\n\n return String(result);\n};\n\nfunction Code({ code, language = \"javascript\", theme, className }: CodeProps) {\n const [copied, setCopied] = useState(false);\n const highlightedCode = useMemo(\n () => highlightCode(code, language),\n [code, language],\n );\n\n const handleCopy = useCallback(async () => {\n try {\n await navigator.clipboard.writeText(code);\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n } catch (err) {\n console.error(\"Failed to copy code:\", err);\n }\n }, [code]);\n\n return (\n <CodeWrapper\n className={`${className ?? \"\"} code-wrapper`.trim()}\n theme={theme}\n >\n <TopBar theme={theme}>\n <DotsContainer>\n <Dot theme={theme} />\n <Dot theme={theme} />\n <Dot theme={theme} />\n </DotsContainer>\n <CopyButton\n onClick={handleCopy}\n $copied={copied}\n theme={theme}\n aria-label={copied ? \"Copied\" : \"Copy code\"}\n >\n <Icon name={copied ? \"check\" : \"copy\"} size={12} />\n </CopyButton>\n </TopBar>\n <Body\n dangerouslySetInnerHTML={{ __html: highlightedCode }}\n theme={theme}\n className=\"code-wrapper-body\"\n />\n </CodeWrapper>\n );\n}\n\nexport { Code };\n";
|
|
1
|
+
export declare const codeTemplate = "\"use client\";\nimport { useState, useCallback, useMemo, useId } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport {\n interactiveStyles,\n resetButton,\n styledCode,\n} from \"cherry-styled-components\";\nimport { Theme } from \"@/app/theme\";\nimport { unified } from \"unified\";\nimport rehypeParse from \"rehype-parse\";\nimport rehypeHighlight from \"rehype-highlight\";\nimport rehypeStringify from \"rehype-stringify\";\nimport { Icon } from \"@/components/layout/Icon\";\nimport { thinScrollbar } from \"@/components/layout/SharedStyled\";\n\ninterface CodeProps extends Omit<\n React.HTMLAttributes<HTMLDivElement>,\n \"theme\"\n> {\n code: string;\n language?: string;\n title?: string;\n theme?: Theme;\n}\n\ninterface CodeTab {\n label: string;\n code: string;\n language?: string;\n}\n\ninterface CodeTabsProps extends Omit<\n React.HTMLAttributes<HTMLDivElement>,\n \"theme\"\n> {\n tabs: CodeTab[];\n theme?: Theme;\n}\n\nconst CodeWrapper = styled.span<{ theme: Theme }>`\n position: relative;\n z-index: 2;\n display: block;\n width: 100%;\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n border: solid 1px rgba(0, 0, 0, 0.1);\n\n :root.dark & {\n border-color: rgba(255, 255, 255, 0.2);\n }\n`;\n\n/* Code block uses a fixed GitHub-style palette in both modes. Independent of\n theme.json so syntax highlighting stays legible regardless of brand colors.\n Dark variants live in :root.dark & blocks so the swap happens via the\n active <html> class with no re-render. */\nconst TopBar = styled.div<{ theme: Theme }>`\n position: relative;\n background: #f6f8fa;\n border-top-left-radius: ${({ theme }) => theme.spacing.radius.lg};\n border-top-right-radius: ${({ theme }) => theme.spacing.radius.lg};\n border-bottom: solid 1px rgba(0, 0, 0, 0.1);\n height: 33px;\n width: 100%;\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: 5px;\n padding: 0 10px;\n\n :root.dark & {\n background: #0d1117;\n border-bottom-color: rgba(255, 255, 255, 0.1);\n }\n`;\n\nconst DotsContainer = styled.div`\n display: flex;\n gap: 5px;\n`;\n\nconst Dot = styled.span<{ theme: Theme }>`\n width: 10px;\n height: 10px;\n border-radius: 50%;\n background: rgba(0, 0, 0, 0.1);\n\n :root.dark & {\n background: rgba(255, 255, 255, 0.1);\n }\n`;\n\n/* Icon-only copy button. interactiveStyles supplies the border highlight on\n hover plus the focus/active rings (no scale effect); the GitHub-style\n copied/base colors stay fixed like the rest of the code block. The dark\n block re-declares the hover border because its higher-specificity\n :root.dark & border-color would otherwise override the mixin's hover. */\nconst CopyButton = styled.button<{ theme: Theme; $copied: boolean }>`\n ${resetButton}\n ${interactiveStyles}\n background: ${({ $copied }) =>\n $copied ? \"rgba(45, 164, 78, 0.1)\" : \"transparent\"};\n border-color: ${({ $copied }) => ($copied ? \"#2da44e\" : \"rgba(0, 0, 0, 0.1)\")};\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n padding: 4px;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-right: -6px;\n\n & svg.lucide {\n color: ${({ $copied }) => ($copied ? \"#2da44e\" : \"#57606a\")};\n }\n\n :root.dark & {\n background: ${({ $copied }) =>\n $copied ? \"rgba(126, 231, 135, 0.2)\" : \"transparent\"};\n border-color: ${({ $copied }) =>\n $copied ? \"#7ee787\" : \"rgba(255, 255, 255, 0.1)\"};\n\n & svg.lucide {\n color: ${({ $copied }) => ($copied ? \"#7ee787\" : \"#c9d1d9\")};\n }\n\n &:hover {\n border-color: ${({ theme }) => theme.colors.primary};\n }\n }\n`;\n\n/* Centered file name in the TopBar. Sits absolutely over the flex row so the\n dots and copy button keep their edge alignment. Monospace + muted GitHub\n grays, swapped via :root.dark like the rest of the block. pointer-events off\n so clicks fall through to nothing and never steal focus from the buttons. */\nconst TopBarTitle = styled.span<{ theme: Theme }>`\n position: absolute;\n left: 50%;\n transform: translateX(-50%);\n max-width: 60%;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n pointer-events: none;\n font-family: ${({ theme }) => theme.fonts.mono};\n font-size: 12px;\n line-height: 2;\n color: #57606a;\n\n :root.dark & {\n color: #8b949e;\n }\n`;\n\n/* Segmented control living in the TopBar for CodeTabs. Scrolls horizontally\n with the thin scrollbar when the labels overflow the 33px bar. */\nconst TabList = styled.div`\n display: flex;\n align-items: center;\n gap: 2px;\n min-width: 0;\n overflow-x: auto;\n ${thinScrollbar};\n margin-left: -6px;\n`;\n\n/* Individual tab button. Active tab reads as part of the window: a light body\n fill (#ffffff) that echoes the code area, with a soft border. Inactive tabs\n are muted and transparent. resetButton strips native styling; focus-visible\n uses the brand primary ring for keyboard users. All colors stay fixed and\n swap purely via :root.dark, matching the no-re-render palette approach. */\nconst CodeTab = styled.button<{ theme: Theme; $active: boolean }>`\n ${resetButton}\n flex: 0 0 auto;\n font-family: ${({ theme }) => theme.fonts.mono};\n font-size: 12px;\n line-height: 1;\n padding: 5px;\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n cursor: pointer;\n white-space: nowrap;\n transition:\n color 0.15s ease,\n background 0.15s ease,\n border-color 0.15s ease;\n border: solid 1px\n ${({ $active }) => ($active ? \"rgba(0, 0, 0, 0.1)\" : \"transparent\")};\n background: ${({ $active }) => ($active ? \"#ffffff\" : \"transparent\")};\n color: ${({ $active }) => ($active ? \"#24292f\" : \"#57606a\")};\n\n &:hover {\n color: #24292f;\n background: ${({ $active }) =>\n $active ? \"#ffffff\" : \"rgba(0, 0, 0, 0.04)\"};\n }\n\n &:focus-visible {\n outline: solid 2px ${({ theme }) => theme.colors.primary};\n outline-offset: 1px;\n }\n\n :root.dark & {\n border-color: ${({ $active }) =>\n $active ? \"rgba(255, 255, 255, 0.15)\" : \"transparent\"};\n background: ${({ $active }) => ($active ? \"#161b22\" : \"transparent\")};\n color: ${({ $active }) => ($active ? \"#e6edf3\" : \"#8b949e\")};\n\n &:hover {\n color: #e6edf3;\n background: ${({ $active }) =>\n $active ? \"#161b22\" : \"rgba(255, 255, 255, 0.06)\"};\n }\n }\n`;\n\n/* GitHub Light syntax highlighting by default; GitHub Dark in :root.dark.\n Browser resolves which rule wins based on the active <html> class with no\n JS or React re-render involved. */\nconst lightSyntaxHighlight = css`\n & .hljs {\n color: #24292f;\n background: #ffffff;\n min-width: min-content;\n width: 100%;\n display: block;\n padding-right: 20px;\n }\n & .hljs-doctag,\n & .hljs-keyword,\n & .hljs-meta .hljs-keyword,\n & .hljs-template-tag,\n & .hljs-template-variable,\n & .hljs-type,\n & .hljs-variable.language_ {\n color: #cf222e;\n }\n & .hljs-title,\n & .hljs-title.class_,\n & .hljs-title.class_.inherited__,\n & .hljs-title.function_ {\n color: #8250df;\n }\n & .hljs-attr,\n & .hljs-attribute,\n & .hljs-literal,\n & .hljs-meta,\n & .hljs-number,\n & .hljs-operator,\n & .hljs-selector-attr,\n & .hljs-selector-class,\n & .hljs-selector-id,\n & .hljs-variable {\n color: #0550ae;\n }\n & .hljs-meta .hljs-string,\n & .hljs-regexp,\n & .hljs-string {\n color: #0a3069;\n }\n & .hljs-built_in,\n & .hljs-symbol {\n color: #953800;\n }\n & .hljs-code,\n & .hljs-comment,\n & .hljs-formula {\n color: #6e7781;\n }\n & .hljs-name,\n & .hljs-quote,\n & .hljs-selector-pseudo,\n & .hljs-selector-tag {\n color: #116329;\n }\n & .hljs-subst {\n color: #24292f;\n }\n & .hljs-section {\n color: #0550ae;\n font-weight: 700;\n }\n & .hljs-bullet {\n color: #953800;\n }\n & .hljs-emphasis {\n color: #24292f;\n font-style: italic;\n }\n & .hljs-strong {\n color: #24292f;\n font-weight: 700;\n }\n & .hljs-addition {\n color: #116329;\n background-color: #dafbe1;\n }\n & .hljs-deletion {\n color: #82071e;\n background-color: #ffebe9;\n }\n`;\n\nconst darkSyntaxHighlight = css`\n & .hljs {\n color: #c9d1d9;\n background: #0d1117;\n }\n & .hljs-doctag,\n & .hljs-keyword,\n & .hljs-meta .hljs-keyword,\n & .hljs-template-tag,\n & .hljs-template-variable,\n & .hljs-type,\n & .hljs-variable.language_ {\n color: #ff7b72;\n }\n & .hljs-title,\n & .hljs-title.class_,\n & .hljs-title.class_.inherited__,\n & .hljs-title.function_ {\n color: #d2a8ff;\n }\n & .hljs-attr,\n & .hljs-attribute,\n & .hljs-literal,\n & .hljs-meta,\n & .hljs-number,\n & .hljs-operator,\n & .hljs-selector-attr,\n & .hljs-selector-class,\n & .hljs-selector-id,\n & .hljs-variable {\n color: #79c0ff;\n }\n & .hljs-meta .hljs-string,\n & .hljs-regexp,\n & .hljs-string {\n color: #a5d6ff;\n }\n & .hljs-built_in,\n & .hljs-symbol {\n color: #ffa657;\n }\n & .hljs-code,\n & .hljs-comment,\n & .hljs-formula {\n color: #8b949e;\n }\n & .hljs-name,\n & .hljs-quote,\n & .hljs-selector-pseudo,\n & .hljs-selector-tag {\n color: #7ee787;\n }\n & .hljs-subst {\n color: #c9d1d9;\n }\n & .hljs-section {\n color: #1f6feb;\n font-weight: 700;\n }\n & .hljs-bullet {\n color: #f2cc60;\n }\n & .hljs-emphasis {\n color: #c9d1d9;\n font-style: italic;\n }\n & .hljs-strong {\n color: #c9d1d9;\n font-weight: 700;\n }\n & .hljs-addition {\n color: #aff5b4;\n background-color: #033a16;\n }\n & .hljs-deletion {\n color: #ffdcd7;\n background-color: #67060c;\n }\n`;\n\nconst Body = styled.div<{ theme: Theme }>`\n background: #ffffff;\n border-bottom-left-radius: ${({ theme }) => theme.spacing.radius.lg};\n border-bottom-right-radius: ${({ theme }) => theme.spacing.radius.lg};\n color: #24292f;\n padding: 20px;\n font-family: ${({ theme }) => theme.fonts.mono};\n text-align: left;\n overflow-x: auto;\n overflow-y: auto;\n max-height: calc(100dvh - 400px);\n ${thinScrollbar};\n ${({ theme }) => styledCode(theme)};\n ${lightSyntaxHighlight};\n\n /* Diff lines: highlight.js wraps each +/- line in a single span, so\n stretching it to the full row reads like a GitHub diff. inline-block keeps\n the trailing newline as the line break instead of adding an empty row. */\n & .hljs-addition,\n & .hljs-deletion {\n display: inline-table;\n width: 100%;\n }\n\n :root.dark & {\n background: #0d1117;\n color: #ffffff;\n ${darkSyntaxHighlight};\n }\n`;\n\nconst escapeHtml = (unsafe: string): string => {\n return unsafe\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n};\n\nconst sanitizeLanguage = (lang: string): string =>\n lang.replace(/[^a-zA-Z0-9_-]/g, \"\");\n\nconst highlightCode = (code: string, language: string): string => {\n const escapedCode = escapeHtml(code);\n const safeLang = sanitizeLanguage(language);\n const result = unified()\n .use(rehypeParse, { fragment: true })\n .use(rehypeHighlight, {\n detect: true,\n ignoreMissing: true,\n })\n .use(rehypeStringify)\n .processSync(\n `<pre><code class=\"language-${safeLang}\">${escapedCode}</code></pre>`,\n );\n\n return String(result);\n};\n\nfunction Code({\n code,\n language = \"javascript\",\n title,\n theme,\n className,\n}: CodeProps) {\n const [copied, setCopied] = useState(false);\n const highlightedCode = useMemo(\n () => highlightCode(code, language),\n [code, language],\n );\n\n const handleCopy = useCallback(async () => {\n try {\n await navigator.clipboard.writeText(code);\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n } catch (err) {\n console.error(\"Failed to copy code:\", err);\n }\n }, [code]);\n\n return (\n <CodeWrapper\n className={`${className ?? \"\"} code-wrapper`.trim()}\n theme={theme}\n >\n <TopBar theme={theme}>\n <DotsContainer>\n <Dot theme={theme} />\n <Dot theme={theme} />\n <Dot theme={theme} />\n </DotsContainer>\n {title ? <TopBarTitle theme={theme}>{title}</TopBarTitle> : null}\n <CopyButton\n onClick={handleCopy}\n $copied={copied}\n theme={theme}\n aria-label={copied ? \"Copied\" : \"Copy code\"}\n >\n <Icon name={copied ? \"check\" : \"copy\"} size={12} />\n </CopyButton>\n </TopBar>\n <Body\n dangerouslySetInnerHTML={{ __html: highlightedCode }}\n theme={theme}\n className=\"code-wrapper-body\"\n />\n </CodeWrapper>\n );\n}\n\n/* Multi-variant code block (npm / pnpm / yarn, etc). Tabs replace the macOS\n dots in the TopBar as a keyboard-accessible tablist; the copy button copies\n whichever tab is active and resets its copied state when the tab changes. */\nfunction CodeTabs({ tabs, theme, className }: CodeTabsProps) {\n const baseId = useId();\n const [active, setActive] = useState(0);\n const [copied, setCopied] = useState(false);\n\n const safeActive = active < tabs.length ? active : 0;\n const activeTab = tabs[safeActive];\n\n const highlightedCode = useMemo(\n () =>\n activeTab\n ? highlightCode(activeTab.code, activeTab.language ?? \"bash\")\n : \"\",\n [activeTab],\n );\n\n const handleSelect = useCallback((index: number) => {\n setActive(index);\n setCopied(false);\n }, []);\n\n const handleCopy = useCallback(async () => {\n if (!activeTab) return;\n try {\n await navigator.clipboard.writeText(activeTab.code);\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n } catch (err) {\n console.error(\"Failed to copy code:\", err);\n }\n }, [activeTab]);\n\n const handleKeyDown = useCallback(\n (event: React.KeyboardEvent<HTMLButtonElement>) => {\n let next = safeActive;\n if (event.key === \"ArrowRight\" || event.key === \"ArrowDown\") {\n next = (safeActive + 1) % tabs.length;\n } else if (event.key === \"ArrowLeft\" || event.key === \"ArrowUp\") {\n next = (safeActive - 1 + tabs.length) % tabs.length;\n } else if (event.key === \"Home\") {\n next = 0;\n } else if (event.key === \"End\") {\n next = tabs.length - 1;\n } else {\n return;\n }\n event.preventDefault();\n handleSelect(next);\n const target = document.getElementById(`${baseId}-tab-${next}`);\n if (target) target.focus();\n },\n [baseId, handleSelect, safeActive, tabs.length],\n );\n\n if (!tabs || tabs.length === 0) return null;\n\n return (\n <CodeWrapper\n className={`${className ?? \"\"} code-wrapper`.trim()}\n theme={theme}\n >\n <TopBar theme={theme}>\n <TabList role=\"tablist\" aria-label=\"Code variants\">\n {tabs.map((tab, index) => (\n <CodeTab\n key={`${tab.label}-${index}`}\n type=\"button\"\n role=\"tab\"\n id={`${baseId}-tab-${index}`}\n aria-selected={index === safeActive}\n aria-controls={`${baseId}-panel`}\n tabIndex={index === safeActive ? 0 : -1}\n onClick={() => handleSelect(index)}\n onKeyDown={handleKeyDown}\n $active={index === safeActive}\n theme={theme}\n >\n {tab.label}\n </CodeTab>\n ))}\n </TabList>\n <CopyButton\n onClick={handleCopy}\n $copied={copied}\n theme={theme}\n aria-label={copied ? \"Copied\" : \"Copy code\"}\n >\n <Icon name={copied ? \"check\" : \"copy\"} size={12} />\n </CopyButton>\n </TopBar>\n <Body\n id={`${baseId}-panel`}\n role=\"tabpanel\"\n aria-labelledby={`${baseId}-tab-${safeActive}`}\n dangerouslySetInnerHTML={{ __html: highlightedCode }}\n theme={theme}\n className=\"code-wrapper-body\"\n />\n </CodeWrapper>\n );\n}\n\nexport { Code, CodeTabs };\n";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export const codeTemplate = `"use client";
|
|
2
|
-
import { useState, useCallback, useMemo } from "react";
|
|
2
|
+
import { useState, useCallback, useMemo, useId } from "react";
|
|
3
3
|
import styled, { css } from "styled-components";
|
|
4
4
|
import {
|
|
5
5
|
interactiveStyles,
|
|
@@ -20,6 +20,21 @@ interface CodeProps extends Omit<
|
|
|
20
20
|
> {
|
|
21
21
|
code: string;
|
|
22
22
|
language?: string;
|
|
23
|
+
title?: string;
|
|
24
|
+
theme?: Theme;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface CodeTab {
|
|
28
|
+
label: string;
|
|
29
|
+
code: string;
|
|
30
|
+
language?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface CodeTabsProps extends Omit<
|
|
34
|
+
React.HTMLAttributes<HTMLDivElement>,
|
|
35
|
+
"theme"
|
|
36
|
+
> {
|
|
37
|
+
tabs: CodeTab[];
|
|
23
38
|
theme?: Theme;
|
|
24
39
|
}
|
|
25
40
|
|
|
@@ -41,6 +56,7 @@ const CodeWrapper = styled.span<{ theme: Theme }>\`
|
|
|
41
56
|
Dark variants live in :root.dark & blocks so the swap happens via the
|
|
42
57
|
active <html> class with no re-render. */
|
|
43
58
|
const TopBar = styled.div<{ theme: Theme }>\`
|
|
59
|
+
position: relative;
|
|
44
60
|
background: #f6f8fa;
|
|
45
61
|
border-top-left-radius: \${({ theme }) => theme.spacing.radius.lg};
|
|
46
62
|
border-top-right-radius: \${({ theme }) => theme.spacing.radius.lg};
|
|
@@ -113,6 +129,90 @@ const CopyButton = styled.button<{ theme: Theme; $copied: boolean }>\`
|
|
|
113
129
|
}
|
|
114
130
|
\`;
|
|
115
131
|
|
|
132
|
+
/* Centered file name in the TopBar. Sits absolutely over the flex row so the
|
|
133
|
+
dots and copy button keep their edge alignment. Monospace + muted GitHub
|
|
134
|
+
grays, swapped via :root.dark like the rest of the block. pointer-events off
|
|
135
|
+
so clicks fall through to nothing and never steal focus from the buttons. */
|
|
136
|
+
const TopBarTitle = styled.span<{ theme: Theme }>\`
|
|
137
|
+
position: absolute;
|
|
138
|
+
left: 50%;
|
|
139
|
+
transform: translateX(-50%);
|
|
140
|
+
max-width: 60%;
|
|
141
|
+
overflow: hidden;
|
|
142
|
+
text-overflow: ellipsis;
|
|
143
|
+
white-space: nowrap;
|
|
144
|
+
pointer-events: none;
|
|
145
|
+
font-family: \${({ theme }) => theme.fonts.mono};
|
|
146
|
+
font-size: 12px;
|
|
147
|
+
line-height: 2;
|
|
148
|
+
color: #57606a;
|
|
149
|
+
|
|
150
|
+
:root.dark & {
|
|
151
|
+
color: #8b949e;
|
|
152
|
+
}
|
|
153
|
+
\`;
|
|
154
|
+
|
|
155
|
+
/* Segmented control living in the TopBar for CodeTabs. Scrolls horizontally
|
|
156
|
+
with the thin scrollbar when the labels overflow the 33px bar. */
|
|
157
|
+
const TabList = styled.div\`
|
|
158
|
+
display: flex;
|
|
159
|
+
align-items: center;
|
|
160
|
+
gap: 2px;
|
|
161
|
+
min-width: 0;
|
|
162
|
+
overflow-x: auto;
|
|
163
|
+
\${thinScrollbar};
|
|
164
|
+
margin-left: -6px;
|
|
165
|
+
\`;
|
|
166
|
+
|
|
167
|
+
/* Individual tab button. Active tab reads as part of the window: a light body
|
|
168
|
+
fill (#ffffff) that echoes the code area, with a soft border. Inactive tabs
|
|
169
|
+
are muted and transparent. resetButton strips native styling; focus-visible
|
|
170
|
+
uses the brand primary ring for keyboard users. All colors stay fixed and
|
|
171
|
+
swap purely via :root.dark, matching the no-re-render palette approach. */
|
|
172
|
+
const CodeTab = styled.button<{ theme: Theme; $active: boolean }>\`
|
|
173
|
+
\${resetButton}
|
|
174
|
+
flex: 0 0 auto;
|
|
175
|
+
font-family: \${({ theme }) => theme.fonts.mono};
|
|
176
|
+
font-size: 12px;
|
|
177
|
+
line-height: 1;
|
|
178
|
+
padding: 5px;
|
|
179
|
+
border-radius: \${({ theme }) => theme.spacing.radius.xs};
|
|
180
|
+
cursor: pointer;
|
|
181
|
+
white-space: nowrap;
|
|
182
|
+
transition:
|
|
183
|
+
color 0.15s ease,
|
|
184
|
+
background 0.15s ease,
|
|
185
|
+
border-color 0.15s ease;
|
|
186
|
+
border: solid 1px
|
|
187
|
+
\${({ $active }) => ($active ? "rgba(0, 0, 0, 0.1)" : "transparent")};
|
|
188
|
+
background: \${({ $active }) => ($active ? "#ffffff" : "transparent")};
|
|
189
|
+
color: \${({ $active }) => ($active ? "#24292f" : "#57606a")};
|
|
190
|
+
|
|
191
|
+
&:hover {
|
|
192
|
+
color: #24292f;
|
|
193
|
+
background: \${({ $active }) =>
|
|
194
|
+
$active ? "#ffffff" : "rgba(0, 0, 0, 0.04)"};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
&:focus-visible {
|
|
198
|
+
outline: solid 2px \${({ theme }) => theme.colors.primary};
|
|
199
|
+
outline-offset: 1px;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
:root.dark & {
|
|
203
|
+
border-color: \${({ $active }) =>
|
|
204
|
+
$active ? "rgba(255, 255, 255, 0.15)" : "transparent"};
|
|
205
|
+
background: \${({ $active }) => ($active ? "#161b22" : "transparent")};
|
|
206
|
+
color: \${({ $active }) => ($active ? "#e6edf3" : "#8b949e")};
|
|
207
|
+
|
|
208
|
+
&:hover {
|
|
209
|
+
color: #e6edf3;
|
|
210
|
+
background: \${({ $active }) =>
|
|
211
|
+
$active ? "#161b22" : "rgba(255, 255, 255, 0.06)"};
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
\`;
|
|
215
|
+
|
|
116
216
|
/* GitHub Light syntax highlighting by default; GitHub Dark in :root.dark.
|
|
117
217
|
Browser resolves which rule wins based on the active <html> class with no
|
|
118
218
|
JS or React re-render involved. */
|
|
@@ -120,6 +220,10 @@ const lightSyntaxHighlight = css\`
|
|
|
120
220
|
& .hljs {
|
|
121
221
|
color: #24292f;
|
|
122
222
|
background: #ffffff;
|
|
223
|
+
min-width: min-content;
|
|
224
|
+
width: 100%;
|
|
225
|
+
display: block;
|
|
226
|
+
padding-right: 20px;
|
|
123
227
|
}
|
|
124
228
|
& .hljs-doctag,
|
|
125
229
|
& .hljs-keyword,
|
|
@@ -291,6 +395,15 @@ const Body = styled.div<{ theme: Theme }>\`
|
|
|
291
395
|
\${({ theme }) => styledCode(theme)};
|
|
292
396
|
\${lightSyntaxHighlight};
|
|
293
397
|
|
|
398
|
+
/* Diff lines: highlight.js wraps each +/- line in a single span, so
|
|
399
|
+
stretching it to the full row reads like a GitHub diff. inline-block keeps
|
|
400
|
+
the trailing newline as the line break instead of adding an empty row. */
|
|
401
|
+
& .hljs-addition,
|
|
402
|
+
& .hljs-deletion {
|
|
403
|
+
display: inline-table;
|
|
404
|
+
width: 100%;
|
|
405
|
+
}
|
|
406
|
+
|
|
294
407
|
:root.dark & {
|
|
295
408
|
background: #0d1117;
|
|
296
409
|
color: #ffffff;
|
|
@@ -327,7 +440,13 @@ const highlightCode = (code: string, language: string): string => {
|
|
|
327
440
|
return String(result);
|
|
328
441
|
};
|
|
329
442
|
|
|
330
|
-
function Code({
|
|
443
|
+
function Code({
|
|
444
|
+
code,
|
|
445
|
+
language = "javascript",
|
|
446
|
+
title,
|
|
447
|
+
theme,
|
|
448
|
+
className,
|
|
449
|
+
}: CodeProps) {
|
|
331
450
|
const [copied, setCopied] = useState(false);
|
|
332
451
|
const highlightedCode = useMemo(
|
|
333
452
|
() => highlightCode(code, language),
|
|
@@ -355,6 +474,109 @@ function Code({ code, language = "javascript", theme, className }: CodeProps) {
|
|
|
355
474
|
<Dot theme={theme} />
|
|
356
475
|
<Dot theme={theme} />
|
|
357
476
|
</DotsContainer>
|
|
477
|
+
{title ? <TopBarTitle theme={theme}>{title}</TopBarTitle> : null}
|
|
478
|
+
<CopyButton
|
|
479
|
+
onClick={handleCopy}
|
|
480
|
+
$copied={copied}
|
|
481
|
+
theme={theme}
|
|
482
|
+
aria-label={copied ? "Copied" : "Copy code"}
|
|
483
|
+
>
|
|
484
|
+
<Icon name={copied ? "check" : "copy"} size={12} />
|
|
485
|
+
</CopyButton>
|
|
486
|
+
</TopBar>
|
|
487
|
+
<Body
|
|
488
|
+
dangerouslySetInnerHTML={{ __html: highlightedCode }}
|
|
489
|
+
theme={theme}
|
|
490
|
+
className="code-wrapper-body"
|
|
491
|
+
/>
|
|
492
|
+
</CodeWrapper>
|
|
493
|
+
);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/* Multi-variant code block (npm / pnpm / yarn, etc). Tabs replace the macOS
|
|
497
|
+
dots in the TopBar as a keyboard-accessible tablist; the copy button copies
|
|
498
|
+
whichever tab is active and resets its copied state when the tab changes. */
|
|
499
|
+
function CodeTabs({ tabs, theme, className }: CodeTabsProps) {
|
|
500
|
+
const baseId = useId();
|
|
501
|
+
const [active, setActive] = useState(0);
|
|
502
|
+
const [copied, setCopied] = useState(false);
|
|
503
|
+
|
|
504
|
+
const safeActive = active < tabs.length ? active : 0;
|
|
505
|
+
const activeTab = tabs[safeActive];
|
|
506
|
+
|
|
507
|
+
const highlightedCode = useMemo(
|
|
508
|
+
() =>
|
|
509
|
+
activeTab
|
|
510
|
+
? highlightCode(activeTab.code, activeTab.language ?? "bash")
|
|
511
|
+
: "",
|
|
512
|
+
[activeTab],
|
|
513
|
+
);
|
|
514
|
+
|
|
515
|
+
const handleSelect = useCallback((index: number) => {
|
|
516
|
+
setActive(index);
|
|
517
|
+
setCopied(false);
|
|
518
|
+
}, []);
|
|
519
|
+
|
|
520
|
+
const handleCopy = useCallback(async () => {
|
|
521
|
+
if (!activeTab) return;
|
|
522
|
+
try {
|
|
523
|
+
await navigator.clipboard.writeText(activeTab.code);
|
|
524
|
+
setCopied(true);
|
|
525
|
+
setTimeout(() => setCopied(false), 2000);
|
|
526
|
+
} catch (err) {
|
|
527
|
+
console.error("Failed to copy code:", err);
|
|
528
|
+
}
|
|
529
|
+
}, [activeTab]);
|
|
530
|
+
|
|
531
|
+
const handleKeyDown = useCallback(
|
|
532
|
+
(event: React.KeyboardEvent<HTMLButtonElement>) => {
|
|
533
|
+
let next = safeActive;
|
|
534
|
+
if (event.key === "ArrowRight" || event.key === "ArrowDown") {
|
|
535
|
+
next = (safeActive + 1) % tabs.length;
|
|
536
|
+
} else if (event.key === "ArrowLeft" || event.key === "ArrowUp") {
|
|
537
|
+
next = (safeActive - 1 + tabs.length) % tabs.length;
|
|
538
|
+
} else if (event.key === "Home") {
|
|
539
|
+
next = 0;
|
|
540
|
+
} else if (event.key === "End") {
|
|
541
|
+
next = tabs.length - 1;
|
|
542
|
+
} else {
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
event.preventDefault();
|
|
546
|
+
handleSelect(next);
|
|
547
|
+
const target = document.getElementById(\`\${baseId}-tab-\${next}\`);
|
|
548
|
+
if (target) target.focus();
|
|
549
|
+
},
|
|
550
|
+
[baseId, handleSelect, safeActive, tabs.length],
|
|
551
|
+
);
|
|
552
|
+
|
|
553
|
+
if (!tabs || tabs.length === 0) return null;
|
|
554
|
+
|
|
555
|
+
return (
|
|
556
|
+
<CodeWrapper
|
|
557
|
+
className={\`\${className ?? ""} code-wrapper\`.trim()}
|
|
558
|
+
theme={theme}
|
|
559
|
+
>
|
|
560
|
+
<TopBar theme={theme}>
|
|
561
|
+
<TabList role="tablist" aria-label="Code variants">
|
|
562
|
+
{tabs.map((tab, index) => (
|
|
563
|
+
<CodeTab
|
|
564
|
+
key={\`\${tab.label}-\${index}\`}
|
|
565
|
+
type="button"
|
|
566
|
+
role="tab"
|
|
567
|
+
id={\`\${baseId}-tab-\${index}\`}
|
|
568
|
+
aria-selected={index === safeActive}
|
|
569
|
+
aria-controls={\`\${baseId}-panel\`}
|
|
570
|
+
tabIndex={index === safeActive ? 0 : -1}
|
|
571
|
+
onClick={() => handleSelect(index)}
|
|
572
|
+
onKeyDown={handleKeyDown}
|
|
573
|
+
$active={index === safeActive}
|
|
574
|
+
theme={theme}
|
|
575
|
+
>
|
|
576
|
+
{tab.label}
|
|
577
|
+
</CodeTab>
|
|
578
|
+
))}
|
|
579
|
+
</TabList>
|
|
358
580
|
<CopyButton
|
|
359
581
|
onClick={handleCopy}
|
|
360
582
|
$copied={copied}
|
|
@@ -365,6 +587,9 @@ function Code({ code, language = "javascript", theme, className }: CodeProps) {
|
|
|
365
587
|
</CopyButton>
|
|
366
588
|
</TopBar>
|
|
367
589
|
<Body
|
|
590
|
+
id={\`\${baseId}-panel\`}
|
|
591
|
+
role="tabpanel"
|
|
592
|
+
aria-labelledby={\`\${baseId}-tab-\${safeActive}\`}
|
|
368
593
|
dangerouslySetInnerHTML={{ __html: highlightedCode }}
|
|
369
594
|
theme={theme}
|
|
370
595
|
className="code-wrapper-body"
|
|
@@ -373,5 +598,5 @@ function Code({ code, language = "javascript", theme, className }: CodeProps) {
|
|
|
373
598
|
);
|
|
374
599
|
}
|
|
375
600
|
|
|
376
|
-
export { Code };
|
|
601
|
+
export { Code, CodeTabs };
|
|
377
602
|
`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const codeMdxTemplate = "---\ntitle: \"Code\"\ndescription: \"Learn how to display inline code and code blocks in documentation.\"\ndate: \"2026-02-19\"\ncategory: \"Components\"\ncategoryOrder: 1\norder: 3\n---\n\n# Code\n\nLearn how to display inline code and code blocks in documentation.\n\n## Adding Code Samples\n\nBoth inline code snippets and full code blocks are supported. Code blocks offer customization for syntax highlighting and more to improve readability and user experience.\n\n### Inline Code\n\nHighlight code within text by wrapping it with backticks:\n\n```text\nEnclose any `word` or `phrase` in backticks to format it as code.\n```\n\nEnclose any `word` or `phrase` in backticks to format it as code.\n\n## Code Blocks\n\nTo present larger code samples, use triple backticks for fenced code blocks. Each block can be copied, and\u2014if assistant features are enabled\u2014users can request explanations.\n\nYou may specify the language for highlighting:\n\n````text\n```java\nclass HelloWorld {\n public static void main(String[] args) {\n System.out.println(\"Hello, World!\");\n }\n}\n```\n````\n\n```java\nclass HelloWorld {\n public static void main(String[] args) {\n System.out.println(\"Hello, World!\");\n }\n}\n```";
|
|
1
|
+
export declare const codeMdxTemplate = "---\ntitle: \"Code\"\ndescription: \"Learn how to display inline code and code blocks in documentation.\"\ndate: \"2026-02-19\"\ncategory: \"Components\"\ncategoryOrder: 1\norder: 3\n---\n\n# Code\n\nLearn how to display inline code and code blocks in documentation.\n\n## Adding Code Samples\n\nBoth inline code snippets and full code blocks are supported. Code blocks offer customization for syntax highlighting and more to improve readability and user experience.\n\n### Inline Code\n\nHighlight code within text by wrapping it with backticks:\n\n```text\nEnclose any `word` or `phrase` in backticks to format it as code.\n```\n\nEnclose any `word` or `phrase` in backticks to format it as code.\n\n## Code Blocks\n\nTo present larger code samples, use triple backticks for fenced code blocks. Each block can be copied, and\u2014if assistant features are enabled\u2014users can request explanations.\n\nYou may specify the language for highlighting:\n\n````text\n```java\nclass HelloWorld {\n public static void main(String[] args) {\n System.out.println(\"Hello, World!\");\n }\n}\n```\n````\n\n```java\nclass HelloWorld {\n public static void main(String[] args) {\n System.out.println(\"Hello, World!\");\n }\n}\n```\n\n## Highlighting Diffs\n\nShow a visual diff of added or removed lines in your code blocks. Added lines are highlighted in green and removed lines are highlighted in red. Set the language to `diff` and prefix each changed line with `+` or `-`:\n\n````text\n```diff\nfunction calculateTotal(items) {\n- return items.reduce((sum, item) => sum + item.price, 0);\n+ const total = items.reduce((sum, item) => sum + item.price, 0);\n+ return Math.round(total * 100) / 100;\n}\n```\n````\n\n```diff\nfunction calculateTotal(items) {\n- return items.reduce((sum, item) => sum + item.price, 0);\n+ const total = items.reduce((sum, item) => sum + item.price, 0);\n+ return Math.round(total * 100) / 100;\n}\n```\n\n## File Names\n\nAdd a `title` to a code block to display a file name in the window bar, styled to match the GitHub-style header. Pass it through the `<Code />` component:\n\n```text\n<Code title=\"package.json\" language=\"json\" code={`{\\n \"name\": \"my-app\",\\n \"version\": \"1.0.0\"\\n}`} />\n```\n\n<Code title=\"package.json\" language=\"json\" code={`{\\n \"name\": \"my-app\",\\n \"version\": \"1.0.0\"\\n}`} />\n\n## Tabbed Code Blocks\n\nUse `<CodeTabs />` to show several variants of the same snippet - for example the same install command across package managers. Each tab is a keyboard-accessible button, and the copy button copies whichever tab is active. Each tab may set a `language` for highlighting (defaults to `bash`).\n\n```text\n<CodeTabs tabs={[{ label: \"npm\", code: \"npm install doccupine\" }, { label: \"pnpm\", code: \"pnpm add doccupine\" }, { label: \"yarn\", code: \"yarn add doccupine\" }]} />\n```\n\n<CodeTabs tabs={[{ label: \"npm\", code: \"npm install doccupine\" }, { label: \"pnpm\", code: \"pnpm add doccupine\" }, { label: \"yarn\", code: \"yarn add doccupine\" }]} />";
|
|
@@ -47,4 +47,46 @@ class HelloWorld {
|
|
|
47
47
|
System.out.println("Hello, World!");
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
|
-
|
|
50
|
+
\`\`\`
|
|
51
|
+
|
|
52
|
+
## Highlighting Diffs
|
|
53
|
+
|
|
54
|
+
Show a visual diff of added or removed lines in your code blocks. Added lines are highlighted in green and removed lines are highlighted in red. Set the language to \`diff\` and prefix each changed line with \`+\` or \`-\`:
|
|
55
|
+
|
|
56
|
+
\`\`\`\`text
|
|
57
|
+
\`\`\`diff
|
|
58
|
+
function calculateTotal(items) {
|
|
59
|
+
- return items.reduce((sum, item) => sum + item.price, 0);
|
|
60
|
+
+ const total = items.reduce((sum, item) => sum + item.price, 0);
|
|
61
|
+
+ return Math.round(total * 100) / 100;
|
|
62
|
+
}
|
|
63
|
+
\`\`\`
|
|
64
|
+
\`\`\`\`
|
|
65
|
+
|
|
66
|
+
\`\`\`diff
|
|
67
|
+
function calculateTotal(items) {
|
|
68
|
+
- return items.reduce((sum, item) => sum + item.price, 0);
|
|
69
|
+
+ const total = items.reduce((sum, item) => sum + item.price, 0);
|
|
70
|
+
+ return Math.round(total * 100) / 100;
|
|
71
|
+
}
|
|
72
|
+
\`\`\`
|
|
73
|
+
|
|
74
|
+
## File Names
|
|
75
|
+
|
|
76
|
+
Add a \`title\` to a code block to display a file name in the window bar, styled to match the GitHub-style header. Pass it through the \`<Code />\` component:
|
|
77
|
+
|
|
78
|
+
\`\`\`text
|
|
79
|
+
<Code title="package.json" language="json" code={\`{\\n "name": "my-app",\\n "version": "1.0.0"\\n}\`} />
|
|
80
|
+
\`\`\`
|
|
81
|
+
|
|
82
|
+
<Code title="package.json" language="json" code={\`{\\n "name": "my-app",\\n "version": "1.0.0"\\n}\`} />
|
|
83
|
+
|
|
84
|
+
## Tabbed Code Blocks
|
|
85
|
+
|
|
86
|
+
Use \`<CodeTabs />\` to show several variants of the same snippet - for example the same install command across package managers. Each tab is a keyboard-accessible button, and the copy button copies whichever tab is active. Each tab may set a \`language\` for highlighting (defaults to \`bash\`).
|
|
87
|
+
|
|
88
|
+
\`\`\`text
|
|
89
|
+
<CodeTabs tabs={[{ label: "npm", code: "npm install doccupine" }, { label: "pnpm", code: "pnpm add doccupine" }, { label: "yarn", code: "yarn add doccupine" }]} />
|
|
90
|
+
\`\`\`
|
|
91
|
+
|
|
92
|
+
<CodeTabs tabs={[{ label: "npm", code: "npm install doccupine" }, { label: "pnpm", code: "pnpm add doccupine" }, { label: "yarn", code: "yarn add doccupine" }]} />`;
|
|
@@ -39,7 +39,7 @@ export const packageJsonTemplate = JSON.stringify({
|
|
|
39
39
|
"@types/node": "^26",
|
|
40
40
|
"@types/react": "^19",
|
|
41
41
|
"@types/react-dom": "^19",
|
|
42
|
-
"baseline-browser-mapping": "^2.10.
|
|
42
|
+
"baseline-browser-mapping": "^2.10.42",
|
|
43
43
|
eslint: "^9",
|
|
44
44
|
"eslint-config-next": "16.2.10",
|
|
45
45
|
prettier: "^3.9.4",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "doccupine",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.99",
|
|
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": {
|
package/dist/index.test.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/index.test.js
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { generateSlug, escapeTemplateContent } from "./index.js";
|
|
3
|
-
describe("generateSlug", () => {
|
|
4
|
-
it("returns empty string for index.mdx", () => {
|
|
5
|
-
expect(generateSlug("index.mdx")).toBe("");
|
|
6
|
-
expect(generateSlug("./index.mdx")).toBe("");
|
|
7
|
-
});
|
|
8
|
-
it("strips .mdx extension", () => {
|
|
9
|
-
expect(generateSlug("getting-started.mdx")).toBe("getting-started");
|
|
10
|
-
});
|
|
11
|
-
it("lowercases the slug", () => {
|
|
12
|
-
expect(generateSlug("MyPage.mdx")).toBe("mypage");
|
|
13
|
-
});
|
|
14
|
-
it("replaces special characters with hyphens", () => {
|
|
15
|
-
expect(generateSlug("hello world.mdx")).toBe("hello-world");
|
|
16
|
-
});
|
|
17
|
-
it("preserves forward slashes for nested paths", () => {
|
|
18
|
-
expect(generateSlug("guides/setup.mdx")).toBe("guides/setup");
|
|
19
|
-
});
|
|
20
|
-
it("converts backslashes to forward slashes", () => {
|
|
21
|
-
expect(generateSlug("guides\\setup.mdx")).toBe("guides/setup");
|
|
22
|
-
});
|
|
23
|
-
it("preserves underscores and hyphens", () => {
|
|
24
|
-
expect(generateSlug("my_page-name.mdx")).toBe("my_page-name");
|
|
25
|
-
});
|
|
26
|
-
});
|
|
27
|
-
describe("escapeTemplateContent", () => {
|
|
28
|
-
it("escapes backticks", () => {
|
|
29
|
-
expect(escapeTemplateContent("hello `world`")).toBe("hello \\`world\\`");
|
|
30
|
-
});
|
|
31
|
-
it("escapes template literal expressions", () => {
|
|
32
|
-
expect(escapeTemplateContent("${variable}")).toBe("\\${variable}");
|
|
33
|
-
});
|
|
34
|
-
it("escapes backslashes", () => {
|
|
35
|
-
expect(escapeTemplateContent("path\\to\\file")).toBe("path\\\\to\\\\file");
|
|
36
|
-
});
|
|
37
|
-
it("escapes all dangerous characters together", () => {
|
|
38
|
-
const input = "Use `${process.env.SECRET}` here";
|
|
39
|
-
const result = escapeTemplateContent(input);
|
|
40
|
-
expect(result).toBe("Use \\`\\${process.env.SECRET}\\` here");
|
|
41
|
-
});
|
|
42
|
-
it("handles content with no special characters", () => {
|
|
43
|
-
expect(escapeTemplateContent("plain text")).toBe("plain text");
|
|
44
|
-
});
|
|
45
|
-
it("handles empty string", () => {
|
|
46
|
-
expect(escapeTemplateContent("")).toBe("");
|
|
47
|
-
});
|
|
48
|
-
});
|
package/dist/metadata.d.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
export interface MetadataOptions {
|
|
2
|
-
title?: string;
|
|
3
|
-
titleFallback: string;
|
|
4
|
-
name?: string;
|
|
5
|
-
titleOrder: "page-first" | "name-first";
|
|
6
|
-
description?: string;
|
|
7
|
-
icon?: string;
|
|
8
|
-
image?: string;
|
|
9
|
-
}
|
|
10
|
-
export declare function generateMetadataBlock(opts: MetadataOptions): string;
|
|
11
|
-
export declare function generateRuntimeOnlyMetadataBlock(): string;
|
package/dist/metadata.js
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { DEFAULT_FAVICON, DEFAULT_META_DESCRIPTION, DEFAULT_OG_IMAGE, DEFAULT_SITE_NAME, } from "./templates/constants.js";
|
|
2
|
-
function buildFieldExpression(staticValue, configKey, defaultValue) {
|
|
3
|
-
if (staticValue)
|
|
4
|
-
return staticValue;
|
|
5
|
-
return `\${config.${configKey} || "${defaultValue}"}`;
|
|
6
|
-
}
|
|
7
|
-
function buildTitleExpression(opts) {
|
|
8
|
-
const title = opts.title || opts.titleFallback;
|
|
9
|
-
if (opts.titleOrder === "page-first") {
|
|
10
|
-
const suffix = opts.name
|
|
11
|
-
? `- ${opts.name}`
|
|
12
|
-
: `\${config.name ? "- " + config.name : "- ${DEFAULT_SITE_NAME}"}`;
|
|
13
|
-
return `${title} ${suffix}`;
|
|
14
|
-
}
|
|
15
|
-
// name-first
|
|
16
|
-
const prefix = opts.name
|
|
17
|
-
? `${opts.name} -`
|
|
18
|
-
: `\${config.name ? config.name + " -" : "${DEFAULT_SITE_NAME} -"}`;
|
|
19
|
-
return `${prefix} ${title}`;
|
|
20
|
-
}
|
|
21
|
-
export function generateMetadataBlock(opts) {
|
|
22
|
-
const title = buildTitleExpression(opts);
|
|
23
|
-
const desc = buildFieldExpression(opts.description, "description", DEFAULT_META_DESCRIPTION);
|
|
24
|
-
const icon = buildFieldExpression(opts.icon, "icon", DEFAULT_FAVICON);
|
|
25
|
-
const image = buildFieldExpression(opts.image, "image", DEFAULT_OG_IMAGE);
|
|
26
|
-
return `export const metadata: Metadata = {
|
|
27
|
-
title: \`${title}\`,
|
|
28
|
-
description: \`${desc}\`,
|
|
29
|
-
icons: \`${icon}\`,
|
|
30
|
-
openGraph: {
|
|
31
|
-
title: \`${title}\`,
|
|
32
|
-
description: \`${desc}\`,
|
|
33
|
-
images: \`${image}\`,
|
|
34
|
-
},
|
|
35
|
-
};`;
|
|
36
|
-
}
|
|
37
|
-
export function generateRuntimeOnlyMetadataBlock() {
|
|
38
|
-
const title = `\${config.name || "${DEFAULT_SITE_NAME}"}`;
|
|
39
|
-
const desc = `\${config.description || "${DEFAULT_META_DESCRIPTION}"}`;
|
|
40
|
-
const icon = `\${config.icon || "${DEFAULT_FAVICON}"}`;
|
|
41
|
-
const image = `\${config.image || "${DEFAULT_OG_IMAGE}"}`;
|
|
42
|
-
return `export const metadata: Metadata = {
|
|
43
|
-
title: \`${title}\`,
|
|
44
|
-
description: \`${desc}\`,
|
|
45
|
-
icons: \`${icon}\`,
|
|
46
|
-
openGraph: {
|
|
47
|
-
title: \`${title}\`,
|
|
48
|
-
description: \`${desc}\`,
|
|
49
|
-
images: \`${image}\`,
|
|
50
|
-
},
|
|
51
|
-
};`;
|
|
52
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const accordionMdxTemplate = "---\ntitle: \"Accordion\"\ndescription: \"Doccupine CLI commands\"\ndate: \"2025-01-15\"\ncategory: \"Components\"\ncategoryOrder: 1\norder: 4\n---\n# Accordion\nInteractive panels for toggling visibility of content.\n\nAccordion elements help organize information by letting users show or hide sections as needed. They\u2019re an effective way to manage progressive disclosure and simplify navigation through dense or optional content.\n\n## Accordion Usage\nYou can use the Accordion component directly within your MDX files without any import. The following example shows a basic usage:\n\n~~~mdx\n<Accordion title=\"What is MDX?\">\n You can put any content in here, including other components, like code:\n\n ```java HelloWorld.java\n class HelloWorld {\n public static void main(String[] args) {\n System.out.println(\"Hello, World!\");\n }\n }\n ```\n</Accordion>\n~~~\n\n<Accordion title=\"What is MDX?\">\n You can put any content in here, including other components, like code:\n\n ```java HelloWorld.java\n class HelloWorld {\n public static void main(String[] args) {\n System.out.println(\"Hello, World!\");\n }\n }\n ```\n</Accordion>";
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
export const accordionMdxTemplate = `---
|
|
2
|
-
title: "Accordion"
|
|
3
|
-
description: "Doccupine CLI commands"
|
|
4
|
-
date: "2025-01-15"
|
|
5
|
-
category: "Components"
|
|
6
|
-
categoryOrder: 1
|
|
7
|
-
order: 4
|
|
8
|
-
---
|
|
9
|
-
# Accordion
|
|
10
|
-
Interactive panels for toggling visibility of content.
|
|
11
|
-
|
|
12
|
-
Accordion elements help organize information by letting users show or hide sections as needed. They’re an effective way to manage progressive disclosure and simplify navigation through dense or optional content.
|
|
13
|
-
|
|
14
|
-
## Accordion Usage
|
|
15
|
-
You can use the Accordion component directly within your MDX files without any import. The following example shows a basic usage:
|
|
16
|
-
|
|
17
|
-
~~~mdx
|
|
18
|
-
<Accordion title="What is MDX?">
|
|
19
|
-
You can put any content in here, including other components, like code:
|
|
20
|
-
|
|
21
|
-
\`\`\`java HelloWorld.java
|
|
22
|
-
class HelloWorld {
|
|
23
|
-
public static void main(String[] args) {
|
|
24
|
-
System.out.println("Hello, World!");
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
\`\`\`
|
|
28
|
-
</Accordion>
|
|
29
|
-
~~~
|
|
30
|
-
|
|
31
|
-
<Accordion title="What is MDX?">
|
|
32
|
-
You can put any content in here, including other components, like code:
|
|
33
|
-
|
|
34
|
-
\`\`\`java HelloWorld.java
|
|
35
|
-
class HelloWorld {
|
|
36
|
-
public static void main(String[] args) {
|
|
37
|
-
System.out.println("Hello, World!");
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
\`\`\`
|
|
41
|
-
</Accordion>`;
|