diff --git a/.github/workflows/deploy-storybook.yml b/.github/workflows/deploy-storybook.yml new file mode 100644 index 000000000..076fb31b7 --- /dev/null +++ b/.github/workflows/deploy-storybook.yml @@ -0,0 +1,34 @@ +name: Build and Deploy Storybook +on: + push: + branches: + - master +jobs: + build-and-deploy-storybook: + runs-on: ubuntu-latest + permissions: + contents: write + concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + steps: + - name: Checkout Repo + uses: actions/checkout@v3 + - name: Get yarn cache directory path + id: yarn-cache-dir-path + run: echo "::set-output name=dir::$(yarn cache dir)" + - uses: actions/cache@v3 + id: yarn-cache + with: + path: ${{ steps.yarn-cache-dir-path.outputs.dir }} + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + - name: Install modules + run: yarn install --frozen-lockfile + - name: Build Storybook + run: yarn build:storybook + - name: Deploy Storybook + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./storybook-static diff --git a/.gitignore b/.gitignore index a7d4a380b..7f007efaf 100644 --- a/.gitignore +++ b/.gitignore @@ -24,7 +24,8 @@ dist-ssr *.sw? # Custom -public/data +reports/* +storybook-static public/imgs/posts public/imgs/authors public/feed.xml diff --git a/bin/build.ts b/bin/build.ts index 9de153661..a7a3af409 100644 --- a/bin/build.ts +++ b/bin/build.ts @@ -1,30 +1,70 @@ +import { cpSync } from 'node:fs'; +import { resolve } from 'node:path'; import { build as buildVite } from 'vite'; +import { createServer as createViteServer } from 'vite'; + +const rootDir = process.cwd(); +const outDir = resolve(rootDir, 'dist'); +const outPublicDir = resolve(rootDir, 'dist/public'); +const args = process.argv.slice(2).reduce>((currentArgs, currentArg) => { + const [key, value] = currentArg.replace('--', '').split('='); + currentArgs[key] = value; + return currentArgs; +}, {}); + +const copyImgs = (): void => { + const srcDir = resolve(rootDir, '_assets'); + const outputDir = resolve(rootDir, 'public/imgs'); + cpSync(srcDir, outputDir, { recursive: true }); +}; + +const generateFeeds = async (): Promise => { + const baseUrl = process.env.BASE_URL || '/'; + + const vite = await createViteServer({ + server: { middlewareMode: true }, + base: baseUrl, + appType: 'custom', + }); + + try { + const { generateFeedFile } = await vite.ssrLoadModule('/src/helpers/feedHelper.ts'); + generateFeedFile({ rootDir: outPublicDir }); + } catch (e) { + console.error(e); + } finally { + vite.close(); + } +}; const build = async (): Promise => { - // 1. Build Server - buildVite({ - base: process.env.BASE_URL || '/', - build: { - emptyOutDir: false, - ssr: true, - outDir: 'dist', - rollupOptions: { - input: 'src/server.ts', + copyImgs(); + if (args.ssr) { + await buildVite({ + base: process.env.BASE_URL || '/', + build: { + emptyOutDir: false, + ssr: true, + outDir: outDir, + rollupOptions: { + input: 'src/server.ts', + }, }, - }, - mode: process.env.NODE_ENV || 'production', - }); + mode: process.env.NODE_ENV || 'production', + }); + } - // 2. Build Client - buildVite({ + await buildVite({ base: process.env.BASE_URL || '/', build: { emptyOutDir: false, manifest: true, - outDir: 'dist/public', + outDir: outPublicDir, }, mode: process.env.NODE_ENV || 'production', }); + + generateFeeds(); }; build(); diff --git a/bin/dev.ts b/bin/dev.ts index 1eb527166..9c4558884 100644 --- a/bin/dev.ts +++ b/bin/dev.ts @@ -1,5 +1,10 @@ +import chokidar from 'chokidar'; +import { cpSync } from 'node:fs'; +import { resolve } from 'node:path'; import { createServer as createViteServer } from 'vite'; +const rootDir = process.cwd(); + const dev = async (): Promise => { const vite = await createViteServer({ base: process.env.BASE_URL || '/', @@ -8,6 +13,14 @@ const dev = async (): Promise => { host: '0.0.0.0', }, }); + + const assetsDir = resolve(rootDir, '_assets'); + const watcher = chokidar.watch(assetsDir, { cwd: assetsDir }); + + watcher.on('all', (event, filePath) => { + cpSync(resolve(assetsDir, filePath), resolve(rootDir, 'public/imgs', filePath), { recursive: true }); + }); + await vite.ssrLoadModule('/src/server.ts'); }; diff --git a/bin/prepare.ts b/bin/prepare.ts deleted file mode 100644 index 338a410ab..000000000 --- a/bin/prepare.ts +++ /dev/null @@ -1,39 +0,0 @@ -import fs from 'node:fs'; -import { resolve } from 'node:path'; -import path from 'node:path'; -import { createServer as createViteServer } from 'vite'; - -const rootDir = process.cwd(); - -const copyImgs = (): void => { - const srcDir = path.resolve(rootDir, '_assets'); - const outputDir = path.resolve(rootDir, 'public/imgs'); - fs.cpSync(srcDir, outputDir, { recursive: true }); -}; - -const generateDataAndFeeds = async (): Promise => { - const baseUrl = process.env.BASE_URL || '/'; - const rootDir = resolve(process.cwd(), 'public'); - const vite = await createViteServer({ - server: { middlewareMode: true }, - base: baseUrl, - appType: 'custom', - }); - - try { - const { generateDataFiles, generateFeedFile } = await vite.ssrLoadModule('/src/helpers/dataHelper.ts'); - generateDataFiles({ rootDir: resolve(rootDir, 'data') }); - generateFeedFile({ rootDir }); - } catch (e) { - console.error(e); - } finally { - vite.close(); - } -}; - -const prepare = (): void => { - copyImgs(); - generateDataAndFeeds(); -}; - -prepare(); diff --git a/bin/prerender.ts b/bin/prerender.ts index 6e9a764ce..b53b8b9f8 100644 --- a/bin/prerender.ts +++ b/bin/prerender.ts @@ -1,11 +1,6 @@ -import { existsSync, mkdirSync, writeFileSync } from 'node:fs'; -import { dirname, resolve } from 'node:path'; +import { resolve } from 'node:path'; import { createServer as createViteServer } from 'vite'; -import { createRequestByUrl } from '../src/helpers/requestHelper'; - -const rootDir = process.cwd(); - const prerender = async (): Promise => { const baseUrl = process.env.BASE_URL || '/'; const vite = await createViteServer({ @@ -16,40 +11,11 @@ const prerender = async (): Promise => { }); try { - const { render } = await vite.ssrLoadModule('/src/entry-server.tsx'); - const { getLinksAndScripts } = await vite.ssrLoadModule('/src/helpers/ssrHelper.ts'); - - const { getI18nInstanceByLang, getUrlsByLang } = await vite.ssrLoadModule('/src/helpers/prerenderHelper.ts'); - const urlsByLang = getUrlsByLang({ baseUrl }); - const { links, scripts } = getLinksAndScripts({ - dirname: resolve(rootDir, 'dist'), - baseUrl: process.env.BASE_URL, + const { generateHtmlFiles } = await vite.ssrLoadModule('/src/helpers/prerenderHelper.ts'); + generateHtmlFiles({ + rootDir: resolve(process.cwd(), 'dist'), + baseUrl, }); - - for (const { lang, url } of urlsByLang) { - const i18n = getI18nInstanceByLang(lang); - const html = await render({ - request: createRequestByUrl({ url }), - i18n, - links, - scripts, - }); - - const is404 = /\/404/.test(url); - const filePath = resolve( - rootDir, - 'dist/public', - is404 ? '404.html' : `${url.length > 1 ? `${url.substring(1)}/` : ''}index.html` - ).replace(baseUrl, '/'); - - const dirPath = dirname(filePath); - if (!existsSync(dirPath)) { - mkdirSync(dirPath, { recursive: true }); - } - writeFileSync(filePath, html, 'utf8'); - } - - console.log('🦖🖨 Your static site is ready to deploy from dist'); } catch (e) { console.error(e); } finally { diff --git a/package.json b/package.json index bff049c07..7f7728213 100644 --- a/package.json +++ b/package.json @@ -4,12 +4,11 @@ "type": "module", "scripts": { "postinstall": "husky install", - "prepare": "yarn ts-node bin/prepare", "ts-node": "ts-node -r dotenv/config", "validate-and-format-markdown": "yarn ts-node bin/validateAndFormatMarkdown", "indexation:algolia": "yarn ts-node bin/indexationAlgolia", "build": "yarn ts-node bin/build", - "prerender": "yarn prepare && yarn build && yarn ts-node bin/prerender", + "prerender": "yarn build && yarn ts-node bin/prerender", "start:dev": "yarn ts-node bin/dev", "start:prod": "NODE_ENV=production node dist/server", "start:static": "npx serv --path dist/public", @@ -32,7 +31,7 @@ "node": ">= 16.0" }, "dependencies": { - "@eleven-labs/design-system": "^0.5.4", + "@eleven-labs/design-system": "^0.6.0", "@remix-run/router": "^1.3.2", "algoliasearch": "^4.14.3", "classnames": "^2.3.2", @@ -48,23 +47,19 @@ "react-dom": "^18.2.0", "react-ga": "^3.3.1", "react-i18next": "^12.1.5", - "react-markdown": "^8.0.5", - "react-router-dom": "^6.8.1", - "react-syntax-highlighter": "^15.5.0", - "rehype-raw": "^6.1.1", - "rehype-rewrite": "^3.0.6", + "react-router-dom": "^6.10.0", "serve-static": "^1.15.0" }, "devDependencies": { "@babel/core": "^7.20.12", "@elevenlabs/eslint-config": "^0.0.1", - "@storybook/addon-actions": "^7.0.0-beta.48", - "@storybook/addon-essentials": "^7.0.0-beta.48", - "@storybook/addon-interactions": "^7.0.0-beta.48", - "@storybook/addon-links": "^7.0.0-beta.48", - "@storybook/react": "^7.0.0-beta.48", - "@storybook/react-vite": "^7.0.0-beta.48", - "@storybook/testing-library": "^0.0.14-next.1", + "@storybook/addon-actions": "^7.0.2", + "@storybook/addon-essentials": "^7.0.2", + "@storybook/addon-interactions": "^7.0.2", + "@storybook/addon-links": "^7.0.2", + "@storybook/react": "^7.0.2", + "@storybook/react-vite": "^7.0.2", + "@storybook/testing-library": "^0.1.0", "@types/express": "^4.17.17", "@types/i18next": "^13.0.0", "@types/markdown-it": "^12.2.3", @@ -72,8 +67,6 @@ "@types/react": "^18.0.28", "@types/react-dom": "^18.0.11", "@types/react-i18next": "^8.1.0", - "@types/react-router-dom": "^5.3.3", - "@types/react-syntax-highlighter": "^15.5.6", "@types/sanitize-html": "^2.8.1", "@vitejs/plugin-react": "^3.1.0", "autoprefixer": "^10.4.13", @@ -82,22 +75,29 @@ "dotenv": "^16.0.3", "eslint": "^8.27.0", "feed": "^4.2.2", + "glob": "^9.3.2", "gray-matter": "^4.0.3", "husky": "^8.0.3", "lint-staged": "^13.1.2", - "markdown-it": "^13.0.1", "node-sass": "^8.0.0", "postcss": "^8.4.21", "postcss-normalize": "^10.0.1", "postcss-scss": "^4.0.6", "prettier": "^2.7.1", + "rehype-raw": "^6.1.1", + "rehype-react": "^7.1.2", + "rehype-rewrite": "^3.0.6", + "remark-parse": "^10.0.1", + "remark-rehype": "^10.1.0", + "rollup-plugin-visualizer": "^5.9.0", "sanitize-html": "^2.10.0", "sass": "^1.58.1", - "storybook": "^7.0.0-beta.48", + "storybook": "^7.0.2", "stylelint": "^15.2.0", "stylelint-config-standard-scss": "^7.0.1", "ts-node": "^10.9.1", "typescript": "^4.7.4", + "unified": "^10.1.2", "vite": "^4.1.1", "vite-tsconfig-paths": "^4.0.5", "yup": "^0.32.11" diff --git a/plugins/markdown/index.ts b/plugins/markdown/index.ts new file mode 100644 index 000000000..1f2bdd4c4 --- /dev/null +++ b/plugins/markdown/index.ts @@ -0,0 +1,56 @@ +import matter from 'gray-matter'; +import fs from 'node:fs'; +import path from 'node:path'; +import { PluginOption } from 'vite'; + +import { markdownToHtml } from './markdownToHtml'; + +const cache: Record = {}; + +const markdownPlugin = (): PluginOption => { + return { + name: 'vite-plugin-markdown', + config: ({ base }): void => { + process.env.BASE_URL = base; + }, + transform: (raw: string, id: string): null | { code: string } => { + if (!/\.md$/.test(id)) { + return null; + } + + const stat = fs.statSync(id); + const cached = cache[id]; + + if (cached && cached.mtime === stat.mtimeMs) { + return { code: cached.code }; + } + + try { + const markdown = fs.readFileSync(id, 'utf-8'); + const matterResult = matter(markdown); + const content = matterResult.content + .replace(/{{\s*?site.baseurl\s*?}}\/assets\//g, `${process.env.BASE_URL || '/'}imgs/posts/`) + .replaceAll('/_assets/posts/', `${process.env.BASE_URL || '/'}imgs/posts/`) + .replace(/({% raw %}|{% endraw %})/g, ''); + const html = markdownToHtml(content); + const code = [ + `const attributes = ${JSON.stringify(matterResult.data)};`, + `const content = ${JSON.stringify(html)};`, + `export { attributes, content }`, + ].join('\n'); + cache[id] = { code, mtime: stat.mtimeMs }; + + return { code }; + } catch (error) { + console.log({ error, id }); + return null; + } + }, + configureServer: (server): void => { + server.watcher.add(path.resolve(process.cwd(), './_posts')); + server.watcher.add(path.resolve(process.cwd(), './_authors')); + }, + }; +}; + +export default markdownPlugin; diff --git a/plugins/markdown/markdownToHtml.tsx b/plugins/markdown/markdownToHtml.tsx new file mode 100644 index 000000000..a9827974c --- /dev/null +++ b/plugins/markdown/markdownToHtml.tsx @@ -0,0 +1,175 @@ +import { + AsProps, + Blockquote, + Box, + Heading, + Link, + Reminder, + ReminderVariantType, + SyntaxHighlighter, + Text, +} from '@eleven-labs/design-system'; +import React from 'react'; +import ReactDOMServer from 'react-dom/server'; +import rehypeRaw from 'rehype-raw'; +import rehypeReact from 'rehype-react'; +import rehypeRewrite from 'rehype-rewrite'; +import remarkParse from 'remark-parse'; +import remark2rehype from 'remark-rehype'; +import { unified } from 'unified'; + +import { intersection } from '../../src/helpers/objectHelper'; + +const getReminderVariantByAdmonitionVariant = (admonitionVariant: string): ReminderVariantType => { + switch (admonitionVariant) { + case 'abstract': + case 'summary': + case 'tldr': + return 'summary'; + case 'info': + case 'todo': + return 'info'; + case 'tip': + case 'hint': + case 'important': + return 'tip'; + case 'success': + case 'check': + case 'done': + return 'success'; + case 'question': + case 'help': + case 'faq': + return 'question'; + case 'warning': + case 'caution': + case 'attention': + return 'warning'; + case 'failure': + case 'fail': + case 'missing': + return 'failure'; + case 'danger': + case 'error': + return 'danger'; + case 'bug': + return 'bug'; + case 'example': + return 'example'; + case 'quote': + case 'cite': + return 'quote'; + case 'note': + default: + return 'note'; + } +}; + +export const markdownToHtml = (markdownContent: string): string => { + const reactComponent = unified() + .use(remarkParse) + .use(remark2rehype, { allowDangerousHtml: true }) + .use(rehypeRaw) + .use(rehypeRewrite, { + selector: 'div', + rewrite: (node): void => { + if (node.type === 'element') { + const classNames: string[] = (node?.properties?.className as string[]) || []; + if (node.properties?.markdown && intersection(['admonition'], classNames).length > 0) { + const reminderVariant = getReminderVariantByAdmonitionVariant(classNames[1]); + const titleNode = node.children.shift(); + const reminderTitle = + titleNode?.type === 'element' + ? titleNode?.children?.map((child) => (child.type === 'text' ? child.value : '')).join() + : ''; + node.properties = { + 'reminder-variant': reminderVariant, + 'reminder-title': reminderTitle, + }; + } + } + }, + }) + .use(rehypeReact, { + createElement: React.createElement, + Fragment: React.Fragment, + passNode: true, + components: { + div: ({ node, children, ...props }): JSX.Element => { + const reminderProps = props as { ['reminder-variant']?: ReminderVariantType; ['reminder-title']?: string }; + if (reminderProps?.['reminder-variant'] && reminderProps?.['reminder-title']) { + return ( + + {children} + + ); + } + + return {children}; + }, + h2: ({ children }): JSX.Element => ( + + {children} + + ), + h3: ({ children }): JSX.Element => ( + + {children} + + ), + h4: ({ children }): JSX.Element => ( + + {children} + + ), + p: ({ node, ...props }): JSX.Element => , + li: ({ node, ...props }): JSX.Element => , + strong: ({ children }): JSX.Element => ( + + {children} + + ), + em: ({ children }): JSX.Element => ( + + {children} + + ), + i: ({ children }): JSX.Element => ( + + {children} + + ), + a: ({ node, ...props }): JSX.Element => { + const isExternalLink = (props.href as string)?.match(/^http(s)?:\/\//); + return ( + + ); + }, + blockquote: ({ node, ...props }): JSX.Element =>
, + pre: ({ node, ...props }): JSX.Element => , + code: ({ node, className, children, ...props }): JSX.Element => { + const match = /language-(\w+)/.exec(className || ''); + return match ? ( + + ) : ( + + {children} + + ); + }, + img: ({ node, ...props }): JSX.Element => { + return React.createElement('img', { + ...props, + style: { + display: 'block', + maxWidth: '100%', + margin: 'var(--spacing-xs) auto', + }, + }); + }, + }, + }) + .processSync(markdownContent).result; + + return String(ReactDOMServer.renderToStaticMarkup(reactComponent)); +}; diff --git a/public/manifest.json b/public/web-app-manifest.json similarity index 100% rename from public/manifest.json rename to public/web-app-manifest.json diff --git a/src/components/AutocompleteField/AutocompleteField.tsx b/src/components/AutocompleteField/AutocompleteField.tsx index abd9b39e9..9f5ad2b4f 100644 --- a/src/components/AutocompleteField/AutocompleteField.tsx +++ b/src/components/AutocompleteField/AutocompleteField.tsx @@ -15,6 +15,7 @@ export type AutocompleteFieldOptions = { placeholder: string; searchLink: Exclude; defaultValue?: string; + onEnter?: (value: string) => void; }; export type AutocompleteFieldProps = BoxProps & @@ -32,10 +33,17 @@ export const AutocompleteField = forwardRef( searchNotFound, onInputValueChange, onSelectedItemChange, + onEnter, ...props }, ref ) => { + const handleKeyDown = (event: React.KeyboardEvent): void => { + if (onEnter && event.key === 'Enter') { + onEnter(event.currentTarget.value); + } + }; + const { getInputProps, getMenuProps, getItemProps, selectItem, toggleMenu, isOpen, inputValue, highlightedIndex } = useCombobox({ defaultInputValue: defaultValue, @@ -57,7 +65,7 @@ export const AutocompleteField = forwardRef( return ( } buttonClose={{ onClick: onClose }} className="autocomplete-field__input" diff --git a/src/components/BackLink/BackLink.stories.tsx b/src/components/BackLink/BackLink.stories.tsx new file mode 100644 index 000000000..fa1838058 --- /dev/null +++ b/src/components/BackLink/BackLink.stories.tsx @@ -0,0 +1,17 @@ +import { Meta, StoryFn } from '@storybook/react'; +import React from 'react'; + +import { BackLink } from './BackLink'; + +export default { + title: 'Components/BackLink', + component: BackLink, + args: { + label: 'Retour', + href: '/', + }, +} as Meta; + +const Template: StoryFn = (args) => ; + +export const Overview = Template.bind({}); diff --git a/src/components/BackLink/BackLink.tsx b/src/components/BackLink/BackLink.tsx new file mode 100644 index 000000000..b22d97f4e --- /dev/null +++ b/src/components/BackLink/BackLink.tsx @@ -0,0 +1,13 @@ +import { AsProps, Link } from '@eleven-labs/design-system'; +import React from 'react'; + +export type BackLinkOptions = { + label: React.ReactNode; +}; +export type BackLinkProps = AsProps<'a'> & BackLinkOptions; + +export const BackLink: React.FC = ({ label, ...props }) => ( + + {label} + +); diff --git a/src/components/BackLink/index.ts b/src/components/BackLink/index.ts new file mode 100644 index 000000000..600436038 --- /dev/null +++ b/src/components/BackLink/index.ts @@ -0,0 +1 @@ +export * from './BackLink'; diff --git a/src/components/Blockquote/Blockquote.scss b/src/components/Blockquote/Blockquote.scss deleted file mode 100644 index 66b58b440..000000000 --- a/src/components/Blockquote/Blockquote.scss +++ /dev/null @@ -1,6 +0,0 @@ -.blockquote { - margin: var(--spacing-xs) var(--spacing-0); - padding-left: var(--spacing-m); - border-left: 5px solid var(--color-yellow); - font-style: italic; -} diff --git a/src/components/Blockquote/Blockquote.stories.tsx b/src/components/Blockquote/Blockquote.stories.tsx deleted file mode 100644 index 23fdca69d..000000000 --- a/src/components/Blockquote/Blockquote.stories.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { Meta, StoryFn } from '@storybook/react'; -import React from 'react'; - -import { Blockquote } from './Blockquote'; - -export default { - title: 'Components/Blockquote', - component: Blockquote, - args: { - content: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce nisi lectus, tincidunt nec nisl ut, dapibus ornare eros.', - }, -} as Meta; - -const Template: StoryFn = (args) =>
; - -export const Overview = Template.bind({}); diff --git a/src/components/Blockquote/Blockquote.tsx b/src/components/Blockquote/Blockquote.tsx deleted file mode 100644 index ff0ca248a..000000000 --- a/src/components/Blockquote/Blockquote.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import './Blockquote.scss'; - -import { AsProps, Box, ColorSystemProps, MarginSystemProps } from '@eleven-labs/design-system'; -import classNames from 'classnames'; -import React from 'react'; - -export type BlockquoteProps = AsProps<'blockquote'> & MarginSystemProps & Pick; - -export const Blockquote: React.FC = (props) => ( - -); diff --git a/src/components/Blockquote/index.ts b/src/components/Blockquote/index.ts deleted file mode 100644 index e16e2f06f..000000000 --- a/src/components/Blockquote/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './Blockquote'; diff --git a/src/components/MarkdownToHtml/MarkdownToHtml.tsx b/src/components/MarkdownToHtml/MarkdownToHtml.tsx deleted file mode 100644 index 821bc568c..000000000 --- a/src/components/MarkdownToHtml/MarkdownToHtml.tsx +++ /dev/null @@ -1,168 +0,0 @@ -import { As, AsProps, Box, BoxProps, Heading, Link, Text } from '@eleven-labs/design-system'; -import React from 'react'; -import ReactMarkdown from 'react-markdown'; -import rehypeRaw from 'rehype-raw'; -import rehypeRewrite, { RehypeRewriteOptions } from 'rehype-rewrite'; - -import { Blockquote, Reminder, ReminderVariantType, SyntaxHighlighter } from '@/components'; -import { Script } from '@/components/Script/Script'; -import { getPathFile } from '@/helpers/assetHelper'; -import { intersection } from '@/helpers/objectHelper'; - -export type MarkdownToHtmlOptions = { - content: string; -}; -export type MarkdownToHtmlProps = BoxProps & MarkdownToHtmlOptions; - -const getReminderVariantByAdmonitionVariant = (admonitionVariant: string): ReminderVariantType => { - switch (admonitionVariant) { - case 'abstract': - case 'summary': - case 'tldr': - return 'summary'; - case 'info': - case 'todo': - return 'info'; - case 'tip': - case 'hint': - case 'important': - return 'tip'; - case 'success': - case 'check': - case 'done': - return 'success'; - case 'question': - case 'help': - case 'faq': - return 'question'; - case 'warning': - case 'caution': - case 'attention': - return 'warning'; - case 'failure': - case 'fail': - case 'missing': - return 'failure'; - case 'danger': - case 'error': - return 'danger'; - case 'bug': - return 'bug'; - case 'example': - return 'example'; - case 'quote': - case 'cite': - return 'quote'; - case 'note': - default: - return 'note'; - } -}; - -export const MarkdownToHtml: React.FC = ({ content, ...props }) => { - return ( - - { - if (node.type === 'element') { - const classNames: string[] = (node?.properties?.className as string[]) || []; - if (node.properties?.markdown && intersection(['admonition'], classNames).length > 0) { - const reminderVariant = getReminderVariantByAdmonitionVariant(classNames[1]); - const titleNode = node.children.shift(); - const reminderTitle = - titleNode?.type === 'element' - ? titleNode?.children?.map((child) => (child.type === 'text' ? child.value : '')).join() - : ''; - node.properties = { - reminderVariant, - reminderTitle, - }; - } - } - }, - }, - ] as [typeof rehypeRewrite, RehypeRewriteOptions], - ]} - children={content.replace(/{:([^}]+)}/g, '')} - components={{ - div: ({ node, children, ...props }): JSX.Element => { - const reminderProps = props as { reminderVariant?: ReminderVariantType; reminderTitle?: string }; - if (reminderProps?.reminderVariant && reminderProps?.reminderTitle) { - return ( - - {children} - - ); - } - - return {children}; - }, - h2: ({ children }): JSX.Element => ( - - {children} - - ), - h3: ({ children }): JSX.Element => ( - - {children} - - ), - h4: ({ children }): JSX.Element => ( - - {children} - - ), - p: ({ node, ...props }): JSX.Element => , - li: ({ node, ordered, ...props }): JSX.Element => , - strong: ({ children }): JSX.Element => ( - - {children} - - ), - em: ({ children }): JSX.Element => ( - - {children} - - ), - i: ({ children }): JSX.Element => ( - - {children} - - ), - a: ({ node, ...props }): JSX.Element => , - blockquote: ({ node, ...props }): JSX.Element =>
, - pre: ({ node, ...props }): JSX.Element => , - code: ({ node, inline, className, children, ...props }): JSX.Element => { - const match = /language-(\w+)/.exec(className || ''); - return !inline && match ? ( - - ) : ( - - {children} - - ); - }, - img: ({ node, ...props }): JSX.Element => { - const src = !(props.src as string).match(/^http(s)?:\/\//) ? getPathFile(props.src as string) : props.src; - return React.createElement('img', { - src, - ...props, - style: { - display: 'block', - maxWidth: '100%', - margin: 'var(--spacing-xs) auto', - }, - }); - }, - script: ({ node, ...props }): JSX.Element =>