From a071f0cc75700ccc1e5646c557f3650a17005e82 Mon Sep 17 00:00:00 2001 From: Baptiste Parmantier Date: Fri, 12 Dec 2025 09:14:09 +0100 Subject: [PATCH 1/3] wip --- astro.config.mjs | 21 +- .../foundamentals/components/card.mdx | 69 +++--- .../foundamentals/components/code-block.mdx | 40 ++-- .../foundamentals/components/markdown.mdx | 22 +- content/index.mdx | 7 +- explainer.config.ts | 11 + src/assets/css/markdown.css | 20 +- .../content/code-group/code-group.astro | 13 ++ .../content/code-group/code-group.tsx | 202 ++++++++++++++++++ .../plugins/code-group/CodeGroupWrapper.astro | 21 -- .../code-group/code-group-component.tsx | 91 -------- src/lib/plugins/code-group/plugin.ts | 151 ------------- src/lib/plugins/parser/block-renderer.astro | 68 ------ src/lib/plugins/parser/dynamic-block.astro | 23 -- src/lib/plugins/parser/plugin.ts | 126 +---------- src/lib/plugins/parser/selectors.ts | 25 --- src/lib/plugins/read-more/remark-directive.ts | 18 +- .../plugins/shiki/transformer-meta-label.ts | 27 +++ src/lib/utils.ts | 5 + src/pages/docs/[...slug].astro | 16 +- 20 files changed, 360 insertions(+), 616 deletions(-) create mode 100644 src/lib/components/content/code-group/code-group.astro create mode 100644 src/lib/components/content/code-group/code-group.tsx delete mode 100644 src/lib/plugins/code-group/CodeGroupWrapper.astro delete mode 100644 src/lib/plugins/code-group/code-group-component.tsx delete mode 100644 src/lib/plugins/code-group/plugin.ts delete mode 100644 src/lib/plugins/parser/block-renderer.astro delete mode 100644 src/lib/plugins/parser/dynamic-block.astro delete mode 100644 src/lib/plugins/parser/selectors.ts create mode 100644 src/lib/plugins/shiki/transformer-meta-label.ts diff --git a/astro.config.mjs b/astro.config.mjs index dc48169..3c1ee64 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -15,16 +15,13 @@ import tailwindcss from "@tailwindcss/vite"; import icon from "astro-icon"; import rehypeCallouts from "rehype-callouts"; import rehypeMermaid from "rehype-mermaid"; -import rehypeCodeGroupReact from "./src/lib/plugins/code-group/plugin"; -import rehypeReadMoreReact from "./src/lib/plugins/read-more/plugin"; import rehypeBlogListReact from "./src/lib/plugins/blog-list/plugin"; -import rehypeBlock from "./src/lib/plugins/parser/plugin"; -import { - default as remarkDirective, - default as remarkReadMoreDirective, -} from "./src/lib/plugins/read-more/remark-directive"; +import remarkBlockParser from "./src/lib/plugins/parser/plugin"; +import remarkDirectivePkg from "remark-directive"; +import remarkReadMoreDirective from "./src/lib/plugins/read-more/remark-directive"; import sitemap from "@astrojs/sitemap"; import { buildDocIntegration } from "./src/hooks/build-doc"; +import transformerMetaLabel from "./src/lib/plugins/shiki/transformer-meta-label"; export default defineConfig({ site: "https://example.com", @@ -45,15 +42,19 @@ export default defineConfig({ transformerNotationFocus(), transformerNotationErrorLevel(), transformerMetaHighlight(), + transformerMetaLabel(), ], }, syntaxHighlight: { type: "shiki", excludeLangs: ["mermaid"], }, - remarkPlugins: [remarkDirective, remarkReadMoreDirective], + remarkPlugins: [ + remarkDirectivePkg, + remarkBlockParser, + remarkReadMoreDirective, + ], rehypePlugins: [ - rehypeBlock, rehypeMermaid, [ rehypeCallouts, @@ -65,8 +66,6 @@ export default defineConfig({ }, }, ], - rehypeCodeGroupReact, - rehypeReadMoreReact, rehypeBlogListReact, ], }, diff --git a/content/docs/documentation/foundamentals/components/card.mdx b/content/docs/documentation/foundamentals/components/card.mdx index 1ad7601..88ead59 100644 --- a/content/docs/documentation/foundamentals/components/card.mdx +++ b/content/docs/documentation/foundamentals/components/card.mdx @@ -10,26 +10,27 @@ icon: lucide:grid-2x2 This component allows you to group text elements while providing visual impact. ``` -:::card-group - :::card {label="Lorem ipsum dolor sit amet", icon="lucide:bell"} +::::card-group + :::card{label="Lorem ipsum dolor sit amet" icon="lucide:bell"} Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ::: - :::card {label="Lorem ipsum dolor sit amet", icon="lucide:bell"} + + :::card{label="Lorem ipsum dolor sit amet" icon="lucide:bell"} Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ::: -::: +:::: ``` -:::card-group {cols=2} -:::card {label="Lorem ipsum dolor sit amet", icon="lucide:bell"} +::::card-group{cols=2} +:::card{label="Lorem ipsum dolor sit amet" icon="lucide:bell"} Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ::: -:::card {label="Lorem ipsum dolor sit amet", icon="lucide:bell"} +:::card{label="Lorem ipsum dolor sit amet" icon="lucide:bell"} Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ::: -::: +:::: --- @@ -43,57 +44,57 @@ You can customize the number of elements per line by setting the `cols` prop. > Default card per line was fixed to 2 ``` -:::card-group {cols=4} - :::card {label="Lorem ipsum dolor sit amet", icon="lucide:bell"} +::::card-group{cols=4} + :::card{label="Lorem ipsum dolor sit amet" icon="lucide:bell"} Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ::: - :::card {label="Lorem ipsum dolor sit amet", icon="lucide:bell"} + :::card{label="Lorem ipsum dolor sit amet" icon="lucide:bell"} Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ::: -::: +:::: ``` -:::card-group {cols=4} -:::card {label="Lorem ipsum dolor sit amet", icon="lucide:bell"} +::::card-group{cols=4} +:::card{label="Lorem ipsum dolor sit amet" icon="lucide:bell"} Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ::: -:::card {label="Lorem ipsum dolor sit amet", icon="lucide:bell"} +:::card{label="Lorem ipsum dolor sit amet" icon="lucide:bell"} Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ::: -::: +:::: If the elements cannot be aligned horizontally, they will be moved to the next line. ``` -:::card-group {cols=2} - :::card {label="Lorem ipsum dolor sit amet", icon="lucide:bell"} +::::card-group{cols=2} + :::card{label="Lorem ipsum dolor sit amet" icon="lucide:bell"} Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ::: - :::card {label="Lorem ipsum dolor sit amet", icon="lucide:bell"} + :::card{label="Lorem ipsum dolor sit amet" icon="lucide:bell"} Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ::: - :::card {label="Lorem ipsum dolor sit amet", icon="lucide:bell"} + :::card{label="Lorem ipsum dolor sit amet" icon="lucide:bell"} Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ::: -::: +:::: ``` -:::card-group {cols=2} -:::card {label="Lorem ipsum dolor sit amet", icon="lucide:bell"} +::::card-group{cols=2} +:::card{label="Lorem ipsum dolor sit amet" icon="lucide:bell"} Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ::: -:::card {label="Lorem ipsum dolor sit amet", icon="lucide:bell"} +:::card{label="Lorem ipsum dolor sit amet" icon="lucide:bell"} Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ::: -:::card {label="Lorem ipsum dolor sit amet", icon="lucide:bell"} +:::card{label="Lorem ipsum dolor sit amet" icon="lucide:bell"} Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ::: -::: +:::: ### Card item @@ -106,23 +107,23 @@ Vous pouvez personaliser chacune de vos cartes en utilisant les props suivantes > The cards use the [`iconify`](https://icon-sets.iconify.design/) library to display icons. ``` -:::card-group - :::card {label="Lorem ipsum dolor sit amet", icon="lucide:bell"} +::::card-group + :::card{label="Lorem ipsum dolor sit amet" icon="lucide:bell"} Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ::: - :::card {label="Lorem ipsum dolor sit amet", icon="lucide:user"} + :::card{label="Lorem ipsum dolor sit amet" icon="lucide:user"} Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ::: -::: +:::: ``` -:::card-group {cols=2} -:::card {label="Lorem ipsum dolor sit amet", icon="lucide:bell"} +::::card-group{cols=2} +:::card{label="Lorem ipsum dolor sit amet" icon="lucide:bell"} Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ::: -:::card {label="Lorem ipsum dolor sit amet", icon="lucide:user"} +:::card{label="Lorem ipsum dolor sit amet" icon="lucide:user"} Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ::: -::: +:::: diff --git a/content/docs/documentation/foundamentals/components/code-block.mdx b/content/docs/documentation/foundamentals/components/code-block.mdx index 29e315f..7ae81bd 100644 --- a/content/docs/documentation/foundamentals/components/code-block.mdx +++ b/content/docs/documentation/foundamentals/components/code-block.mdx @@ -110,39 +110,44 @@ impl User { ## Group code blocks -Group code blocks can be done with the `:::codegroup labels=[...labels]` syntax. - > [!warning] This is a **collapsible** callout > Only code blocks can be used in a group. -:::codegroup labels=[Without labels, With labels] +:::codegroup -``` -:::codegroup auto -// Code block without labels -::: +```rs [foo] +pub struct User { + pub firstname: String, + pub lastname: String, +} ``` -``` -:::codegroup labels=[first,, third] -// First code block (named) -// Second code block -// Third code block (named) -::: +```rs [bar] +pub struct User { + pub firstname: String, + pub lastname: String, +} + +impl User { + pub fn new(firstname: String, lastname: String) -> Self { + Self { firstname, lastname } + } +} ``` ::: -:::codegroup labels=[struct, impl] +````md +:::codegroup -```rust +```rs [foo] pub struct User { - pub firstname: String, // [!code highlight] + pub firstname: String, pub lastname: String, } ``` -```rust +```rs [bar] pub struct User { pub firstname: String, pub lastname: String, @@ -156,3 +161,4 @@ impl User { ``` ::: +```` diff --git a/content/docs/documentation/foundamentals/components/markdown.mdx b/content/docs/documentation/foundamentals/components/markdown.mdx index 8506538..bc1126c 100644 --- a/content/docs/documentation/foundamentals/components/markdown.mdx +++ b/content/docs/documentation/foundamentals/components/markdown.mdx @@ -13,15 +13,15 @@ Markdown is a lightweight markup language that you can use to add formatting ele You can create ordered and unordered lists: -:::codegroup labels=[Ordered, Unordered] +:::codegroup -```mdx +```mdx [ordered] 1. First item 2. Second item 3. Third item ``` -```mdx +```mdx [unordered] - First item - Second item - Third item @@ -73,19 +73,3 @@ You can create tables using the `|` syntax: | -------- | -------- | -------- | | Cell 1 | Cell 2 | Cell 3 | | Cell 4 | Cell 5 | Cell 6 | - -## Read More - -You can add a read more link using the `:read-more` syntax: - -:::codegroup labels=[Base, With title] - -```mdx -dd -``` - -```mdx -ff -``` - -::: diff --git a/content/index.mdx b/content/index.mdx index 2615bc1..a202a54 100644 --- a/content/index.mdx +++ b/content/index.mdx @@ -150,12 +150,9 @@ import { Badge } from "@/components/ui/badge"; Follow our blog for updates and news

- Learn more about our latest developments. - We share tips and tricks to help you succeed.og posts and follow us + Learn more about our latest developments. We share tips and tricks to + help you succeed.og posts and follow us

- -:::blog-list - diff --git a/explainer.config.ts b/explainer.config.ts index eae3d12..5f7ee88 100644 --- a/explainer.config.ts +++ b/explainer.config.ts @@ -1,3 +1,6 @@ +import CardGroup from "@/components/content/card-group/card-group.astro"; +import Card from "@/components/content/card-group/card.astro"; +import CodeGroup from "@/components/content/code-group/code-group.astro"; import { defineExplainerConfig } from "@/utils"; export default defineExplainerConfig({ @@ -38,4 +41,12 @@ export default defineExplainerConfig({ href: "/blog", }, ], + content: { + components: { + "card-group": CardGroup, + card: Card, + codegroup: CodeGroup, + "code-group": CodeGroup, + }, + }, }); diff --git a/src/assets/css/markdown.css b/src/assets/css/markdown.css index 2f7538a..241645d 100644 --- a/src/assets/css/markdown.css +++ b/src/assets/css/markdown.css @@ -220,11 +220,11 @@ h6 { border-spacing: 0; border-style: hidden; border-radius: 0.5rem !important; - border: 1px solid var(--color-border-tab); + border: 1px solid var(--color-border) !important; } thead { - border: 1px solid var(--color-border-tab); + border: 1px solid var(--color-border); border-radius: 0.5rem !important; background-color: var(--tab-container); } @@ -234,13 +234,13 @@ h6 { padding: 0.75rem 1rem; font-size: 0.875rem; text-align: left; - border-right: 1px solid var(--ui-border-muted); - border-bottom: 1px solid var(--ui-border-muted); - border-top: 1px solid var(--ui-border-muted); + border-right: 1px solid var(--ui-border); + border-bottom: 1px solid var(--ui-border); + border-top: 1px solid var(--ui-border); } tbody tr { - border: 1px solid var(--color-border-tab); + border: 1px solid var(--color-border); } tbody td { @@ -248,9 +248,9 @@ h6 { font-size: 0.875rem; text-align: left; vertical-align: top; - border-right: 1px solid var(--ui-border-muted); - border-bottom: 1px solid var(--ui-border-muted); - border-left: 1px solid var(--ui-border-muted); + border-right: 1px solid var(--ui-border); + border-bottom: 1px solid var(--ui-border); + border-left: 1px solid var(--ui-border); } img { @@ -295,7 +295,7 @@ html.dark .prose > .astro-code { border-radius: 0.5rem; font-size: 0.875rem; position: relative; - border: 1px solid var(--color-border-tab) !important; + border: 1px solid var(--color-border) !important; .line { padding-left: 1.5rem !important; diff --git a/src/lib/components/content/code-group/code-group.astro b/src/lib/components/content/code-group/code-group.astro new file mode 100644 index 0000000..34b4dd6 --- /dev/null +++ b/src/lib/components/content/code-group/code-group.astro @@ -0,0 +1,13 @@ +--- +import CodeGroupComponent from "./code-group"; + +interface Props { + labels?: string; +} + +const props = Astro.props; +--- + + + + diff --git a/src/lib/components/content/code-group/code-group.tsx b/src/lib/components/content/code-group/code-group.tsx new file mode 100644 index 0000000..9f47b90 --- /dev/null +++ b/src/lib/components/content/code-group/code-group.tsx @@ -0,0 +1,202 @@ +import { Icon } from "@iconify/react"; +import clsx from "clsx"; +import { useEffect, useRef, useState, type PropsWithChildren } from "react"; + +export type CodeGroupProps = { + labels?: string | string[]; + languages?: string | string[]; + codes?: string | string[]; +}; + +export default function CodeGroupComponent({ + children, + labels: propLabels, + languages: propLanguages, + codes: propCodes, +}: PropsWithChildren) { + const [activeTab, setActiveTab] = useState(0); + const [tabs, setTabs] = useState< + { label: string; language: string; icon: string }[] + >([]); + const containerRef = useRef(null); + + const parseProp = (prop: any) => { + if (Array.isArray(prop)) return prop; + if (typeof prop === "string") { + try { + return JSON.parse(prop); + } catch { + return []; + } + } + return []; + }; + + const parsedCodes = parseProp(propCodes); + const parsedLabels = parseProp(propLabels); + const parsedLanguages = parseProp(propLanguages); + + const isPropsMode = parsedCodes.length > 0; + + useEffect(() => { + if (isPropsMode || !containerRef.current) return; + + const elements = containerRef.current.querySelectorAll("pre"); + const newTabs: typeof tabs = []; + + elements.forEach((el, index) => { + const label = + el.getAttribute("data-label") || + el.getAttribute("title") || + el.getAttribute("data-language") || + `Tab ${index + 1}`; + + const language = el.getAttribute("data-language") || "text"; + + newTabs.push({ + label, + language, + icon: getCurrentIcon(label, language), + }); + }); + + if (JSON.stringify(newTabs) !== JSON.stringify(tabs)) { + setTabs(newTabs); + } + }, [children, isPropsMode]); + + useEffect(() => { + if (isPropsMode || !containerRef.current) return; + + const elements = containerRef.current.querySelectorAll("pre"); + + elements.forEach((el, index) => { + if (!(el instanceof HTMLElement)) return; + + const shouldShow = index === activeTab; + + el.style.display = shouldShow ? "block" : "none"; + + const parent = el.parentElement; + if (parent && parent.tagName === "P" && parent.children.length === 1) { + parent.style.display = shouldShow ? "block" : "none"; + } + }); + }, [activeTab, isPropsMode, tabs]); + + function getCurrentIcon(label?: string, language?: string) { + if (!label && !language) return "mdi:code-tags"; + + const labelBase = label?.toLowerCase(); + const languageBase = language?.toLowerCase(); + + return ( + icons[labelBase as keyof typeof icons] || + icons[languageBase as keyof typeof icons] || + "mdi:code-tags" + ); + } + + // Render for Props Mode (rehype plugin generated) + if (isPropsMode) { + const tabsData = parsedCodes.map((code: string, i: number) => ({ + label: parsedLabels[i] || parsedLanguages[i] || "Code", + language: parsedLanguages[i], + content: code, + })); + + return ( +
+
+ {tabsData.map((tab: any, i: number) => ( + + ))} +
+
+
+
+
+ ); + } + + return ( +
+ {tabs.length > 0 && ( +
+ {tabs.map((tab, i) => ( + + ))} +
+ )} +
+ {children} +
+
+ ); +} + +const icons = { + markdown: "devicon:markdown", + mdx: "devicon:markdown", + html: "devicon:html5", + css: "devicon:css3", + javascript: "devicon:javascript", + js: "devicon:javascript", + typescript: "devicon:typescript", + ts: "devicon:typescript", + python: "devicon:python", + py: "devicon:python", + dart: "devicon:dart", + rust: "catppuccin:rust", + rs: "catppuccin:rust", + npm: "devicon:npm", + yarn: "devicon:yarn", + pnpm: "devicon:pnpm", + bun: "devicon:bun", + vite: "devicon:vite", + "tailwind.config.js": "devicon:tailwindcss", + "tailwind.config.ts": "devicon:tailwindcss", + react: "devicon:react", + nextjs: "devicon:nextjs", + svelte: "devicon:svelte", + vue: "devicon:vuejs", + go: "devicon:go", + bash: "devicon:bash", + sh: "devicon:bash", + shell: "devicon:bash", + sql: "devicon:azuresqldatabase", + yaml: "devicon:yaml", + yml: "devicon:yaml", + json: "devicon:json", + dockerfile: "devicon:docker", + git: "devicon:git", + github: "devicon:github", + gitlab: "devicon:gitlab", +}; diff --git a/src/lib/plugins/code-group/CodeGroupWrapper.astro b/src/lib/plugins/code-group/CodeGroupWrapper.astro deleted file mode 100644 index 7864de6..0000000 --- a/src/lib/plugins/code-group/CodeGroupWrapper.astro +++ /dev/null @@ -1,21 +0,0 @@ ---- -import { CodeGroup } from "./code-group-component"; - -interface Props { - labels: string; - languages: string; - codes: string; -} - -const { labels, languages, codes } = Astro.props; ---- - -
- -
- - diff --git a/src/lib/plugins/code-group/code-group-component.tsx b/src/lib/plugins/code-group/code-group-component.tsx deleted file mode 100644 index 3fd93a2..0000000 --- a/src/lib/plugins/code-group/code-group-component.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { Icon } from "@iconify/react"; -import clsx from "clsx"; -import { useState } from "react"; - -export type CodeGroupProps = { - labels: string; - languages: string; - codes: string; -}; - -export function CodeGroup(props: CodeGroupProps) { - const [activeTab, setActiveTab] = useState(0); - - // Parse the JSON strings into arrays - const languages = JSON.parse(props.languages || "[]"); - const labels = JSON.parse(props.labels || "[]"); - const codes = JSON.parse(props.codes || "[]"); - - if (!languages.length || !codes.length) { - return null; - } - - function getCurrentIcon(label?: string, language?: string) { - if (!label && !language) return "mdi:code-tags"; - - const labelBase = label?.toLowerCase(); - const languageBase = language?.toLowerCase(); - - return ( - icons[labelBase as keyof typeof icons] || - icons[languageBase as keyof typeof icons] || - "mdi:code-tags" - ); - } - - return ( -
-
- {languages.map((language: string, i: number) => ( - - ))} -
-
-
- ); -} - -const icons = { - markdown: "devicon:markdown", - mdx: "devicon:markdown", - html: "devicon:html5", - css: "devicon:css3", - javascript: "devicon:javascript", - typescript: "devicon:typescript", - python: "devicon:python", - dart: "devicon:dart", - rust: "catppuccin:rust", - rs: "catppuccin:rust", - npm: "devicon:npm", - yarn: "devicon:yarn", - pnpm: "devicon:pnpm", - bun: "devicon:bun", - vite: "devicon:vite", - "tailwind.config.js": "devicon:tailwindcss", - "tailwind.config.ts": "devicon:tailwindcss", - react: "devicon:react", - nextjs: "devicon:nextjs", - svelte: "devicon:svelte", - vue: "devicon:vuejs", - go: "devicon:go", - ts: "devicon:typescript", - bash: "devicon:bash", - sql: "devicon:azuresqldatabase", - yaml: "devicon:yaml", - json: "devicon:json", - dockerfile: "devicon:docker", - git: "devicon:git", - github: "devicon:github", - gitlab: "devicon:gitlab", -}; diff --git a/src/lib/plugins/code-group/plugin.ts b/src/lib/plugins/code-group/plugin.ts deleted file mode 100644 index 880eb33..0000000 --- a/src/lib/plugins/code-group/plugin.ts +++ /dev/null @@ -1,151 +0,0 @@ -import type { Element, Root } from "hast"; -import { toHtml } from "hast-util-to-html"; -import type { Plugin } from "unified"; -import { visit } from "unist-util-visit"; - -const rehypeCodeGroupReact: Plugin<[], Root> = () => { - return (tree: Root) => { - const codeGroups: { - parent: any; - index: number; - endIndex: number; - labels: string[] | null; - languages: string[]; - }[] = []; - - // First pass: identify code groups - visit(tree, "element", (node, index, parent) => { - if ( - node.type === "element" && - node.tagName === "p" && - node.children && - node.children[0] && - node.children[0].type === "text" - ) { - const textContent = node.children[0].value; - - const matchWithoutLabels = textContent.match(/^:::codegroup auto$/); - const matchWithLabels = textContent.match( - /^:::codegroup\s+labels=\[(.*?)\]$/, - ); - - if ( - (matchWithLabels || matchWithoutLabels) && - parent && - typeof index === "number" - ) { - let endIndex = -1; - - let labels = []; - let languages: string[] = []; - for (let i = index + 1; i < parent.children.length; i++) { - const child = parent.children[i]; - - if ( - child.type === "element" && - child.tagName === "pre" && - "properties" in child && - child.properties?.dataLanguage - ) { - labels.push(child.properties!.dataLanguage as string); - languages.push(child.properties!.dataLanguage as string); - } - - if ( - child.type === "element" && - child.tagName === "p" && - child.children && - child.children[0] && - child.children[0].type === "text" && - child.children[0].value.trim() === ":::" - ) { - endIndex = i; - break; - } - } - - if (endIndex !== -1) { - if (matchWithLabels) { - matchWithLabels[1].split(",").forEach((label, index) => { - if (label.length) { - labels[index] = label.trim(); - } - }); - } - - codeGroups.push({ - parent, - index, - endIndex, - labels, - languages, - }); - } - } - } - }); - - for (let i = codeGroups.length - 1; i >= 0; i--) { - const { - parent, - index, - endIndex, - labels: providedLabels, - languages: providedLanguages, - } = codeGroups[i]; - - const codeBlocks = parent.children - .slice(index + 1, endIndex) - .filter( - (node: any) => node.type === "element" && node.tagName === "pre", - ); - - const codes: string[] = []; - const extractedLabels: string[] = []; - - codeBlocks.forEach((codeBlock: Element) => { - const codeElement = codeBlock.children[0] as Element; - - if ( - codeElement && - codeElement.type === "element" && - codeElement.tagName === "code" - ) { - const codeHtml = toHtml(codeBlock); - codes.push(codeHtml); - } - }); - - const finalLabels = providedLabels || extractedLabels; - if (providedLanguages.length > 0 && codes.length > 0) { - const newNode = { - type: "mdxJsxFlowElement", - name: "CodeGroupWrapper", - attributes: [ - { - type: "mdxJsxAttribute", - name: "labels", - value: JSON.stringify(finalLabels), - }, - { - type: "mdxJsxAttribute", - name: "languages", - value: JSON.stringify(providedLanguages), - }, - { - type: "mdxJsxAttribute", - name: "codes", - value: JSON.stringify(codes), - }, - ], - children: [], - data: { _mdxExplicitJsx: true }, - }; - - parent.children.splice(index, endIndex - index + 1, newNode); - } - } - }; -}; - -export default rehypeCodeGroupReact; diff --git a/src/lib/plugins/parser/block-renderer.astro b/src/lib/plugins/parser/block-renderer.astro deleted file mode 100644 index 3259e8a..0000000 --- a/src/lib/plugins/parser/block-renderer.astro +++ /dev/null @@ -1,68 +0,0 @@ ---- -import CardGroup from "@/components/content/card-group/card-group.astro"; -import Card from "@/components/content/card-group/card.astro"; -import BlockDynamic from "./dynamic-block.astro"; - -interface ASTNode { - delimiter?: string; - startTag?: string; - attributes?: Record; - children?: Array; - type?: string; -} - -const mdx: Record = { - "card-group": CardGroup, - card: Card, -}; - -const { ast } = Astro.props as { ast: string | undefined }; -if (!ast) return null; - -const node: ASTNode | ASTNode[] = JSON.parse(ast); - -const renderNodeData = (block: any): any => { - if (!block) return null; - - if (Array.isArray(block)) { - return block.flatMap(renderNodeData).filter(Boolean); - } - - if (typeof block !== "object") return block; - - if ("type" in block && block.type === "text") return block.value; - - const Component = mdx[block.startTag ?? ""]; - - const childrenRendered = - block.children?.flatMap(renderNodeData).filter(Boolean) || []; - - if (!Component) { - return childrenRendered; - } - - return { - component: Component, - props: block.attributes || {}, - children: childrenRendered, - }; -}; - -const treeData = Array.isArray(node) - ? node.flatMap(renderNodeData).filter(Boolean) - : renderNodeData(node); ---- - -{ - treeData.map((node: any, i: number) => { - if (typeof node === "string") return node; - return ( - - ); - }) -} diff --git a/src/lib/plugins/parser/dynamic-block.astro b/src/lib/plugins/parser/dynamic-block.astro deleted file mode 100644 index 1ef7a83..0000000 --- a/src/lib/plugins/parser/dynamic-block.astro +++ /dev/null @@ -1,23 +0,0 @@ ---- -import DynamicBlockRenderer from "./dynamic-block.astro"; -const { component: Component, props, children } = Astro.props; - -const childrenArray = Array.isArray(children) - ? children - : [children].filter(Boolean); ---- - - - { - childrenArray.map((child: any) => { - if (typeof child === "string") return child; - return ( - - ); - }) - } - diff --git a/src/lib/plugins/parser/plugin.ts b/src/lib/plugins/parser/plugin.ts index 946e728..1069ddf 100644 --- a/src/lib/plugins/parser/plugin.ts +++ b/src/lib/plugins/parser/plugin.ts @@ -1,125 +1,17 @@ -import type { Element, Root } from "unist"; +import type { Root } from "mdast"; import { visit } from "unist-util-visit"; -const mdx: Record = { - "card-group": "CardGroup", - card: "Card", -}; - -interface Block { - delimiter: string; - startTag: string; - attributes: Record; - children: Array; -} - -const parseAttributes = (str: string): Record => { - const regex = /(\w+)\s*=\s*(?:"([^"]*)"|([^,\s]+))/g; - const attrs: Record = {}; - let match: RegExpExecArray | null; - - while ((match = regex.exec(str)) !== null) { - const key = match[1]; - let value: any; - if (match[2] !== undefined) { - const raw = match[2]; - try { - value = JSON.parse(raw); - } catch { - value = raw; - } - } else if (match[3] !== undefined) { - const raw = match[3]; - - if (raw === "true") value = true; - else if (raw === "false") value = false; - else if (!isNaN(Number(raw))) value = Number(raw); - else value = raw; - } - - attrs[key] = value; - } - - return attrs; -}; - -const parseSingleNode = (node: Element): Block[] => { - let combinedText = ""; - - for (const child of node.children || []) { - if (child.type === "text") { - combinedText += child.value; - } else if ((child as any).type === "mdxTextExpression") { - combinedText += " " + (child as any).value; - } - } - - const parseBlockText = (text: string): Block[] => { - const blocks: Block[] = []; - const lines = text.split(/\r?\n/); - const stack: Block[] = []; - - for (const line of lines) { - const startMatch = line.match(/^:::(\w[\w-]*)\s*(.*)$/); - const endMatch = line.match(/^:::/); - - if (startMatch) { - const block: Block = { - delimiter: ":::", - startTag: startMatch[1], - attributes: parseAttributes(startMatch[2] || ""), - children: [], - }; - - if (stack.length > 0) { - stack[stack.length - 1].children.push(block); - } else { - blocks.push(block); - } - - stack.push(block); - } else if (endMatch) { - stack.pop(); - } else { - if (stack.length > 0) { - stack[stack.length - 1].children.push({ type: "text", value: line }); - } - } - } - - while (stack.length > 0) { - const remaining = stack.pop()!; - if (stack.length > 0) { - stack[stack.length - 1].children.push(remaining); - } else if (!blocks.includes(remaining)) { - blocks.push(remaining); - } - } - - return blocks; - }; - - return parseBlockText(combinedText); -}; - -export default function rehypeComponents() { +export default function remarkBlockParser() { return (tree: Root) => { - visit(tree, "element", (node: Element, index, parent) => { - if (!parent) return; + visit(tree, (node) => { + if (node.type !== "containerDirective" && node.type !== "leafDirective") + return; - const parsedBlocks = parseSingleNode(node); - const hasBlock = parsedBlocks.some((block) => block.startTag in mdx); + const n = node as any; + const data = n.data || (n.data = {}); - if (parent.type === "root" && hasBlock) { - parent.children[index] = { - type: "element", - tagName: "BlockRenderer", - properties: { - ast: JSON.stringify(parsedBlocks), - }, - children: [], - } as Element; - } + data.hName = n.name; + data.hProperties = { ...n.attributes }; }); }; } diff --git a/src/lib/plugins/parser/selectors.ts b/src/lib/plugins/parser/selectors.ts deleted file mode 100644 index 960aba7..0000000 --- a/src/lib/plugins/parser/selectors.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { toHtml } from "hast-util-to-html"; - -export const codeblockSelector = { - filter: (child: any) => { - return child.tagName === "code" && child.children[0].type !== "text"; - }, - metadata: (child: any) => ({ - delimiter: "", - startTag: "codeblock", - attributes: { html: toHtml(child) }, - children: [], - }), -}; - -export const cardGroupSelector = { - filter: (child: any) => { - return child.tagName === "card-group"; - }, - metadata: (child: any) => ({ - delimiter: "", - startTag: "card-group", - attributes: child.attributes, - children: child.children, - }), -}; diff --git a/src/lib/plugins/read-more/remark-directive.ts b/src/lib/plugins/read-more/remark-directive.ts index 7208827..81cecf3 100644 --- a/src/lib/plugins/read-more/remark-directive.ts +++ b/src/lib/plugins/read-more/remark-directive.ts @@ -1,5 +1,5 @@ -import type { Root } from 'mdast'; -import { visit } from 'unist-util-visit'; +import type { Root } from "mdast"; +import { visit } from "unist-util-visit"; /** * Plugin remark qui transforme les directives ::read-more en éléments HTML read-more @@ -9,16 +9,14 @@ const remarkReadMoreDirective = () => { return (tree: Root) => { visit(tree, (node: any) => { if ( - (node.type === 'textDirective' || - node.type === 'leafDirective' || - node.type === 'containerDirective') && - node.name === 'read-more' + (node.type === "textDirective" || + node.type === "leafDirective" || + node.type === "containerDirective") && + node.name === "read-more" ) { - // Transformer la directive en HTML à tagName 'read-more' const data = node.data || (node.data = {}); - data.hName = 'read-more'; + data.hName = "read-more"; - // Préserver les attributs en les transformant en properties HTML if (node.attributes) { data.hProperties = node.attributes; } @@ -27,4 +25,4 @@ const remarkReadMoreDirective = () => { }; }; -export default remarkReadMoreDirective; \ No newline at end of file +export default remarkReadMoreDirective; diff --git a/src/lib/plugins/shiki/transformer-meta-label.ts b/src/lib/plugins/shiki/transformer-meta-label.ts new file mode 100644 index 0000000..8d09875 --- /dev/null +++ b/src/lib/plugins/shiki/transformer-meta-label.ts @@ -0,0 +1,27 @@ +/** + * Transformer to extract label from meta string + * + * @example + * ```bash [pnpm] + * pnpm install + * ``` + */ +export default function transformerMetaLabel() { + return { + name: "transformer-meta-label", + pre(node: any) { + const meta = (this as any).options.meta as { __raw?: string } | undefined; + const metaString = typeof meta === "string" ? meta : meta?.__raw; + + if (!metaString) return; + + const match = metaString.match(/\[(.*?)\]/); + if (match) { + const label = match[1]; + if (node.properties) { + node.properties["data-label"] = label; + } + } + }, + }; +} diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 32d592e..7319793 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -41,6 +41,11 @@ type ExplainerConfig = { label: string; href: string; }[]; + content: { + components: { + [key: string]: (...props: any[]) => any; + }; + }; }; export function defineExplainerConfig(config: ExplainerConfig) { diff --git a/src/pages/docs/[...slug].astro b/src/pages/docs/[...slug].astro index f77466a..d8e91f0 100644 --- a/src/pages/docs/[...slug].astro +++ b/src/pages/docs/[...slug].astro @@ -10,18 +10,14 @@ import { import DocsLayout from "@/layouts/DocsLayout.astro"; import { render, type CollectionEntry } from "astro:content"; import config from "../../../explainer.config"; -import CodeGroupWrapper from "../../lib/plugins/code-group/CodeGroupWrapper.astro"; + import { useDocumentation, type HeadingNode } from "../../lib/utils"; import * as astroContent from "astro:content"; import DocNavigationWrapper from "@/components/elements/DocNavigationWrapper.astro"; import { Icon } from "@iconify/react"; import BlogListWrapper from "@/plugins/blog-list/BlogListWrapper.astro"; -import BlockRenderer from "@/plugins/parser/block-renderer.astro"; import H2 from "@/components/content/heading/h2.astro"; import H3 from "@/components/content/heading/h3.astro"; -import { writeFile, mkdir } from "node:fs/promises"; -import { join } from "node:path"; -import { generateThumbnail } from "@/components/content/thumbnail"; interface Props { element: CollectionEntry<"blog">; @@ -107,15 +103,7 @@ function pascalCase(str: string) {
- +
Date: Fri, 12 Dec 2025 21:47:35 +0100 Subject: [PATCH 2/3] wip --- .../getting-started/installation.mdx | 10 +- explainer.config.ts | 41 ++++++ .../content/code-group/code-group.tsx | 107 +++++++-------- src/lib/components/content/codeblock.astro | 125 +++++++++++++++++- src/lib/utils.ts | 1 + 5 files changed, 215 insertions(+), 69 deletions(-) diff --git a/content/docs/documentation/getting-started/installation.mdx b/content/docs/documentation/getting-started/installation.mdx index 63de934..116dbef 100644 --- a/content/docs/documentation/getting-started/installation.mdx +++ b/content/docs/documentation/getting-started/installation.mdx @@ -21,7 +21,7 @@ git clone git@github.com:LeadcodeDev/explainer.git You can start by create new repository from the template. -```bash +```bash [npx] npx create-astro@latest --template https://github.com/LeadcodeDev/explainer ``` @@ -35,17 +35,17 @@ When you create a new repository in Github, you can use this project as a templa In your local repository, run the following command to install the dependencies. -:::codegroup labels=[pnpm, npm, yarn] +:::codegroup -```bash +```bash [pnpm] pnpm install ``` -```bash +```bash [npm] npm install ``` -```bash +```bash [yarn] yarn install ``` diff --git a/explainer.config.ts b/explainer.config.ts index 5f7ee88..13594f5 100644 --- a/explainer.config.ts +++ b/explainer.config.ts @@ -1,6 +1,7 @@ import CardGroup from "@/components/content/card-group/card-group.astro"; import Card from "@/components/content/card-group/card.astro"; import CodeGroup from "@/components/content/code-group/code-group.astro"; +import CodeBlock from "@/components/content/codeblock.astro"; import { defineExplainerConfig } from "@/utils"; export default defineExplainerConfig({ @@ -47,6 +48,46 @@ export default defineExplainerConfig({ card: Card, codegroup: CodeGroup, "code-group": CodeGroup, + pre: CodeBlock, + }, + icons: { + markdown: "devicon:markdown", + mdx: "devicon:markdown", + html: "devicon:html5", + css: "devicon:css3", + javascript: "devicon:javascript", + js: "devicon:javascript", + typescript: "devicon:typescript", + ts: "devicon:typescript", + python: "devicon:python", + py: "devicon:python", + dart: "devicon:dart", + rust: "catppuccin:rust", + rs: "catppuccin:rust", + npm: "devicon:npm", + npx: "devicon:npm", + yarn: "devicon:yarn", + pnpm: "devicon:pnpm", + bun: "devicon:bun", + vite: "devicon:vite", + "tailwind.config.js": "devicon:tailwindcss", + "tailwind.config.ts": "devicon:tailwindcss", + react: "devicon:react", + nextjs: "devicon:nextjs", + svelte: "devicon:svelte", + vue: "devicon:vuejs", + go: "devicon:go", + bash: "devicon:bash", + sh: "devicon:bash", + shell: "devicon:bash", + sql: "devicon:azuresqldatabase", + yaml: "devicon:yaml", + yml: "devicon:yaml", + json: "devicon:json", + dockerfile: "devicon:docker", + git: "devicon:git", + github: "devicon:github", + gitlab: "devicon:gitlab", }, }, }); diff --git a/src/lib/components/content/code-group/code-group.tsx b/src/lib/components/content/code-group/code-group.tsx index 9f47b90..800c195 100644 --- a/src/lib/components/content/code-group/code-group.tsx +++ b/src/lib/components/content/code-group/code-group.tsx @@ -1,6 +1,7 @@ import { Icon } from "@iconify/react"; import clsx from "clsx"; import { useEffect, useRef, useState, type PropsWithChildren } from "react"; +import config from "../../../../../explainer.config"; export type CodeGroupProps = { labels?: string | string[]; @@ -8,12 +9,10 @@ export type CodeGroupProps = { codes?: string | string[]; }; -export default function CodeGroupComponent({ - children, - labels: propLabels, - languages: propLanguages, - codes: propCodes, -}: PropsWithChildren) { +export default function CodeGroupComponent( + props: PropsWithChildren, +) { + const icons = config.content.icons; const [activeTab, setActiveTab] = useState(0); const [tabs, setTabs] = useState< { label: string; language: string; icon: string }[] @@ -32,9 +31,9 @@ export default function CodeGroupComponent({ return []; }; - const parsedCodes = parseProp(propCodes); - const parsedLabels = parseProp(propLabels); - const parsedLanguages = parseProp(propLanguages); + const parsedCodes = parseProp(props.codes); + const parsedLabels = parseProp(props.labels); + const parsedLanguages = parseProp(props.languages); const isPropsMode = parsedCodes.length > 0; @@ -53,17 +52,31 @@ export default function CodeGroupComponent({ const language = el.getAttribute("data-language") || "text"; + let icon = getCurrentIcon(label, language); + + if (["bash", "sh", "shell"].includes(language.toLowerCase())) { + const text = el.textContent?.trim() || ""; + const match = text.replace(/^\$\s*/, "").match(/^(\w+)/); + if (match) { + const cmd = match[1].toLowerCase(); + if (["npm", "npx", "pnpm", "yarn", "bun"].includes(cmd)) { + const cmdIcon = icons[cmd as keyof typeof icons]; + if (cmdIcon) icon = cmdIcon; + } + } + } + newTabs.push({ label, language, - icon: getCurrentIcon(label, language), + icon, }); }); if (JSON.stringify(newTabs) !== JSON.stringify(tabs)) { setTabs(newTabs); } - }, [children, isPropsMode]); + }, [props.children, isPropsMode]); useEffect(() => { if (isPropsMode || !containerRef.current) return; @@ -99,11 +112,30 @@ export default function CodeGroupComponent({ // Render for Props Mode (rehype plugin generated) if (isPropsMode) { - const tabsData = parsedCodes.map((code: string, i: number) => ({ - label: parsedLabels[i] || parsedLanguages[i] || "Code", - language: parsedLanguages[i], - content: code, - })); + const tabsData = parsedCodes.map((code: string, i: number) => { + const language = parsedLanguages[i] || "text"; + const label = parsedLabels[i] || language || "Code"; + let icon = getCurrentIcon(label, language); + + if (["bash", "sh", "shell"].includes(language.toLowerCase())) { + const text = code.replace(/<[^>]+>/g, "").trim(); + const match = text.replace(/^\$\s*/, "").match(/^(\w+)/); + if (match) { + const cmd = match[1].toLowerCase(); + if (["npm", "pnpm", "yarn", "bun"].includes(cmd)) { + const cmdIcon = icons[cmd as keyof typeof icons]; + if (cmdIcon) icon = cmdIcon; + } + } + } + + return { + label, + language, + icon, + content: code, + }; + }); return (
@@ -120,7 +152,7 @@ export default function CodeGroupComponent({ )} type="button" > - + {tab.label} ))} @@ -156,47 +188,8 @@ export default function CodeGroupComponent({
)}
- {children} + {props.children}
); } - -const icons = { - markdown: "devicon:markdown", - mdx: "devicon:markdown", - html: "devicon:html5", - css: "devicon:css3", - javascript: "devicon:javascript", - js: "devicon:javascript", - typescript: "devicon:typescript", - ts: "devicon:typescript", - python: "devicon:python", - py: "devicon:python", - dart: "devicon:dart", - rust: "catppuccin:rust", - rs: "catppuccin:rust", - npm: "devicon:npm", - yarn: "devicon:yarn", - pnpm: "devicon:pnpm", - bun: "devicon:bun", - vite: "devicon:vite", - "tailwind.config.js": "devicon:tailwindcss", - "tailwind.config.ts": "devicon:tailwindcss", - react: "devicon:react", - nextjs: "devicon:nextjs", - svelte: "devicon:svelte", - vue: "devicon:vuejs", - go: "devicon:go", - bash: "devicon:bash", - sh: "devicon:bash", - shell: "devicon:bash", - sql: "devicon:azuresqldatabase", - yaml: "devicon:yaml", - yml: "devicon:yaml", - json: "devicon:json", - dockerfile: "devicon:docker", - git: "devicon:git", - github: "devicon:github", - gitlab: "devicon:gitlab", -}; diff --git a/src/lib/components/content/codeblock.astro b/src/lib/components/content/codeblock.astro index b36dac6..9cbe5ba 100644 --- a/src/lib/components/content/codeblock.astro +++ b/src/lib/components/content/codeblock.astro @@ -1,10 +1,121 @@ --- -const props = Astro.props; +import { Icon } from "@iconify/react"; + +interface Props { + class?: string; + "data-language"?: string; + "data-label"?: string; + [key: string]: any; +} + +const { + class: className, + "data-language": language, + "data-label": label, + ...props +} = Astro.props; + +const icons: Record = { + markdown: "devicon:markdown", + mdx: "devicon:markdown", + html: "devicon:html5", + css: "devicon:css3", + javascript: "devicon:javascript", + js: "devicon:javascript", + typescript: "devicon:typescript", + ts: "devicon:typescript", + python: "devicon:python", + py: "devicon:python", + dart: "devicon:dart", + rust: "catppuccin:rust", + rs: "catppuccin:rust", + npm: "devicon:npm", + npx: "devicon:npm", + yarn: "devicon:yarn", + pnpm: "devicon:pnpm", + bun: "devicon:bun", + vite: "devicon:vite", + "tailwind.config.js": "devicon:tailwindcss", + "tailwind.config.ts": "devicon:tailwindcss", + react: "devicon:react", + nextjs: "devicon:nextjs", + svelte: "devicon:svelte", + vue: "devicon:vuejs", + go: "devicon:go", + bash: "devicon:bash", + sh: "devicon:bash", + shell: "devicon:bash", + sql: "devicon:azuresqldatabase", + yaml: "devicon:yaml", + yml: "devicon:yaml", + json: "devicon:json", + dockerfile: "devicon:docker", + git: "devicon:git", + github: "devicon:github", + gitlab: "devicon:gitlab", +}; + +const displayLabel = label || language || "text"; +const displayLanguage = language || "text"; + +let icon = + icons[displayLabel.toLowerCase()] || + icons[displayLanguage.toLowerCase()] || + "mdi:code-tags"; + +if (["bash", "sh", "shell"].includes(displayLanguage.toLowerCase())) { + const html = await Astro.slots.render("default"); + const text = html.replace(/<[^>]+>/g, "").trim(); + const match = text.match(/^(\w+)/); + if (match) { + const cmd = match[1].toLowerCase(); + if (["npm", "npx", "pnpm", "yarn", "bun"].includes(cmd) && icons[cmd]) { + icon = icons[cmd]; + } + } +} --- -

-

-

+
+ { + label && ( +
+ + {displayLabel} +
+ ) + } +
+
+
+
+ + diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 7319793..21fe35e 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -42,6 +42,7 @@ type ExplainerConfig = { href: string; }[]; content: { + icons: Record; components: { [key: string]: (...props: any[]) => any; }; From 0c9d79334ff0ec9364c605541e04561e1c77b2c9 Mon Sep 17 00:00:00 2001 From: Baptiste Parmantier Date: Fri, 12 Dec 2025 23:38:46 +0100 Subject: [PATCH 3/3] feat: implement callout --- astro.config.mjs | 7 +- .../foundamentals/components/_default.mdx | 1 + .../foundamentals/components/alert.mdx | 62 ------------ .../foundamentals/components/callout.mdx | 96 +++++++++++++++++++ .../foundamentals/components/code-block.mdx | 18 ++++ .../foundamentals/components/markdown.mdx | 17 +--- .../foundamentals/components/text.mdx | 16 +++- .../docs/documentation/foundamentals/docs.mdx | 18 ++-- .../documentation/foundamentals/routing.mdx | 22 +++-- .../getting-started/configuration.mdx | 2 +- .../documentation/getting-started/deploy.mdx | 10 +- .../getting-started/getting-started.mdx | 3 - .../getting-started/project-structure.mdx | 12 ++- explainer.config.ts | 2 + src/lib/components/content/callout.astro | 81 ++++++++++++++++ src/lib/components/content/codeblock.astro | 1 - .../plugins/read-more/ReadMoreWrapper.astro | 5 - src/lib/plugins/read-more/plugin.ts | 24 ----- .../plugins/read-more/read-more-component.tsx | 42 -------- src/lib/plugins/read-more/remark-directive.ts | 28 ------ 20 files changed, 252 insertions(+), 215 deletions(-) delete mode 100644 content/docs/documentation/foundamentals/components/alert.mdx create mode 100644 content/docs/documentation/foundamentals/components/callout.mdx create mode 100644 src/lib/components/content/callout.astro delete mode 100644 src/lib/plugins/read-more/ReadMoreWrapper.astro delete mode 100644 src/lib/plugins/read-more/plugin.ts delete mode 100644 src/lib/plugins/read-more/read-more-component.tsx delete mode 100644 src/lib/plugins/read-more/remark-directive.ts diff --git a/astro.config.mjs b/astro.config.mjs index 3c1ee64..54c9134 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -18,7 +18,6 @@ import rehypeMermaid from "rehype-mermaid"; import rehypeBlogListReact from "./src/lib/plugins/blog-list/plugin"; import remarkBlockParser from "./src/lib/plugins/parser/plugin"; import remarkDirectivePkg from "remark-directive"; -import remarkReadMoreDirective from "./src/lib/plugins/read-more/remark-directive"; import sitemap from "@astrojs/sitemap"; import { buildDocIntegration } from "./src/hooks/build-doc"; import transformerMetaLabel from "./src/lib/plugins/shiki/transformer-meta-label"; @@ -49,11 +48,7 @@ export default defineConfig({ type: "shiki", excludeLangs: ["mermaid"], }, - remarkPlugins: [ - remarkDirectivePkg, - remarkBlockParser, - remarkReadMoreDirective, - ], + remarkPlugins: [remarkDirectivePkg, remarkBlockParser], rehypePlugins: [ rehypeMermaid, [ diff --git a/content/docs/documentation/foundamentals/components/_default.mdx b/content/docs/documentation/foundamentals/components/_default.mdx index ef423f2..cdbe2ca 100644 --- a/content/docs/documentation/foundamentals/components/_default.mdx +++ b/content/docs/documentation/foundamentals/components/_default.mdx @@ -10,4 +10,5 @@ collection: - markdown - text - card + - callout --- diff --git a/content/docs/documentation/foundamentals/components/alert.mdx b/content/docs/documentation/foundamentals/components/alert.mdx deleted file mode 100644 index f17a672..0000000 --- a/content/docs/documentation/foundamentals/components/alert.mdx +++ /dev/null @@ -1,62 +0,0 @@ ---- -title: Alert -description: Lorem ipsum dolor sit amet, consectetur adipiscing elit. -permalink: alert -icon: lucide:bell ---- - -# Alert - -Alerts are UI components that help highlight important information to users. They can be used to provide feedback, warnings, errors, or general notes. - -In Explainer, alerts can be created using a simple Markdown syntax and can be either expanded by default or collapsible. Different types of alerts (`success`, `warning`, `note`, `error`) are available to convey different levels of importance or urgency. - -## Success - -``` -> [!success] -> Some content is displayed directly! -``` - -> [!success] -> Some content is displayed directly! - -## Warning - -``` -> [!warning] This is a **collapsible** callout -> Some content shown after opening! -``` - -> [!warning] This is a **collapsible** callout -> Some content shown after opening! - -## Note - -``` -> [!note] This is a **collapsible** callout -> Some content shown after opening! -``` - -> [!note] This is a **collapsible** callout -> Some content shown after opening! - -## Error - -``` -> [!error] This is a **collapsible** callout -> Some content shown after opening! -``` - -> [!error] This is a **collapsible** callout -> Some content shown after opening! - -## Important - -``` -> [!important] This is a **collapsible** callout -> Some content shown after opening! -``` - -> [!important] This is a **collapsible** callout -> Some content shown after opening! diff --git a/content/docs/documentation/foundamentals/components/callout.mdx b/content/docs/documentation/foundamentals/components/callout.mdx new file mode 100644 index 0000000..390b139 --- /dev/null +++ b/content/docs/documentation/foundamentals/components/callout.mdx @@ -0,0 +1,96 @@ +--- +title: Callout +description: Lorem ipsum dolor sit amet, consectetur adipiscing elit. +permalink: callout +icon: lucide:tag +--- + +# Callouts + +## Basic callouts + +### Info + +:::callout{variant="info"} +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. +::: + +``` +:::callout{variant="info"} +Lorem ipsum dolor sit amet, consectetur adipiscing elit, +sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. +::: +``` + +--- + +### Success + +:::callout{variant="success"} +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. +::: + +``` +:::callout{variant="success"} +Lorem ipsum dolor sit amet, consectetur adipiscing elit, +sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. +::: +``` + +--- + +### Warning + +:::callout{variant="warning"} +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. +::: + +``` +:::callout{variant="warning"} +Lorem ipsum dolor sit amet, consectetur adipiscing elit, +sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. +::: +``` + +--- + +### Danger + +:::callout{variant="danger"} +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. +::: + +``` +:::callout{variant="danger"} +Lorem ipsum dolor sit amet, consectetur adipiscing elit, +sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. +::: +``` + +--- + +## Children + +:::callout +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. + +```rs [main.rs] +pub fn main() { + println!("Hello, world!"); +} +``` + +::: + +```` +:::callout +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. + +```rs [main.rs] +pub fn main() { + println!("Hello, world!"); +} +``` + +::: +```` diff --git a/content/docs/documentation/foundamentals/components/code-block.mdx b/content/docs/documentation/foundamentals/components/code-block.mdx index 7ae81bd..48a8d8c 100644 --- a/content/docs/documentation/foundamentals/components/code-block.mdx +++ b/content/docs/documentation/foundamentals/components/code-block.mdx @@ -86,6 +86,24 @@ pub struct User { } ``` +## Include embedded code blocks + +```rs [entities/user.rs] +pub struct User { + pub firstname: String, + pub lastname: String, +} +``` + +```` +```rs [entities/user.rs] +pub struct User { + pub firstname: String, + pub lastname: String, +} +``` +```` + --- ## Line modifiers diff --git a/content/docs/documentation/foundamentals/components/markdown.mdx b/content/docs/documentation/foundamentals/components/markdown.mdx index bc1126c..82dbfba 100644 --- a/content/docs/documentation/foundamentals/components/markdown.mdx +++ b/content/docs/documentation/foundamentals/components/markdown.mdx @@ -33,7 +33,7 @@ You can create ordered and unordered lists: You can create links using the `[text](url)` syntax: -```mdx +```mdx [Simple link] [Google](https://www.google.com) ``` @@ -41,28 +41,19 @@ You can create links using the `[text](url)` syntax: You can add images using the `![alt text](image.png)` syntax: -```mdx +```mdx [Simple image] ![Explainer Logo](https://placehold.co/600x400) ``` ![Explainer Logo](https://placehold.co/600x400) -## Code Blocks - -You can add code blocks using the ``` syntax: - -```dart -print("Hello, World!") -``` - -> [!NOTE] -> See more about [code blocks](/docs/syntax/code-blocks) +--- ## Tables You can create tables using the `|` syntax: -```mdx +```mdx [Simple table] | Header 1 | Header 2 | Header 3 | | -------- | -------- | -------- | | Cell 1 | Cell 2 | Cell 3 | diff --git a/content/docs/documentation/foundamentals/components/text.mdx b/content/docs/documentation/foundamentals/components/text.mdx index 7107e1c..5ad25b6 100644 --- a/content/docs/documentation/foundamentals/components/text.mdx +++ b/content/docs/documentation/foundamentals/components/text.mdx @@ -24,12 +24,26 @@ Markdown supports various text formatting options: You can create headers from level 1 (largest) to level 6 (smallest): -```mdx +```mdx [h1] # Header 1 +``` + +# Header 1 + +```mdx [h2] +## Header 2 +``` ## Header 2 +```mdx [h3] +### Header 3 +``` + ### Header 3 +```mdx [h4] #### Header 4 ``` + +#### Header 4 diff --git a/content/docs/documentation/foundamentals/docs.mdx b/content/docs/documentation/foundamentals/docs.mdx index e4fb347..567f127 100644 --- a/content/docs/documentation/foundamentals/docs.mdx +++ b/content/docs/documentation/foundamentals/docs.mdx @@ -23,9 +23,7 @@ To organise the documentation, each section or complete documentation must be co Each documentation folder must contain a configuration file named `_default.md`. This file defines the metadata for the section, and its front matter must follow the following structure : -:::codegroup labels=[Tree] - -``` +```plain [Tree] ├── content/ │ ├── [...page] │ └── docs/ @@ -35,13 +33,11 @@ Each documentation folder must contain a configuration file named `_default.md`. │ └── second_file.mdx ``` -::: - The configuration file is structured in the form of standardised `frontmatter`, which allows various information to be defined. -:::codegroup labels=[_example.mdx, Collection] +:::codegroup -```mdx +```mdx [example] --- label: My documentation directory: my_doc @@ -54,7 +50,7 @@ collection: --- ``` -```ts +```ts [Collection] export const docDefaultSchema = z.object({ label: z.string(), description: z.string(), @@ -82,9 +78,9 @@ export const docDefaultSchema = z.object({ The other Markdown files (`.md` or `.mdx`) in the same folder represent the individual pages of the documentation. The front matter of each of these files must comply with the following schema : -:::codegroup labels=[first_file.mdx, Collection] +:::codegroup -```mdx +```mdx [first_file.mdx] --- title: First file description: Lorem ipsum dolor sit amet, consectetur adipiscing elit. @@ -95,7 +91,7 @@ visibility: --- ``` -```typescript +```typescript [Collection] export const docSchema = z.object({ title: z.string(), description: z.string(), diff --git a/content/docs/documentation/foundamentals/routing.mdx b/content/docs/documentation/foundamentals/routing.mdx index 7ba30e3..27caaf4 100644 --- a/content/docs/documentation/foundamentals/routing.mdx +++ b/content/docs/documentation/foundamentals/routing.mdx @@ -9,8 +9,9 @@ icon: lucide:signpost Routing is an important part of any content-driven website, allowing you to create dynamic or static pages for your site. -> [!important] -> The `blog` and `docs` folders are special collections; they should be considered separate from routing. +:::callout{variant="info"} +The `blog` and `docs` folders are special collections; they should be considered separate from routing. +::: --- @@ -18,16 +19,19 @@ Routing is an important part of any content-driven website, allowing you to crea All of your routes must be defined within the `content/` folder. -> [!note] -> Each file representing a new page must be written in markdown format `md` or `mdx`. +:::callout{variant="info"} +Each file representing a new page must be written in markdown format `md` or `mdx`. +::: Let us consider the desire to create a new `/about` page within our site, we should create an `about` markdown file in the `content/` folder. -:::codegroup labels=[about.mdx] +:::codegroup -``` +```plain [Url] https://{hostname}/about +``` +```plain [Tree] ├── content/ │ └── about.mdx ``` @@ -41,15 +45,15 @@ https://{hostname}/about It is possible to create nested routes by creating a folder with the same name as the route in the `content/` folder. In this example we will create two nested routes `/about` and `/hello/world`. -:::codegroup labels=[about.mdx, world.mdx] +:::codegroup -``` +```plain [about.mdx] ├── content/ │ └── about/ │ └── index.mdx ``` -``` +```plain [world.mdx] ├── content/ │ └── hello/ │ └── world.mdx diff --git a/content/docs/documentation/getting-started/configuration.mdx b/content/docs/documentation/getting-started/configuration.mdx index 16389ff..665e992 100644 --- a/content/docs/documentation/getting-started/configuration.mdx +++ b/content/docs/documentation/getting-started/configuration.mdx @@ -13,7 +13,7 @@ The project configuration is simple and centralized in the `explainer.config.ts` This file uses the `defineExplainerConfig` function to define all the configurations for the site. Here is an overview of the different configuration sections available: -```ts +```ts [explainer.config.ts] import { defineExplainerConfig } from "@/utils"; export default defineExplainerConfig({ diff --git a/content/docs/documentation/getting-started/deploy.mdx b/content/docs/documentation/getting-started/deploy.mdx index 523dd91..01d1c10 100644 --- a/content/docs/documentation/getting-started/deploy.mdx +++ b/content/docs/documentation/getting-started/deploy.mdx @@ -15,11 +15,13 @@ Your environment must support installing Playwrite and its browsers in order for Explainer provides a GitHub Actions workflow to deploy your static site to a Cloudflare Pages environment. -> [!NOTE] -> Cloudflare does not allow the installation of Playwright or its browsers. -> You must use an external CI/CD pipeline to generate the diagrams and then push them to your Cloudflare Pages environment. +:::callout{variant="info"} +Cloudflare does not allow the installation of Playwright or its browsers. -```yaml +You must use an external CI/CD pipeline to generate the diagrams and then push them to your Cloudflare Pages environment. +::: + +```yaml [.github/workflows/deploy.yml] name: Astro CI/CD with Playwright & Cloudflare Pages on: diff --git a/content/docs/documentation/getting-started/getting-started.mdx b/content/docs/documentation/getting-started/getting-started.mdx index af3f875..3895dcd 100644 --- a/content/docs/documentation/getting-started/getting-started.mdx +++ b/content/docs/documentation/getting-started/getting-started.mdx @@ -10,6 +10,3 @@ icon: lucide:info ## Introduction Explainer provides a rich set of components that can be used directly in your Markdown files. This documentation outlines the various markdown components available for creating beautiful, interactive documentation. - -:::card-group{cols=2} -::: diff --git a/content/docs/documentation/getting-started/project-structure.mdx b/content/docs/documentation/getting-started/project-structure.mdx index ec3a9fc..cda7e65 100644 --- a/content/docs/documentation/getting-started/project-structure.mdx +++ b/content/docs/documentation/getting-started/project-structure.mdx @@ -7,7 +7,7 @@ icon: lucide:layout-panel-top # Project Structure -``` +```plain [Tree project] ├── content/ │ ├── [...page] │ │ @@ -56,12 +56,14 @@ The `content` folder is divided into two parts: The `docs` directory contains all your documentation content organized by collections. -> [!NOTE] -> Please refer to the dedicated [section](/docs/framework/docs) for more information. +:::callout{variant="info"} +Please refer to the dedicated [section](/docs/framework/docs) for more information. +::: ### Blog Structure The `blog` directory contains all your blog articles. -> [!NOTE] -> Please refer to the dedicated [section](/docs/framework/blog) for more information. +:::callout{variant="info"} +Please refer to the dedicated [section](/docs/framework/blog) for more information. +::: diff --git a/explainer.config.ts b/explainer.config.ts index 13594f5..e1da083 100644 --- a/explainer.config.ts +++ b/explainer.config.ts @@ -1,3 +1,4 @@ +import Callout from "@/components/content/callout.astro"; import CardGroup from "@/components/content/card-group/card-group.astro"; import Card from "@/components/content/card-group/card.astro"; import CodeGroup from "@/components/content/code-group/code-group.astro"; @@ -49,6 +50,7 @@ export default defineExplainerConfig({ codegroup: CodeGroup, "code-group": CodeGroup, pre: CodeBlock, + callout: Callout, }, icons: { markdown: "devicon:markdown", diff --git a/src/lib/components/content/callout.astro b/src/lib/components/content/callout.astro new file mode 100644 index 0000000..a3be9d2 --- /dev/null +++ b/src/lib/components/content/callout.astro @@ -0,0 +1,81 @@ +--- +import { cn } from "@/utils"; +import { Icon } from "@iconify/react"; +import { undefined } from "astro:schema"; + +type Props = { + to: string; + text?: string; + variant?: Variant; +}; + +const props = Astro.props; + +type Variant = "info" | "success" | "warning" | "danger"; + +const icons: Record = { + info: "lucide:info", + success: "lucide:lightbulb", + warning: "lucide:triangle-alert", + danger: "lucide:circle-alert", +}; + +const variants: Record & Record<"_default", string> = { + _default: "bg-background !text-muted-foreground", + info: "bg-blue-50 border-blue-200 !text-blue-500", + success: "bg-green-50 border-green-300 !text-green-700", + warning: "bg-yellow-50 border-orange-200 !text-yellow-700", + danger: "bg-red-100 border-red-300 !text-red-500", +}; + +const currentIcon = props.variant ? icons[props.variant] : undefined; +const currentVariant = variants[props.variant ?? "_default"]; +--- + +
p]:text-sm !font-light", + currentVariant, + )} + style={{ + textDecoration: "none", + }} +> +
+ { + currentIcon && ( + + ) + } +
+
+ +
+
+ + diff --git a/src/lib/components/content/codeblock.astro b/src/lib/components/content/codeblock.astro index 9cbe5ba..2e659c7 100644 --- a/src/lib/components/content/codeblock.astro +++ b/src/lib/components/content/codeblock.astro @@ -103,7 +103,6 @@ if (["bash", "sh", "shell"].includes(displayLanguage.toLowerCase())) {