diff --git a/app/components/syntax/Fence.tsx b/app/components/syntax/Fence.tsx index 87c0d1e..6a138f6 100644 --- a/app/components/syntax/Fence.tsx +++ b/app/components/syntax/Fence.tsx @@ -1,46 +1,19 @@ -import { Highlight, Prism } from "prism-react-renderer"; -import { prismThemes } from "@/data/themes/prism"; -import { useTheme } from "@/hooks/useTheme"; -import { Fragment, useMemo } from "react"; - import { clientOnly } from "vike-react/clientOnly"; +import { SSRSnippet } from "./SSRSnippet"; const CSRSnippet = clientOnly(() => import("./CSRSnippet")); -function SSRFence({ children, language }: { children: string; language: string }) { - const { theme } = useTheme(); - const prismTheme = useMemo(() => { - return prismThemes[theme]; - }, [theme]); - - return ( - - {({ className, style, tokens, getTokenProps }) => ( -
-          
-            {tokens.map((line, lineIndex) => (
-              
-                {line
-                  .filter((token) => !token.empty)
-                  .map((token, tokenIndex) => (
-                    
-                  ))}
-                {"\n"}
-              
-            ))}
-          
-        
- )} -
- ); -} - export function Fence({ children, language }: { children: string; language: string }) { + const props = { + language, + label: undefined, + showLineNumbers: false, + children, + }; + return (
- }> - {children} - + } />
); } diff --git a/app/components/syntax/SSRSnippet.tsx b/app/components/syntax/SSRSnippet.tsx new file mode 100644 index 0000000..c623496 --- /dev/null +++ b/app/components/syntax/SSRSnippet.tsx @@ -0,0 +1,58 @@ +import { Highlight, Prism } from "prism-react-renderer"; +import { prismThemes } from "@/data/themes/prism"; +import { useTheme } from "@/hooks/useTheme"; +import { Fragment, useMemo } from "react"; +import clsx from "clsx"; + +export function SSRSnippet({ + children, + language, + label, + showLineNumbers = false, +}: { + children: string; + language: string; + label?: string; + showLineNumbers?: boolean; +}) { + const { theme } = useTheme(); + const prismTheme = useMemo(() => { + return prismThemes[theme]; + }, [theme]); + + return ( + + {({ className, style, tokens, getTokenProps }) => ( +
+ {label && ( +
+ {label} +
+ )} +
+            
+              {tokens.map((line, lineIndex) => (
+                
+                  {showLineNumbers && (
+                    
+                      {lineIndex + 1}
+                    
+                  )}
+                  {line
+                    .filter((token) => !token.empty)
+                    .map((token, tokenIndex) => (
+                      
+                    ))}
+                  {"\n"}
+                
+              ))}
+            
+          
+
+ )} +
+ ); +} diff --git a/app/components/syntax/Snippet.tsx b/app/components/syntax/Snippet.tsx index 4c0ba6a..b7ea54c 100644 --- a/app/components/syntax/Snippet.tsx +++ b/app/components/syntax/Snippet.tsx @@ -1,57 +1,11 @@ import type { Data } from "@/pages/docs/+data"; -import { snippetsService } from "@/services/SnippetsService"; -import { Highlight, Prism } from "prism-react-renderer"; import { clientOnly } from "vike-react/clientOnly"; -import { prismThemes } from "@/data/themes/prism"; import { useData } from "vike-react/useData"; -import { useTheme } from "@/hooks/useTheme"; -import { Fragment, useMemo } from "react"; +import { SSRSnippet } from "./SSRSnippet"; const CSRSnippet = clientOnly(() => import("./CSRSnippet")); -function SSRSnippet({ - language, - children, - label, - showLineNumbers = false, -}: { - language: string; - children: string; - label?: string; - showLineNumbers?: boolean; -}) { - const { theme } = useTheme(); - - const prismTheme = useMemo(() => { - return prismThemes[theme]; - }, [theme]); - - return ( - <> - {label &&
{label}
} - - {({ className, style, tokens, getTokenProps }) => ( -
-            
-              {tokens.map((line, lineIndex) => (
-                
-                  {line
-                    .filter((token) => !token.empty)
-                    .map((token, tokenIndex) => (
-                      
-                    ))}
-                  {"\n"}
-                
-              ))}
-            
-          
- )} -
- - ); -} - export function Snippet({ path, language, diff --git a/app/data/docs/react/usereducer/page.md b/app/data/docs/react/usereducer/page.md index 0b6e710..9a46bad 100644 --- a/app/data/docs/react/usereducer/page.md +++ b/app/data/docs/react/usereducer/page.md @@ -47,19 +47,13 @@ Comme expliqué plus tôt, un reducer est une fonction qui prend en paramètre u Parlons dans un premier temps de la signature d'un reducer : {% tabs defaultSelectedTab="jsx" %} - {% tab value="jsx" label="JSX" %} - -{% snippet path="data/docs/react/usereducer/reducer-example.jsx" language="jsx" showLineNumbers=true /%} - +{% snippet path="react/reducer/reducer-example.jsx" language="jsx" showLineNumbers=true /%} {% /tab %} {% tab value="tsx" label="TSX" %} - -{% snippet path="data/docs/react/usereducer/reducer-example.tsx" language="tsx" showLineNumbers=true /%} - +{% snippet path="react/reducer/reducer-example.tsx" language="tsx" showLineNumbers=true /%} {% /tab %} - {% /tabs %} Comme tu peux le voir, on récupère bien deux paramètres : `state` et `action`. @@ -82,7 +76,7 @@ En déversant le contenu de l'état actuel, on s'assure de ne pas perdre ces pro Par exemple : -{% snippet path="data/docs/react/usereducer/reducer-why-spread-operator.jsx" language="jsx" showLineNumbers=true /%} +{% snippet path="react/reducer/reducer-why-spread-operator.jsx" language="jsx" showLineNumbers=true /%} On perdrait ici la propriété `message` si on ne la déversait pas dans le nouvel état. @@ -103,20 +97,13 @@ Ensuite, on va définir notre état initial : {% tabs defaultSelectedTab="js" %} {% tab value="js" label="JavaScript" %} -```js -const initialState = { count: 0 }; -``` +{% snippet path="react/reducer/reducer-initial-state.js" language="js" /%} {% /tab %} {% tab value="ts" label="TypeScript" %} -```ts -type State = { - count: number; -}; -const initialState: State = { count: 0 }; -``` +{% snippet path="react/reducer/reducer-initial-state.ts" language="ts" /%} {% /tab %} @@ -128,52 +115,13 @@ On peut maintenant définir notre reducer : {% tab value="js" label="JavaScript" %} -```js -const reducer = (state, action) => { - switch (action.type) { - case "INCREMENT": - return { ...state, count: state.count + 1 }; - case "DECREMENT": - return { ...state, count: state.count - 1 }; - case "RESET": - return { ...state, count: 0 }; - case "SET": - return { ...state, count: action.payload }; - default: - return state; - } -}; -``` +{% snippet path="react/reducer/reducer.js" language="js" showLineNumbers=true /%} {% /tab %} {% tab value="ts" label="TypeScript" %} -```ts -type State = { - count: number; -}; - -type Action = { - type: "INCREMENT" | "DECREMENT" | "RESET" | "SET"; - payload?: number; -}; - -const reducer = (state: State, action: Action) => { - switch (action.type) { - case "INCREMENT": - return { ...state, count: state.count + 1 }; - case "DECREMENT": - return { ...state, count: state.count - 1 }; - case "RESET": - return { ...state, count: 0 }; - case "SET": - return { ...state, count: action.payload! }; - default: - return state; - } -}; -``` +{% snippet path="react/reducer/reducer.ts" language="ts" showLineNumbers=true /%} {% /tab %} @@ -196,17 +144,13 @@ Enfin, on peut utiliser le hook useReducer dans notre composant : {% tab value="js" label="JavaScript" %} -```js -const [state, dispatch] = useReducer(reducer, initialState); -``` +{% snippet path="react/reducer/reducer-hook.js" language="js" /%} {% /tab %} {% tab value="ts" label="TypeScript" %} -```ts -const [state, dispatch] = useReducer(reducer, initialState); -``` +{% snippet path="react/reducer/reducer-hook.ts" language="ts" /%} {% /tab %} diff --git a/app/data/docs/react/usereducer/reducer-example.jsx b/app/data/snippets/react/reducer/reducer-example.jsx similarity index 100% rename from app/data/docs/react/usereducer/reducer-example.jsx rename to app/data/snippets/react/reducer/reducer-example.jsx diff --git a/app/data/docs/react/usereducer/reducer-example.tsx b/app/data/snippets/react/reducer/reducer-example.tsx similarity index 100% rename from app/data/docs/react/usereducer/reducer-example.tsx rename to app/data/snippets/react/reducer/reducer-example.tsx diff --git a/app/data/snippets/react/reducer/reducer-hook.js b/app/data/snippets/react/reducer/reducer-hook.js new file mode 100644 index 0000000..f6df2d1 --- /dev/null +++ b/app/data/snippets/react/reducer/reducer-hook.js @@ -0,0 +1 @@ +const [state, dispatch] = useReducer(reducer, initialState); diff --git a/app/data/snippets/react/reducer/reducer-hook.ts b/app/data/snippets/react/reducer/reducer-hook.ts new file mode 100644 index 0000000..d213e28 --- /dev/null +++ b/app/data/snippets/react/reducer/reducer-hook.ts @@ -0,0 +1 @@ +const [state, dispatch] = useReducer(reducer, initialState); diff --git a/app/data/snippets/react/reducer/reducer-initial-state.js b/app/data/snippets/react/reducer/reducer-initial-state.js new file mode 100644 index 0000000..16982c2 --- /dev/null +++ b/app/data/snippets/react/reducer/reducer-initial-state.js @@ -0,0 +1 @@ +const initialState = { count: 0 }; diff --git a/app/data/snippets/react/reducer/reducer-initial-state.ts b/app/data/snippets/react/reducer/reducer-initial-state.ts new file mode 100644 index 0000000..b9dacf5 --- /dev/null +++ b/app/data/snippets/react/reducer/reducer-initial-state.ts @@ -0,0 +1,4 @@ +type State = { + count: number; +}; +const initialState: State = { count: 0 }; diff --git a/app/data/docs/react/usereducer/reducer-why-spread-operator.jsx b/app/data/snippets/react/reducer/reducer-why-spread-operator.jsx similarity index 100% rename from app/data/docs/react/usereducer/reducer-why-spread-operator.jsx rename to app/data/snippets/react/reducer/reducer-why-spread-operator.jsx diff --git a/app/data/snippets/react/reducer/reducer.js b/app/data/snippets/react/reducer/reducer.js new file mode 100644 index 0000000..144a007 --- /dev/null +++ b/app/data/snippets/react/reducer/reducer.js @@ -0,0 +1,14 @@ +const reducer = (state, action) => { + switch (action.type) { + case "INCREMENT": + return { ...state, count: state.count + 1 }; + case "DECREMENT": + return { ...state, count: state.count - 1 }; + case "RESET": + return { ...state, count: 0 }; + case "SET": + return { ...state, count: action.payload }; + default: + return state; + } +}; diff --git a/app/data/snippets/react/reducer/reducer.ts b/app/data/snippets/react/reducer/reducer.ts new file mode 100644 index 0000000..465e076 --- /dev/null +++ b/app/data/snippets/react/reducer/reducer.ts @@ -0,0 +1,23 @@ +type State = { + count: number; +}; + +type Action = { + type: "INCREMENT" | "DECREMENT" | "RESET" | "SET"; + payload?: number; +}; + +const reducer = (state: State, action: Action) => { + switch (action.type) { + case "INCREMENT": + return { ...state, count: state.count + 1 }; + case "DECREMENT": + return { ...state, count: state.count - 1 }; + case "RESET": + return { ...state, count: 0 }; + case "SET": + return { ...state, count: action.payload! }; + default: + return state; + } +}; diff --git a/app/eslint.config.js b/app/eslint.config.js index 751c129..1ba6c0c 100644 --- a/app/eslint.config.js +++ b/app/eslint.config.js @@ -10,6 +10,7 @@ export default tseslint.config( { ignores: [ "dist/*", + "data/snippets/*", // Temporary compiled files "**/*.ts.build-*.mjs", diff --git a/app/services/DocsService.ts b/app/services/DocsService.ts index 602213b..7e1ae09 100644 --- a/app/services/DocsService.ts +++ b/app/services/DocsService.ts @@ -20,6 +20,7 @@ type DocData = { title: string; description: string; content: string; sections: type DocExtension = "mdx" | "md"; class DocsService { + private static readonly SNIPPETS_PATH = path.resolve(path.join(__dirname, "data", "snippets")); private static readonly DOCS_PATH = path.resolve(path.join(__dirname, "data")); private static readonly DOCS_EXTS: DocExtension[] = ["mdx", "md"]; // Order matters private static instance: DocsService; @@ -83,18 +84,23 @@ class DocsService { } public fetchSnippets(content: string) { - const identifierResults = snippetsService.identifyNewSnippets(content); - if (!identifierResults) return; + try { + const identifierResults = snippetsService.identifyNewSnippets(content); + if (!identifierResults) return []; - const [snippetsToFetch, allSnippets] = identifierResults; + const [snippetsToFetch, allSnippets] = identifierResults; - for (const snippet of snippetsToFetch) { - const absolutePath = path.resolve(__dirname, snippet); - const content = fs.readFileSync(absolutePath, "utf-8"); - snippetsService.setToCache(snippet, content); + for (const snippet of snippetsToFetch) { + const absolutePath = path.resolve(DocsService.SNIPPETS_PATH, snippet); + const content = fs.readFileSync(absolutePath, "utf-8"); + snippetsService.setToCache(snippet, content); + } + + return allSnippets || []; + } catch (error) { + console.error("Error fetching snippets:", error); + return []; } - - return allSnippets; } public async fetchDocs() { @@ -109,15 +115,13 @@ class DocsService { .replace(`.${extension}`, "") .replace(/\/$/g, ""); - const allSnippets = this.fetchSnippets(content); - const ast = Markdoc.parse(content); const title = ast.attributes?.frontmatter?.match(/^title:\s*(.*?)\s*$/m)?.[1]; const description = ast.attributes?.frontmatter?.match(/^description:\s*(.*?)\s*$/m)?.[1]?.replaceAll('"', ""); const sections: DocSection[] = [[title, null, []]]; this.extractSections(ast, sections); - this.setToCache(key, { title, description, content, sections, snippets: allSnippets || [] }); + this.setToCache(key, { title, description, content, sections, snippets: this.fetchSnippets(content) }); return { key, sections }; }), @@ -152,6 +156,7 @@ class DocsService { return doc; } catch (error) { + console.error("Error fetching docs:", error); return null; } } diff --git a/app/tsconfig.json b/app/tsconfig.json index 8703a13..a0bf316 100644 --- a/app/tsconfig.json +++ b/app/tsconfig.json @@ -20,5 +20,5 @@ "@syntax/*": ["./components/syntax/*"] } }, - "exclude": ["dist"] + "exclude": ["dist", "data/snippets"] }