@intlayer/design-system 9.3.2 → 9.4.0-canary.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/esm/api/hooks/analytics.mjs +6 -5
- package/dist/esm/api/hooks/analytics.mjs.map +1 -1
- package/dist/esm/api/hooks/auth.mjs +2 -0
- package/dist/esm/api/hooks/auth.mjs.map +1 -1
- package/dist/esm/api/index.mjs +2 -2
- package/dist/esm/api/useAuth/useOAuth2.mjs +1 -1
- package/dist/esm/api/useAuth/useSession.mjs +1 -1
- package/dist/esm/components/Accordion/Accordion.mjs +2 -2
- package/dist/esm/components/Accordion/Accordion.mjs.map +1 -1
- package/dist/esm/components/Browser/Browser.mjs +1 -1
- package/dist/esm/components/Browser/Browser.mjs.map +1 -1
- package/dist/esm/components/ContentEditor/ContentEditorTextArea.mjs +1 -1
- package/dist/esm/components/CopyButton/index.mjs +2 -1
- package/dist/esm/components/CopyButton/index.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/ContentEditor.mjs +4 -5
- package/dist/esm/components/DictionaryFieldEditor/ContentEditor.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/ContentEditorView/TextEditor.mjs +6 -10
- package/dist/esm/components/DictionaryFieldEditor/ContentEditorView/TextEditor.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/DictionaryCreationForm/DictionaryCreationForm.mjs +1 -1
- package/dist/esm/components/DictionaryFieldEditor/DictionaryDetails/DictionaryDetailsForm.mjs +6 -6
- package/dist/esm/components/DictionaryFieldEditor/DictionaryDetails/DictionaryDetailsForm.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/DictionaryFieldEditor.mjs +3 -7
- package/dist/esm/components/DictionaryFieldEditor/DictionaryFieldEditor.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/NavigationView/NavigationViewNode.mjs +1 -1
- package/dist/esm/components/DictionaryFieldEditor/SaveForm/SaveForm.mjs +2 -2
- package/dist/esm/components/DictionaryFieldEditor/StructureView/StructureView.mjs +1 -1
- package/dist/esm/components/ExpandCollapse/ExpandCollapse.mjs +19 -12
- package/dist/esm/components/ExpandCollapse/ExpandCollapse.mjs.map +1 -1
- package/dist/esm/components/Form/elements/OTPElement.mjs +4 -4
- package/dist/esm/components/Form/elements/OTPElement.mjs.map +1 -1
- package/dist/esm/components/HTMLRender/HTMLRender.mjs +2 -2
- package/dist/esm/components/HTMLRender/HTMLRender.mjs.map +1 -1
- package/dist/esm/components/IDE/Code.mjs +3 -3
- package/dist/esm/components/IDE/Code.mjs.map +1 -1
- package/dist/esm/components/IDE/CodeBlockClient.mjs +16 -4
- package/dist/esm/components/IDE/CodeBlockClient.mjs.map +1 -1
- package/dist/esm/components/IDE/CodeBlockHighlight.mjs +14 -5
- package/dist/esm/components/IDE/CodeBlockHighlight.mjs.map +1 -1
- package/dist/esm/components/IDE/CodeBlockServer.mjs +5 -3
- package/dist/esm/components/IDE/CodeBlockServer.mjs.map +1 -1
- package/dist/esm/components/IDE/CodeBlockShiki.mjs +18 -18
- package/dist/esm/components/IDE/CodeBlockShiki.mjs.map +1 -1
- package/dist/esm/components/IDE/IDE.mjs +2 -5
- package/dist/esm/components/IDE/IDE.mjs.map +1 -1
- package/dist/esm/components/IDE/MarkDownRender.mjs +1 -2
- package/dist/esm/components/IDE/MarkDownRender.mjs.map +1 -1
- package/dist/esm/components/IDE/index.mjs +2 -2
- package/dist/esm/components/IDE/shikiThemes.mjs +18 -0
- package/dist/esm/components/IDE/shikiThemes.mjs.map +1 -0
- package/dist/esm/components/Link/Link.mjs +10 -5
- package/dist/esm/components/Link/Link.mjs.map +1 -1
- package/dist/esm/components/LocaleSwitcherContentDropDown/LocaleSwitcherContent.mjs +1 -1
- package/dist/esm/components/MarkDownRender/MarkDownIframe.mjs +105 -4
- package/dist/esm/components/MarkDownRender/MarkDownIframe.mjs.map +1 -1
- package/dist/esm/components/MarkDownRender/MarkDownRender.mjs +16 -16
- package/dist/esm/components/MarkDownRender/MarkDownRender.mjs.map +1 -1
- package/dist/esm/components/MaxHeightSmoother/index.mjs +1 -1
- package/dist/esm/components/MaxHeightSmoother/index.mjs.map +1 -1
- package/dist/esm/components/MaxWidthSmoother/index.mjs +1 -1
- package/dist/esm/components/MaxWidthSmoother/index.mjs.map +1 -1
- package/dist/esm/components/Modal/Modal.mjs +2 -2
- package/dist/esm/components/Navbar/MobileNavbar.mjs +1 -1
- package/dist/esm/components/Pagination/Pagination.mjs +4 -4
- package/dist/esm/components/Pagination/Pagination.mjs.map +1 -1
- package/dist/esm/components/Pattern/GridDistortionPattern.mjs +103 -17
- package/dist/esm/components/Pattern/GridDistortionPattern.mjs.map +1 -1
- package/dist/esm/components/Popover/dynamic.mjs +73 -28
- package/dist/esm/components/Popover/dynamic.mjs.map +1 -1
- package/dist/esm/components/Popover/index.mjs +2 -2
- package/dist/esm/components/Popover/static.mjs +47 -18
- package/dist/esm/components/Popover/static.mjs.map +1 -1
- package/dist/esm/components/RightDrawer/RightDrawer.mjs +5 -4
- package/dist/esm/components/RightDrawer/RightDrawer.mjs.map +1 -1
- package/dist/esm/components/SwitchSelector/useSwitchSelector.mjs +2 -2
- package/dist/esm/components/SwitchSelector/useSwitchSelector.mjs.map +1 -1
- package/dist/esm/components/Tab/Tab.mjs +38 -17
- package/dist/esm/components/Tab/Tab.mjs.map +1 -1
- package/dist/esm/components/TabSelector/TabSelector.mjs +2 -2
- package/dist/esm/components/TabSelector/TabSelector.mjs.map +1 -1
- package/dist/esm/components/TextArea/AutoSizeTextArea.mjs +3 -2
- package/dist/esm/components/TextArea/AutoSizeTextArea.mjs.map +1 -1
- package/dist/esm/components/ThemeSwitcherDropDown/DesktopThemeSwitcher.mjs +10 -18
- package/dist/esm/components/ThemeSwitcherDropDown/DesktopThemeSwitcher.mjs.map +1 -1
- package/dist/esm/components/ThemeSwitcherDropDown/MobileThemeSwitcher.mjs +14 -13
- package/dist/esm/components/ThemeSwitcherDropDown/MobileThemeSwitcher.mjs.map +1 -1
- package/dist/esm/components/index.mjs +3 -3
- package/dist/esm/hooks/index.mjs +9 -9
- package/dist/esm/hooks/useDevice.mjs +16 -6
- package/dist/esm/hooks/useDevice.mjs.map +1 -1
- package/dist/esm/hooks/useItemSelector.mjs +110 -112
- package/dist/esm/hooks/useItemSelector.mjs.map +1 -1
- package/dist/esm/hooks/useScrollY.mjs +64 -45
- package/dist/esm/hooks/useScrollY.mjs.map +1 -1
- package/dist/esm/hooks/useStickToBottom.mjs +69 -0
- package/dist/esm/hooks/useStickToBottom.mjs.map +1 -0
- package/dist/esm/libs/auth.mjs +16 -3
- package/dist/esm/libs/auth.mjs.map +1 -1
- package/dist/esm/providers/ThemeProvider.mjs +143 -0
- package/dist/esm/providers/ThemeProvider.mjs.map +1 -0
- package/dist/esm/providers/index.mjs +2 -1
- package/dist/esm/routes.mjs +2 -1
- package/dist/esm/routes.mjs.map +1 -1
- package/dist/esm/structured-data/buildCreativeWorkJsonLd.mjs +1 -1
- package/dist/esm/structured-data/buildCreativeWorkJsonLd.mjs.map +1 -1
- package/dist/esm/utils/index.mjs +3 -1
- package/dist/esm/utils/observeScrollExtent.mjs +39 -0
- package/dist/esm/utils/observeScrollExtent.mjs.map +1 -0
- package/dist/esm/utils/scheduleFrameTask.mjs +44 -0
- package/dist/esm/utils/scheduleFrameTask.mjs.map +1 -0
- package/dist/types/api/hooks/analytics.d.ts +5 -3
- package/dist/types/api/hooks/analytics.d.ts.map +1 -1
- package/dist/types/api/hooks/auth.d.ts.map +1 -1
- package/dist/types/components/Browser/Browser.d.ts +7 -1
- package/dist/types/components/Browser/Browser.d.ts.map +1 -1
- package/dist/types/components/CopyButton/index.d.ts +9 -3
- package/dist/types/components/CopyButton/index.d.ts.map +1 -1
- package/dist/types/components/DictionaryFieldEditor/ContentEditor.d.ts +0 -1
- package/dist/types/components/DictionaryFieldEditor/ContentEditor.d.ts.map +1 -1
- package/dist/types/components/DictionaryFieldEditor/ContentEditorView/TextEditor.d.ts +0 -1
- package/dist/types/components/DictionaryFieldEditor/ContentEditorView/TextEditor.d.ts.map +1 -1
- package/dist/types/components/DictionaryFieldEditor/DictionaryDetails/DictionaryDetailsForm.d.ts +0 -1
- package/dist/types/components/DictionaryFieldEditor/DictionaryDetails/DictionaryDetailsForm.d.ts.map +1 -1
- package/dist/types/components/ExpandCollapse/ExpandCollapse.d.ts.map +1 -1
- package/dist/types/components/HTMLRender/HTMLRender.d.ts +1 -2
- package/dist/types/components/HTMLRender/HTMLRender.d.ts.map +1 -1
- package/dist/types/components/IDE/Code.d.ts +6 -1
- package/dist/types/components/IDE/Code.d.ts.map +1 -1
- package/dist/types/components/IDE/CodeBlockClient.d.ts +18 -2
- package/dist/types/components/IDE/CodeBlockClient.d.ts.map +1 -1
- package/dist/types/components/IDE/CodeBlockHighlight.d.ts +3 -2
- package/dist/types/components/IDE/CodeBlockHighlight.d.ts.map +1 -1
- package/dist/types/components/IDE/CodeBlockServer.d.ts +0 -1
- package/dist/types/components/IDE/CodeBlockServer.d.ts.map +1 -1
- package/dist/types/components/IDE/CodeBlockShiki.d.ts +0 -1
- package/dist/types/components/IDE/CodeBlockShiki.d.ts.map +1 -1
- package/dist/types/components/IDE/IDE.d.ts +0 -1
- package/dist/types/components/IDE/IDE.d.ts.map +1 -1
- package/dist/types/components/IDE/MarkDownRender.d.ts +0 -1
- package/dist/types/components/IDE/MarkDownRender.d.ts.map +1 -1
- package/dist/types/components/IDE/index.d.ts +2 -2
- package/dist/types/components/IDE/shikiThemes.d.ts +18 -0
- package/dist/types/components/IDE/shikiThemes.d.ts.map +1 -0
- package/dist/types/components/MarkDownRender/MarkDownIframe.d.ts.map +1 -1
- package/dist/types/components/MarkDownRender/MarkDownRender.d.ts +1 -2
- package/dist/types/components/MarkDownRender/MarkDownRender.d.ts.map +1 -1
- package/dist/types/components/Pattern/GridDistortionPattern.d.ts.map +1 -1
- package/dist/types/components/Popover/dynamic.d.ts.map +1 -1
- package/dist/types/components/Popover/index.d.ts +2 -2
- package/dist/types/components/Popover/static.d.ts +6 -1
- package/dist/types/components/Popover/static.d.ts.map +1 -1
- package/dist/types/components/Tab/Tab.d.ts +2 -2
- package/dist/types/components/Tab/Tab.d.ts.map +1 -1
- package/dist/types/components/ThemeSwitcherDropDown/DesktopThemeSwitcher.d.ts +1 -1
- package/dist/types/components/ThemeSwitcherDropDown/DesktopThemeSwitcher.d.ts.map +1 -1
- package/dist/types/components/ThemeSwitcherDropDown/MobileThemeSwitcher.d.ts +1 -1
- package/dist/types/components/ThemeSwitcherDropDown/MobileThemeSwitcher.d.ts.map +1 -1
- package/dist/types/components/ThemeSwitcherDropDown/types.d.ts +3 -1
- package/dist/types/components/ThemeSwitcherDropDown/types.d.ts.map +1 -1
- package/dist/types/components/index.d.ts +3 -3
- package/dist/types/hooks/index.d.ts +2 -2
- package/dist/types/hooks/useItemSelector.d.ts +2 -0
- package/dist/types/hooks/useItemSelector.d.ts.map +1 -1
- package/dist/types/hooks/useScrollY.d.ts +10 -0
- package/dist/types/hooks/useScrollY.d.ts.map +1 -1
- package/dist/types/hooks/useStickToBottom.d.ts +41 -0
- package/dist/types/hooks/useStickToBottom.d.ts.map +1 -0
- package/dist/types/libs/auth.d.ts +3 -0
- package/dist/types/libs/auth.d.ts.map +1 -1
- package/dist/types/providers/ThemeProvider.d.ts +41 -0
- package/dist/types/providers/ThemeProvider.d.ts.map +1 -0
- package/dist/types/providers/index.d.ts +2 -1
- package/dist/types/routes.d.ts +2 -1
- package/dist/types/routes.d.ts.map +1 -1
- package/dist/types/structured-data/buildCreativeWorkJsonLd.d.ts +9 -3
- package/dist/types/structured-data/buildCreativeWorkJsonLd.d.ts.map +1 -1
- package/dist/types/utils/index.d.ts +3 -1
- package/dist/types/utils/observeScrollExtent.d.ts +22 -0
- package/dist/types/utils/observeScrollExtent.d.ts.map +1 -0
- package/dist/types/utils/scheduleFrameTask.d.ts +25 -0
- package/dist/types/utils/scheduleFrameTask.d.ts.map +1 -0
- package/package.json +46 -41
- package/tailwind.css +27 -1
- package/dist/esm/hooks/useIsDarkMode.mjs +0 -17
- package/dist/esm/hooks/useIsDarkMode.mjs.map +0 -1
- package/dist/types/hooks/useIsDarkMode.d.ts +0 -5
- package/dist/types/hooks/useIsDarkMode.d.ts.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CodeBlockShiki.mjs","names":[],"sources":["../../../../src/components/IDE/CodeBlockShiki.tsx"],"sourcesContent":["'use client';\n\nimport { type FC, type ReactNode, useEffect, useState } from 'react';\nimport
|
|
1
|
+
{"version":3,"file":"CodeBlockShiki.mjs","names":[],"sources":["../../../../src/components/IDE/CodeBlockShiki.tsx"],"sourcesContent":["'use client';\n\nimport { type FC, type ReactNode, useEffect, useState } from 'react';\nimport { CodeDefault, shikiWrapperClassName } from './CodeBlockClient';\nimport {\n type CodeLanguage,\n type LanguageGrammar,\n type ResolvedCodeLanguage,\n resolveCodeLanguage,\n} from './shikiLanguages';\nimport { SHIKI_THEMES } from './shikiThemes';\n\n/** Theme registration, as default-exported by the `shiki/themes/*` modules. */\ntype ThemeRegistration =\n typeof import('shiki/themes/github-dark.mjs')['default'];\n\n/** Minimal Shiki highlighter surface used here, with unrestricted language ids. */\ntype CodeHighlighter = {\n getLoadedLanguages(): string[];\n getLoadedThemes(): string[];\n loadLanguage(grammar: LanguageGrammar): Promise<void>;\n loadTheme(theme: ThemeRegistration): Promise<void>;\n codeToHtml(\n code: string,\n options: {\n lang: string;\n themes: { light: string; dark: string };\n defaultColor: false;\n }\n ): string;\n};\n\n// Map of in-flight/loaded modules to avoid re-importing\nconst languageCache = new Map<string, Promise<LanguageGrammar>>();\nconst themeCache = new Map<string, Promise<ThemeRegistration>>();\n\n/**\n * Lazy load the grammar of an already resolved language.\n *\n * @returns The grammar registrations, or `null` for languages Shiki renders\n * without a grammar (plain text).\n */\nconst loadLanguage = async ({\n id,\n loadGrammar,\n}: ResolvedCodeLanguage): Promise<LanguageGrammar | null> => {\n if (!loadGrammar) return null;\n\n const cachedLanguage = languageCache.get(id);\n if (cachedLanguage) return cachedLanguage;\n\n const languagePromise = loadGrammar().then((module) => module.default);\n languageCache.set(id, languagePromise);\n\n return languagePromise;\n};\n\n/**\n * Lazy load a theme module.\n */\nconst loadTheme = async (themeName: string): Promise<ThemeRegistration> => {\n const cachedTheme = themeCache.get(themeName);\n if (cachedTheme) return cachedTheme;\n\n const themePromise = (\n themeName === SHIKI_THEMES.dark\n ? import('shiki/themes/github-dark.mjs')\n : import('shiki/themes/github-light.mjs')\n ).then((module) => module.default);\n\n themeCache.set(themeName, themePromise);\n\n return themePromise;\n};\n\n// Singleton Highlighter Instance\nlet highlighterPromise: Promise<CodeHighlighter> | null = null;\n\nconst getHighlighterInstance = async () => {\n if (!highlighterPromise) {\n highlighterPromise = import('shiki/bundle/web').then(\n ({ createHighlighter }) =>\n createHighlighter({\n langs: [],\n themes: [],\n })\n );\n }\n return highlighterPromise;\n};\n\n/**\n * Highlight a snippet, lazily loading only the grammar and themes it needs.\n *\n * The language is resolved to a canonical Shiki id first, so that the id passed\n * to `codeToHtml` always matches the grammar that was loaded.\n */\nconst highlightCode = async (\n code: ReactNode,\n lang: CodeLanguage\n): Promise<string> => {\n const resolvedLanguage = resolveCodeLanguage(lang);\n\n // Load highlighter, language, and themes in parallel\n const [highlighter, languageModule, lightTheme, darkTheme] =\n await Promise.all([\n getHighlighterInstance(),\n loadLanguage(resolvedLanguage),\n loadTheme(SHIKI_THEMES.light),\n loadTheme(SHIKI_THEMES.dark),\n ]);\n\n // Load into the singleton instance if not already loaded\n if (\n languageModule &&\n !highlighter.getLoadedLanguages().includes(resolvedLanguage.id)\n ) {\n await highlighter.loadLanguage(languageModule);\n }\n\n const loadedThemes = highlighter.getLoadedThemes();\n if (!loadedThemes.includes(SHIKI_THEMES.light)) {\n await highlighter.loadTheme(lightTheme);\n }\n if (!loadedThemes.includes(SHIKI_THEMES.dark)) {\n await highlighter.loadTheme(darkTheme);\n }\n\n return highlighter.codeToHtml(String(code), {\n lang: resolvedLanguage.id,\n themes: SHIKI_THEMES,\n defaultColor: false,\n });\n};\n\nexport type CodeBlockShikiProps = {\n children: ReactNode;\n lang: CodeLanguage;\n};\n\nexport const CodeBlockShiki: FC<CodeBlockShikiProps> = ({ children, lang }) => {\n const [html, setHtml] = useState<string | null>(null);\n\n useEffect(() => {\n let isCancelled = false;\n\n highlightCode(children, lang)\n .then((result) => {\n if (!isCancelled) setHtml(result);\n })\n .catch((error) => {\n console.error('Failed to highlight code:', error);\n if (!isCancelled) setHtml('');\n });\n\n return () => {\n isCancelled = true;\n };\n }, [children, lang]);\n\n return (\n <div className={shikiWrapperClassName}>\n {html ? (\n // biome-ignore lint/security/noDangerouslySetInnerHtml: Shiki generates safe HTML for code highlighting\n <div dangerouslySetInnerHTML={{ __html: html }} />\n ) : (\n <CodeDefault>{children}</CodeDefault>\n )}\n </div>\n );\n};\n"],"mappings":";;;;;;;;;AAiCA,MAAM,gCAAgB,IAAI,IAAsC;AAChE,MAAM,6BAAa,IAAI,IAAwC;;;;;;;AAQ/D,MAAM,eAAe,OAAO,EAC1B,IACA,kBAC2D;CAC3D,IAAI,CAAC,aAAa,OAAO;CAEzB,MAAM,iBAAiB,cAAc,IAAI,EAAE;CAC3C,IAAI,gBAAgB,OAAO;CAE3B,MAAM,kBAAkB,YAAY,CAAC,CAAC,MAAM,WAAW,OAAO,OAAO;CACrE,cAAc,IAAI,IAAI,eAAe;CAErC,OAAO;AACT;;;;AAKA,MAAM,YAAY,OAAO,cAAkD;CACzE,MAAM,cAAc,WAAW,IAAI,SAAS;CAC5C,IAAI,aAAa,OAAO;CAExB,MAAM,gBACJ,cAAc,aAAa,OACvB,OAAO,kCACP,OAAO,iCAAgC,CAC3C,MAAM,WAAW,OAAO,OAAO;CAEjC,WAAW,IAAI,WAAW,YAAY;CAEtC,OAAO;AACT;AAGA,IAAI,qBAAsD;AAE1D,MAAM,yBAAyB,YAAY;CACzC,IAAI,CAAC,oBACH,qBAAqB,OAAO,mBAAmB,CAAC,MAC7C,EAAE,wBACD,kBAAkB;EAChB,OAAO,CAAC;EACR,QAAQ,CAAC;CACX,CAAC,CACL;CAEF,OAAO;AACT;;;;;;;AAQA,MAAM,gBAAgB,OACpB,MACA,SACoB;CACpB,MAAM,mBAAmB,oBAAoB,IAAI;CAGjD,MAAM,CAAC,aAAa,gBAAgB,YAAY,aAC9C,MAAM,QAAQ,IAAI;EAChB,uBAAuB;EACvB,aAAa,gBAAgB;EAC7B,UAAU,aAAa,KAAK;EAC5B,UAAU,aAAa,IAAI;CAC7B,CAAC;CAGH,IACE,kBACA,CAAC,YAAY,mBAAmB,CAAC,CAAC,SAAS,iBAAiB,EAAE,GAE9D,MAAM,YAAY,aAAa,cAAc;CAG/C,MAAM,eAAe,YAAY,gBAAgB;CACjD,IAAI,CAAC,aAAa,SAAS,aAAa,KAAK,GAC3C,MAAM,YAAY,UAAU,UAAU;CAExC,IAAI,CAAC,aAAa,SAAS,aAAa,IAAI,GAC1C,MAAM,YAAY,UAAU,SAAS;CAGvC,OAAO,YAAY,WAAW,OAAO,IAAI,GAAG;EAC1C,MAAM,iBAAiB;EACvB,QAAQ;EACR,cAAc;CAChB,CAAC;AACH;AAOA,MAAa,kBAA2C,EAAE,UAAU,WAAW;CAC7E,MAAM,CAAC,MAAM,WAAW,SAAwB,IAAI;CAEpD,gBAAgB;EACd,IAAI,cAAc;EAElB,cAAc,UAAU,IAAI,CAAC,CAC1B,MAAM,WAAW;GAChB,IAAI,CAAC,aAAa,QAAQ,MAAM;EAClC,CAAC,CAAC,CACD,OAAO,UAAU;GAChB,QAAQ,MAAM,6BAA6B,KAAK;GAChD,IAAI,CAAC,aAAa,QAAQ,EAAE;EAC9B,CAAC;EAEH,aAAa;GACX,cAAc;EAChB;CACF,GAAG,CAAC,UAAU,IAAI,CAAC;CAEnB,OACE,oBAAC,OAAD;EAAK,WAAW;YACb,OAEC,oBAAC,OAAD,EAAK,yBAAyB,EAAE,QAAQ,KAAK,EAAI,KAEjD,oBAAC,aAAD,EAAc,SAAsB;CAEnC;AAET"}
|
|
@@ -9,7 +9,7 @@ import { useEffect, useState } from "react";
|
|
|
9
9
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
10
10
|
|
|
11
11
|
//#region src/components/IDE/IDE.tsx
|
|
12
|
-
const IDE = ({ pages: initialPages,
|
|
12
|
+
const IDE = ({ pages: initialPages, className, activeTab: defaultActiveTab, ...props }) => {
|
|
13
13
|
const [pages, setPages] = useState(initialPages);
|
|
14
14
|
const tabs = pages.filter(({ isOpen }) => isOpen);
|
|
15
15
|
const firstTabIndex = tabs.findIndex(({ isOpen }) => isOpen);
|
|
@@ -76,10 +76,7 @@ const IDE = ({ pages: initialPages, isDarkMode, className, activeTab: defaultAct
|
|
|
76
76
|
})
|
|
77
77
|
}), /* @__PURE__ */ jsx("div", {
|
|
78
78
|
className: "size-full flex-1 overflow-auto pt-2 text-xs",
|
|
79
|
-
children: /* @__PURE__ */ jsx(MarkdownRenderer, {
|
|
80
|
-
isDarkMode,
|
|
81
|
-
children: content
|
|
82
|
-
})
|
|
79
|
+
children: /* @__PURE__ */ jsx(MarkdownRenderer, { children: content })
|
|
83
80
|
})]
|
|
84
81
|
})
|
|
85
82
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IDE.mjs","names":[],"sources":["../../../../src/components/IDE/IDE.tsx"],"sourcesContent":["'use client';\n\nimport { cn } from '@utils/cn';\nimport { type FC, type HTMLAttributes, useEffect, useState } from 'react';\nimport { Container } from '../Container';\nimport { WithResizer } from '../WithResizer';\nimport { FileTree } from './FileTree';\nimport { MarkdownRenderer } from './MarkDownRender';\n\nexport type IDEProps = {\n pages: {\n path: string;\n content: string;\n isOpen?: boolean;\n }[];\n
|
|
1
|
+
{"version":3,"file":"IDE.mjs","names":[],"sources":["../../../../src/components/IDE/IDE.tsx"],"sourcesContent":["'use client';\n\nimport { cn } from '@utils/cn';\nimport { type FC, type HTMLAttributes, useEffect, useState } from 'react';\nimport { Container } from '../Container';\nimport { WithResizer } from '../WithResizer';\nimport { FileTree } from './FileTree';\nimport { MarkdownRenderer } from './MarkDownRender';\n\nexport type IDEProps = {\n pages: {\n path: string;\n content: string;\n isOpen?: boolean;\n }[];\n activeTab?: number;\n} & HTMLAttributes<HTMLDivElement>;\n\nexport const IDE: FC<IDEProps> = ({\n pages: initialPages,\n className,\n activeTab: defaultActiveTab,\n ...props\n}) => {\n const [pages, setPages] = useState(initialPages);\n const tabs = pages.filter(({ isOpen }) => isOpen);\n\n const firstTabIndex = tabs.findIndex(({ isOpen }) => isOpen);\n const [activeTab, setActiveTab] = useState(defaultActiveTab ?? firstTabIndex);\n\n useEffect(() => {\n setActiveTab(defaultActiveTab ?? firstTabIndex);\n }, [initialPages, defaultActiveTab]);\n\n const { content, path } = pages[activeTab] ?? {};\n const filePaths = initialPages.map(({ path: title }) => title);\n\n const handleClickFile = (title: string) => {\n const page = pages.find(({ path: tabTitle }) => tabTitle === title);\n if (!page) return;\n\n const newPages = pages.map((page) => {\n if (page.path === title) {\n return { ...page, isOpen: true };\n }\n return page;\n });\n\n setPages(newPages);\n\n const newPageIndex = newPages.findIndex(\n ({ path: tabTitle }) => tabTitle === title\n );\n\n setActiveTab(newPageIndex);\n };\n\n return (\n <Container\n className={cn(\n 'flex size-full flex-col justify-start overflow-hidden shadow-lg',\n className\n )}\n roundedSize=\"3xl\"\n transparency=\"none\"\n {...props}\n >\n <div className=\"flex w-auto flex-row items-center justify-start gap-1 bg-neutral-200 text-neutral text-xs dark:bg-neutral-950\">\n <div className=\"mx-2 flex items-center justify-start gap-2 p-1\">\n <div className=\"size-3 rounded-full bg-error\" />\n <div className=\"size-3 rounded-full bg-yellow-500\" />\n <div className=\"size-3 rounded-full bg-green-500\" />\n </div>\n <div className=\"flex size-full overflow-y-auto\">\n {tabs.map(({ path }, index) => {\n const fullPath = path.split('/');\n const title = fullPath[fullPath.length - 1];\n const isActive = index === activeTab;\n\n return (\n <button\n className={cn(\n 'flex h-8 min-w-20 max-w-30 items-center justify-start truncate text-nowrap px-3 py-1 transition',\n isActive\n ? 'bg-card'\n : 'cursor-pointer bg-neutral-200 hover:bg-neutral-300 dark:bg-neutral-950'\n )}\n key={title}\n onClick={() => setActiveTab(index)}\n >\n {title}\n </button>\n );\n })}\n </div>\n </div>\n <div className=\"relative flex size-full flex-1 flex-row justify-start\">\n <div className=\"absolute top-0 left-0 size-full\">\n <div className=\"flex size-full\">\n <WithResizer initialWidth={150}>\n <div className=\"max-h-full flex-1 overflow-y-auto\">\n <FileTree\n filesPaths={filePaths}\n activeFile={path}\n onClickFile={handleClickFile}\n />\n </div>\n </WithResizer>\n\n <div className=\"size-full flex-1 overflow-auto pt-2 text-xs\">\n <MarkdownRenderer>{content}</MarkdownRenderer>\n </div>\n </div>\n </div>\n </div>\n </Container>\n );\n};\n"],"mappings":";;;;;;;;;;;AAkBA,MAAa,OAAqB,EAChC,OAAO,cACP,WACA,WAAW,kBACX,GAAG,YACC;CACJ,MAAM,CAAC,OAAO,YAAY,SAAS,YAAY;CAC/C,MAAM,OAAO,MAAM,QAAQ,EAAE,aAAa,MAAM;CAEhD,MAAM,gBAAgB,KAAK,WAAW,EAAE,aAAa,MAAM;CAC3D,MAAM,CAAC,WAAW,gBAAgB,SAAS,oBAAoB,aAAa;CAE5E,gBAAgB;EACd,aAAa,oBAAoB,aAAa;CAChD,GAAG,CAAC,cAAc,gBAAgB,CAAC;CAEnC,MAAM,EAAE,SAAS,SAAS,MAAM,cAAc,CAAC;CAC/C,MAAM,YAAY,aAAa,KAAK,EAAE,MAAM,YAAY,KAAK;CAE7D,MAAM,mBAAmB,UAAkB;EAEzC,IAAI,CADS,MAAM,MAAM,EAAE,MAAM,eAAe,aAAa,KACrD,GAAG;EAEX,MAAM,WAAW,MAAM,KAAK,SAAS;GACnC,IAAI,KAAK,SAAS,OAChB,OAAO;IAAE,GAAG;IAAM,QAAQ;GAAK;GAEjC,OAAO;EACT,CAAC;EAED,SAAS,QAAQ;EAEjB,MAAM,eAAe,SAAS,WAC3B,EAAE,MAAM,eAAe,aAAa,KACvC;EAEA,aAAa,YAAY;CAC3B;CAEA,OACE,qBAAC,WAAD;EACE,WAAW,GACT,mEACA,SACF;EACA,aAAY;EACZ,cAAa;EACb,GAAI;YAPN,CASE,qBAAC,OAAD;GAAK,WAAU;aAAf,CACE,qBAAC,OAAD;IAAK,WAAU;cAAf;KACE,oBAAC,OAAD,EAAK,WAAU,+BAAgC;KAC/C,oBAAC,OAAD,EAAK,WAAU,oCAAqC;KACpD,oBAAC,OAAD,EAAK,WAAU,mCAAoC;IAChD;OACL,oBAAC,OAAD;IAAK,WAAU;cACZ,KAAK,KAAK,EAAE,QAAQ,UAAU;KAC7B,MAAM,WAAW,KAAK,MAAM,GAAG;KAC/B,MAAM,QAAQ,SAAS,SAAS,SAAS;KAGzC,OACE,oBAAC,UAAD;MACE,WAAW,GACT,mGALW,UAAU,YAOjB,YACA,wEACN;MAEA,eAAe,aAAa,KAAK;gBAEhC;KACK,GAJD,KAIC;IAEZ,CAAC;GACE,EACF;MACL,oBAAC,OAAD;GAAK,WAAU;aACb,oBAAC,OAAD;IAAK,WAAU;cACb,qBAAC,OAAD;KAAK,WAAU;eAAf,CACE,oBAAC,aAAD;MAAa,cAAc;gBACzB,oBAAC,OAAD;OAAK,WAAU;iBACb,oBAAC,UAAD;QACE,YAAY;QACZ,YAAY;QACZ,aAAa;OACd;MACE;KACM,IAEb,oBAAC,OAAD;MAAK,WAAU;gBACb,oBAAC,kBAAD,YAAmB,QAA0B;KAC1C,EACF;;GACF;EACF,EACI;;AAEf"}
|
|
@@ -3,11 +3,10 @@ import { jsx } from "react/jsx-runtime";
|
|
|
3
3
|
import { MarkdownRenderer as MarkdownRenderer$1 } from "react-intlayer";
|
|
4
4
|
|
|
5
5
|
//#region src/components/IDE/MarkDownRender.tsx
|
|
6
|
-
const MarkdownRenderer = ({ children
|
|
6
|
+
const MarkdownRenderer = ({ children }) => /* @__PURE__ */ jsx(MarkdownRenderer$1, {
|
|
7
7
|
components: {
|
|
8
8
|
code: (props) => /* @__PURE__ */ jsx(Code, {
|
|
9
9
|
...props,
|
|
10
|
-
isDarkMode,
|
|
11
10
|
language: props.className?.replace("lang-", ""),
|
|
12
11
|
showHeader: false,
|
|
13
12
|
className: "text-xs leading-5",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MarkDownRender.mjs","names":["MarkdownRendererIntlayer"],"sources":["../../../../src/components/IDE/MarkDownRender.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { MarkdownRenderer as MarkdownRendererIntlayer } from 'react-intlayer';\nimport { Code } from './Code';\nimport type { CodeLanguage } from './shikiLanguages';\n\ntype MarkdownRendererProps = {\n
|
|
1
|
+
{"version":3,"file":"MarkDownRender.mjs","names":["MarkdownRendererIntlayer"],"sources":["../../../../src/components/IDE/MarkDownRender.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { MarkdownRenderer as MarkdownRendererIntlayer } from 'react-intlayer';\nimport { Code } from './Code';\nimport type { CodeLanguage } from './shikiLanguages';\n\ntype MarkdownRendererProps = {\n children: string;\n};\n\nexport const MarkdownRenderer: FC<MarkdownRendererProps> = ({ children }) => (\n <MarkdownRendererIntlayer\n components={{\n code: (props) => (\n <Code\n {...props}\n language={props.className?.replace('lang-', '') as CodeLanguage}\n showHeader={false}\n className=\"text-xs leading-5\"\n >\n {props.children as string}\n </Code>\n ),\n pre: (props) => props.children,\n }}\n >\n {children ?? ''}\n </MarkdownRendererIntlayer>\n);\n"],"mappings":";;;;;AASA,MAAa,oBAA+C,EAAE,eAC5D,oBAACA,oBAAD;CACE,YAAY;EACV,OAAO,UACL,oBAAC,MAAD;GACE,GAAI;GACJ,UAAU,MAAM,WAAW,QAAQ,SAAS,EAAE;GAC9C,YAAY;GACZ,WAAU;aAET,MAAM;EACH;EAER,MAAM,UAAU,MAAM;CACxB;WAEC,YAAY;AACW"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { CodeBlock, CodeDefault } from "./CodeBlockClient.mjs";
|
|
1
|
+
import { CodeBlock, CodeDefault, CodeHighlighted, shikiWrapperClassName } from "./CodeBlockClient.mjs";
|
|
2
2
|
import { Code } from "./Code.mjs";
|
|
3
3
|
import { FileList } from "./FileList.mjs";
|
|
4
4
|
import { IDE } from "./IDE.mjs";
|
|
5
5
|
|
|
6
|
-
export { Code, CodeBlock, CodeDefault, FileList, IDE };
|
|
6
|
+
export { Code, CodeBlock, CodeDefault, CodeHighlighted, FileList, IDE, shikiWrapperClassName };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//#region src/components/IDE/shikiThemes.ts
|
|
2
|
+
/**
|
|
3
|
+
* Themes every code block is highlighted with.
|
|
4
|
+
*
|
|
5
|
+
* Shiki is always asked for both, with `defaultColor: false`, so each token
|
|
6
|
+
* carries its light and dark colour as the `--shiki-light` / `--shiki-dark`
|
|
7
|
+
* custom properties rather than a resolved `color`. The markup is therefore
|
|
8
|
+
* theme-agnostic: it can be produced once — at build time — and never has to be
|
|
9
|
+
* recomputed in the browser when the resolved theme changes.
|
|
10
|
+
*/
|
|
11
|
+
const SHIKI_THEMES = {
|
|
12
|
+
light: "github-light",
|
|
13
|
+
dark: "github-dark"
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
//#endregion
|
|
17
|
+
export { SHIKI_THEMES };
|
|
18
|
+
//# sourceMappingURL=shikiThemes.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shikiThemes.mjs","names":[],"sources":["../../../../src/components/IDE/shikiThemes.ts"],"sourcesContent":["/**\n * Themes every code block is highlighted with.\n *\n * Shiki is always asked for both, with `defaultColor: false`, so each token\n * carries its light and dark colour as the `--shiki-light` / `--shiki-dark`\n * custom properties rather than a resolved `color`. The markup is therefore\n * theme-agnostic: it can be produced once — at build time — and never has to be\n * recomputed in the browser when the resolved theme changes.\n */\nexport const SHIKI_THEMES = {\n light: 'github-light',\n dark: 'github-dark',\n} as const;\n\nexport type ShikiThemes = typeof SHIKI_THEMES;\n"],"mappings":";;;;;;;;;;AASA,MAAa,eAAe;CAC1B,OAAO;CACP,MAAM;AACR"}
|
|
@@ -155,17 +155,23 @@ const isTextChildren = (children) => {
|
|
|
155
155
|
return false;
|
|
156
156
|
};
|
|
157
157
|
const Link = (props) => {
|
|
158
|
-
const { variant = "default", color = "custom", roundedSize, children, label, className, isActive, underlined, locale, size: sizeProp, isExternalLink: isExternalLinkProp, isPageSection: isPageSectionProp, href: hrefProp, ...otherProps } = props;
|
|
158
|
+
const { variant = "default", color = "custom", roundedSize, children, label, className, isActive, underlined, locale, size: sizeProp, isExternalLink: isExternalLinkProp, isPageSection: isPageSectionProp, href: hrefProp, rel: relProp, target: targetProp, ...otherProps } = props;
|
|
159
159
|
const isButton = variant === "button" || variant === "button-outlined";
|
|
160
160
|
const size = sizeProp ?? (isButton ? "md" : "custom");
|
|
161
161
|
const isExternalLink = isExternalLinkProp ?? checkIsExternalLink(props);
|
|
162
162
|
const isPageSection = isPageSectionProp ?? hrefProp?.startsWith("#") ?? false;
|
|
163
163
|
const isChildrenString = isTextChildren(children);
|
|
164
|
-
|
|
165
|
-
|
|
164
|
+
/**
|
|
165
|
+
* External links always carry the full safety/SEO `rel`, so a caller cannot
|
|
166
|
+
* accidentally strip `nofollow`. Internal links keep whatever the caller asked for.
|
|
167
|
+
*/
|
|
168
|
+
const rel = isExternalLink ? "noopener noreferrer nofollow" : relProp;
|
|
169
|
+
const target = isExternalLink ? "_blank" : targetProp ?? "_self";
|
|
166
170
|
const resolvedHref = locale && hrefProp && !isExternalLink && !isPageSection ? getLocalizedUrl(hrefProp, locale) : hrefProp;
|
|
171
|
+
const href = resolvedHref === "" ? void 0 : resolvedHref;
|
|
167
172
|
return /* @__PURE__ */ jsxs("a", {
|
|
168
|
-
|
|
173
|
+
...otherProps,
|
|
174
|
+
href,
|
|
169
175
|
"aria-label": label,
|
|
170
176
|
rel,
|
|
171
177
|
target,
|
|
@@ -179,7 +185,6 @@ const Link = (props) => {
|
|
|
179
185
|
size,
|
|
180
186
|
className
|
|
181
187
|
})),
|
|
182
|
-
...otherProps,
|
|
183
188
|
children: [
|
|
184
189
|
isButton && isChildrenString ? /* @__PURE__ */ jsx("span", { children }) : children,
|
|
185
190
|
isExternalLink && isChildrenString && /* @__PURE__ */ jsx(ExternalLink, { className: "ml-2 inline-block size-4" }),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Link.mjs","names":[],"sources":["../../../../src/components/Link/Link.tsx"],"sourcesContent":["import { getLocalizedUrl } from '@intlayer/core/localization';\nimport type { LocalesValues } from '@intlayer/types/module_augmentation';\nimport { cn } from '@utils/cn';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { ExternalLink, MoveRight } from 'lucide-react';\nimport {\n type AnchorHTMLAttributes,\n type DetailedHTMLProps,\n type FC,\n isValidElement,\n type ReactNode,\n} from 'react';\n\nexport type LinkVariant =\n | 'default'\n | 'invisible-link'\n | 'button'\n | 'button-outlined'\n | 'hoverable';\n\n/**\n * Color theme variants for Link component\n */\nexport type LinkColor =\n | 'primary'\n | 'secondary'\n | 'neutral'\n | 'light'\n | 'dark'\n | 'text'\n | 'text-inverse'\n | 'error'\n | 'success'\n | 'custom';\n\nexport type LinkRoundedSize =\n | 'none'\n | 'sm'\n | 'md'\n | 'lg'\n | 'xl'\n | '2xl'\n | '3xl'\n | 'full';\n\nexport type LinkSize = 'sm' | 'md' | 'lg' | 'xl' | 'custom';\n\nexport type LinkUnderlined = 'default' | 'true' | 'false';\n\nexport const linkVariants = cva(\n 'gap-3 transition-all duration-300 focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50',\n {\n variants: {\n variant: {\n default:\n 'h-auto justify-start border-inherit bg-current/0 px-1 font-medium decoration-[1.5] underline-offset-5 hover:bg-current/0 hover:text-current/80 hover:underline hover:underline-offset-6',\n 'invisible-link':\n 'h-auto justify-start border-inherit bg-current/0 px-1',\n\n button:\n 'relative inline-flex min-h-8 cursor-pointer flex-row items-center justify-center gap-2 rounded-full bg-current px-6 text-center font-medium text-sm text-text ring-0 *:text-text-opposite hover:bg-current/90 hover:ring-5 aria-selected:ring-5 aria-[current]:ring-5 max-md:py-2',\n\n 'button-outlined':\n 'relative inline-flex min-h-8 cursor-pointer flex-row items-center justify-center gap-2 rounded-full border-[1.3px] border-current px-6 text-center font-medium text-sm text-text ring-0 *:text-text hover:bg-current/20 hover:ring-5 aria-selected:ring-5 aria-[current]:ring-5 max-md:py-2',\n\n hoverable:\n 'rounded-lg border-none bg-current/0 transition *:text-current! hover:bg-current/10 aria-[current]:bg-current/5',\n },\n roundedSize: {\n none: 'rounded-none',\n sm: 'rounded-lg [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-xl',\n md: 'rounded-xl [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-2xl',\n lg: 'rounded-2xl [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-3xl',\n xl: 'rounded-3xl [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-4xl',\n '2xl':\n 'rounded-4xl [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-[2.5rem]',\n '3xl':\n 'rounded-[2.5rem] [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-[3rem]',\n full: 'rounded-full',\n },\n color: {\n primary: 'text-primary',\n secondary: 'text-secondary',\n neutral: 'text-neutral',\n light: 'text-white',\n dark: 'text-neutral-800',\n text: 'text-text',\n 'text-inverse': 'text-text-opposite',\n error: 'text-error',\n success: 'text-success',\n custom: '',\n },\n size: {\n sm: 'text-sm',\n md: 'text-base',\n lg: 'text-lg',\n xl: 'text-xl',\n custom: '',\n },\n underlined: {\n default: '',\n true: 'underline',\n false: 'no-underline',\n },\n },\n // Compound variants handle height and padding\n compoundVariants: [\n // ---------------------------------------------------------\n // FIX START: Correctly Handle Contrast for TEXT_INVERSE\n // ---------------------------------------------------------\n {\n // Filled Button + Inverse Color (e.g., White Button):\n // We DO NOT override parent text color (it must remain 'text-opposite' so bg-current is white).\n // We ONLY override children to be 'text-text' (Dark) so they show up on white.\n variant: 'button',\n color: 'text-inverse',\n class: '*:text-text',\n },\n {\n // Outlined Button + Inverse Color (e.g., White Border):\n // Parent is 'text-opposite' (Border is white).\n // Children must also be 'text-opposite' (White text) to show on dark background.\n variant: 'button-outlined',\n color: 'text-inverse',\n class: 'text-text-opposite *:text-text-opposite',\n },\n\n // Min height and padding for button variants\n {\n variant: ['button', 'button-outlined'],\n size: 'sm',\n class: 'min-h-7 px-3 text-xs max-md:py-1',\n },\n {\n variant: ['button', 'button-outlined'],\n size: 'md',\n class: 'min-h-8 px-6 text-sm max-md:py-2',\n },\n {\n variant: ['button', 'button-outlined'],\n size: 'lg',\n class: 'min-h-10 px-8 text-lg max-md:py-3',\n },\n {\n variant: ['button', 'button-outlined'],\n size: 'xl',\n class: 'min-h-11 px-10 text-xl max-md:py-4',\n },\n // Ring color variants\n {\n variant: ['button', 'button-outlined'],\n color: 'primary',\n class: 'ring-primary/20',\n },\n {\n variant: ['button', 'button-outlined'],\n color: 'secondary',\n class: 'ring-secondary/20',\n },\n {\n variant: ['button', 'button-outlined'],\n color: 'neutral',\n class: 'ring-neutral/20',\n },\n {\n variant: ['button', 'button-outlined'],\n color: 'light',\n class: 'ring-white/20',\n },\n {\n variant: ['button', 'button-outlined'],\n color: 'dark',\n class: 'ring-neutral-800/20',\n },\n {\n variant: ['button', 'button-outlined'],\n color: 'text',\n class: 'ring-text/20',\n },\n {\n variant: ['button', 'button-outlined'],\n color: 'text-inverse',\n class: 'ring-text-opposite/20',\n },\n {\n variant: ['button', 'button-outlined'],\n color: 'error',\n class: 'ring-error/20',\n },\n {\n variant: ['button', 'button-outlined'],\n color: 'success',\n class: 'ring-success/20',\n },\n ],\n\n defaultVariants: {\n variant: 'default',\n roundedSize: 'md',\n underlined: 'default',\n size: 'custom',\n },\n }\n);\n\nexport type LinkProps = DetailedHTMLProps<\n AnchorHTMLAttributes<HTMLAnchorElement>,\n HTMLAnchorElement\n> &\n VariantProps<typeof linkVariants> & {\n label: string;\n isExternalLink?: boolean;\n isPageSection?: boolean;\n isActive?: boolean;\n locale?: LocalesValues;\n };\n\nexport const checkIsExternalLink = (\n {\n href,\n isExternalLink: isExternalLinkProp,\n }: Pick<LinkProps, 'href' | 'isExternalLink'>,\n url?: string\n): boolean => {\n // Explicit prop override takes precedence\n if (typeof isExternalLinkProp === 'boolean') {\n return isExternalLinkProp;\n }\n\n const isValidHref = typeof href === 'string' && href.trim() !== '';\n\n if (!isValidHref) return false;\n\n // Relative URLs (e.g., '/about') are always internal\n if (!/^https?:\\/\\//.test(href)) {\n return false;\n }\n\n // Compare base domains\n if (url) {\n try {\n const hrefHost = new URL(href).hostname;\n // Ensure the reference url has a protocol so URL() can parse it correctly\n const currentHost = new URL(\n url.startsWith('http') ? url : `https://${url}`\n ).hostname;\n\n // Extract the root domain (e.g., 'app.intlayer.org' -> 'intlayer.org')\n const getBaseDomain = (host: string) =>\n host.split('.').slice(-2).join('.');\n\n return getBaseDomain(hrefHost) !== getBaseDomain(currentHost);\n } catch {\n // If URL parsing fails for any reason, default to treating it as external\n return true;\n }\n }\n\n // Absolute URL with no comparison URL provided\n return true;\n};\n\nexport const isTextChildren = (children: ReactNode): boolean => {\n if (typeof children === 'string' || typeof children === 'number') {\n return true;\n }\n if (Array.isArray(children)) {\n return children.every(isTextChildren);\n }\n if (isValidElement(children)) {\n return isTextChildren(\n (children.props as { children?: ReactNode }).children\n );\n }\n return false;\n};\n\nexport const Link: FC<LinkProps> = (props) => {\n const {\n variant = 'default',\n color = 'custom',\n roundedSize,\n children,\n label,\n className,\n isActive,\n underlined,\n locale,\n size: sizeProp,\n isExternalLink: isExternalLinkProp,\n isPageSection: isPageSectionProp,\n href: hrefProp,\n ...otherProps\n } = props;\n\n const isButton = variant === 'button' || variant === 'button-outlined';\n const size = sizeProp ?? (isButton ? 'md' : 'custom');\n\n const isExternalLink = isExternalLinkProp ?? checkIsExternalLink(props);\n const isPageSection = isPageSectionProp ?? hrefProp?.startsWith('#') ?? false;\n\n const isChildrenString = isTextChildren(children);\n\n const rel = isExternalLink ? 'noopener noreferrer nofollow' : undefined;\n\n const target = isExternalLink ? '_blank' : '_self';\n\n const resolvedHref =\n locale && hrefProp && !isExternalLink && !isPageSection\n ? getLocalizedUrl(hrefProp, locale)\n : hrefProp;\n\n const href = resolvedHref === '' ? undefined : resolvedHref;\n\n return (\n <a\n href={href}\n aria-label={label}\n rel={rel}\n target={target}\n aria-current={isActive ? 'page' : undefined}\n suppressHydrationWarning\n className={cn(\n linkVariants({\n variant,\n color,\n roundedSize,\n underlined,\n size,\n className,\n })\n )}\n {...otherProps}\n >\n {isButton && isChildrenString ? <span>{children}</span> : children}\n\n {isExternalLink && isChildrenString && (\n <ExternalLink className=\"ml-2 inline-block size-4\" />\n )}\n {isPageSection && <MoveRight className=\"ml-2 inline-block size-4\" />}\n </a>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Link.mjs","names":[],"sources":["../../../../src/components/Link/Link.tsx"],"sourcesContent":["import { getLocalizedUrl } from '@intlayer/core/localization';\nimport type { LocalesValues } from '@intlayer/types/module_augmentation';\nimport { cn } from '@utils/cn';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { ExternalLink, MoveRight } from 'lucide-react';\nimport {\n type AnchorHTMLAttributes,\n type DetailedHTMLProps,\n type FC,\n isValidElement,\n type ReactNode,\n} from 'react';\n\nexport type LinkVariant =\n | 'default'\n | 'invisible-link'\n | 'button'\n | 'button-outlined'\n | 'hoverable';\n\n/**\n * Color theme variants for Link component\n */\nexport type LinkColor =\n | 'primary'\n | 'secondary'\n | 'neutral'\n | 'light'\n | 'dark'\n | 'text'\n | 'text-inverse'\n | 'error'\n | 'success'\n | 'custom';\n\nexport type LinkRoundedSize =\n | 'none'\n | 'sm'\n | 'md'\n | 'lg'\n | 'xl'\n | '2xl'\n | '3xl'\n | 'full';\n\nexport type LinkSize = 'sm' | 'md' | 'lg' | 'xl' | 'custom';\n\nexport type LinkUnderlined = 'default' | 'true' | 'false';\n\nexport const linkVariants = cva(\n 'gap-3 transition-all duration-300 focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50',\n {\n variants: {\n variant: {\n default:\n 'h-auto justify-start border-inherit bg-current/0 px-1 font-medium decoration-[1.5] underline-offset-5 hover:bg-current/0 hover:text-current/80 hover:underline hover:underline-offset-6',\n 'invisible-link':\n 'h-auto justify-start border-inherit bg-current/0 px-1',\n\n button:\n 'relative inline-flex min-h-8 cursor-pointer flex-row items-center justify-center gap-2 rounded-full bg-current px-6 text-center font-medium text-sm text-text ring-0 *:text-text-opposite hover:bg-current/90 hover:ring-5 aria-selected:ring-5 aria-[current]:ring-5 max-md:py-2',\n\n 'button-outlined':\n 'relative inline-flex min-h-8 cursor-pointer flex-row items-center justify-center gap-2 rounded-full border-[1.3px] border-current px-6 text-center font-medium text-sm text-text ring-0 *:text-text hover:bg-current/20 hover:ring-5 aria-selected:ring-5 aria-[current]:ring-5 max-md:py-2',\n\n hoverable:\n 'rounded-lg border-none bg-current/0 transition *:text-current! hover:bg-current/10 aria-[current]:bg-current/5',\n },\n roundedSize: {\n none: 'rounded-none',\n sm: 'rounded-lg [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-xl',\n md: 'rounded-xl [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-2xl',\n lg: 'rounded-2xl [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-3xl',\n xl: 'rounded-3xl [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-4xl',\n '2xl':\n 'rounded-4xl [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-[2.5rem]',\n '3xl':\n 'rounded-[2.5rem] [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-[3rem]',\n full: 'rounded-full',\n },\n color: {\n primary: 'text-primary',\n secondary: 'text-secondary',\n neutral: 'text-neutral',\n light: 'text-white',\n dark: 'text-neutral-800',\n text: 'text-text',\n 'text-inverse': 'text-text-opposite',\n error: 'text-error',\n success: 'text-success',\n custom: '',\n },\n size: {\n sm: 'text-sm',\n md: 'text-base',\n lg: 'text-lg',\n xl: 'text-xl',\n custom: '',\n },\n underlined: {\n default: '',\n true: 'underline',\n false: 'no-underline',\n },\n },\n // Compound variants handle height and padding\n compoundVariants: [\n // ---------------------------------------------------------\n // FIX START: Correctly Handle Contrast for TEXT_INVERSE\n // ---------------------------------------------------------\n {\n // Filled Button + Inverse Color (e.g., White Button):\n // We DO NOT override parent text color (it must remain 'text-opposite' so bg-current is white).\n // We ONLY override children to be 'text-text' (Dark) so they show up on white.\n variant: 'button',\n color: 'text-inverse',\n class: '*:text-text',\n },\n {\n // Outlined Button + Inverse Color (e.g., White Border):\n // Parent is 'text-opposite' (Border is white).\n // Children must also be 'text-opposite' (White text) to show on dark background.\n variant: 'button-outlined',\n color: 'text-inverse',\n class: 'text-text-opposite *:text-text-opposite',\n },\n\n // Min height and padding for button variants\n {\n variant: ['button', 'button-outlined'],\n size: 'sm',\n class: 'min-h-7 px-3 text-xs max-md:py-1',\n },\n {\n variant: ['button', 'button-outlined'],\n size: 'md',\n class: 'min-h-8 px-6 text-sm max-md:py-2',\n },\n {\n variant: ['button', 'button-outlined'],\n size: 'lg',\n class: 'min-h-10 px-8 text-lg max-md:py-3',\n },\n {\n variant: ['button', 'button-outlined'],\n size: 'xl',\n class: 'min-h-11 px-10 text-xl max-md:py-4',\n },\n // Ring color variants\n {\n variant: ['button', 'button-outlined'],\n color: 'primary',\n class: 'ring-primary/20',\n },\n {\n variant: ['button', 'button-outlined'],\n color: 'secondary',\n class: 'ring-secondary/20',\n },\n {\n variant: ['button', 'button-outlined'],\n color: 'neutral',\n class: 'ring-neutral/20',\n },\n {\n variant: ['button', 'button-outlined'],\n color: 'light',\n class: 'ring-white/20',\n },\n {\n variant: ['button', 'button-outlined'],\n color: 'dark',\n class: 'ring-neutral-800/20',\n },\n {\n variant: ['button', 'button-outlined'],\n color: 'text',\n class: 'ring-text/20',\n },\n {\n variant: ['button', 'button-outlined'],\n color: 'text-inverse',\n class: 'ring-text-opposite/20',\n },\n {\n variant: ['button', 'button-outlined'],\n color: 'error',\n class: 'ring-error/20',\n },\n {\n variant: ['button', 'button-outlined'],\n color: 'success',\n class: 'ring-success/20',\n },\n ],\n\n defaultVariants: {\n variant: 'default',\n roundedSize: 'md',\n underlined: 'default',\n size: 'custom',\n },\n }\n);\n\nexport type LinkProps = DetailedHTMLProps<\n AnchorHTMLAttributes<HTMLAnchorElement>,\n HTMLAnchorElement\n> &\n VariantProps<typeof linkVariants> & {\n label: string;\n isExternalLink?: boolean;\n isPageSection?: boolean;\n isActive?: boolean;\n locale?: LocalesValues;\n };\n\nexport const checkIsExternalLink = (\n {\n href,\n isExternalLink: isExternalLinkProp,\n }: Pick<LinkProps, 'href' | 'isExternalLink'>,\n url?: string\n): boolean => {\n // Explicit prop override takes precedence\n if (typeof isExternalLinkProp === 'boolean') {\n return isExternalLinkProp;\n }\n\n const isValidHref = typeof href === 'string' && href.trim() !== '';\n\n if (!isValidHref) return false;\n\n // Relative URLs (e.g., '/about') are always internal\n if (!/^https?:\\/\\//.test(href)) {\n return false;\n }\n\n // Compare base domains\n if (url) {\n try {\n const hrefHost = new URL(href).hostname;\n // Ensure the reference url has a protocol so URL() can parse it correctly\n const currentHost = new URL(\n url.startsWith('http') ? url : `https://${url}`\n ).hostname;\n\n // Extract the root domain (e.g., 'app.intlayer.org' -> 'intlayer.org')\n const getBaseDomain = (host: string) =>\n host.split('.').slice(-2).join('.');\n\n return getBaseDomain(hrefHost) !== getBaseDomain(currentHost);\n } catch {\n // If URL parsing fails for any reason, default to treating it as external\n return true;\n }\n }\n\n // Absolute URL with no comparison URL provided\n return true;\n};\n\nexport const isTextChildren = (children: ReactNode): boolean => {\n if (typeof children === 'string' || typeof children === 'number') {\n return true;\n }\n if (Array.isArray(children)) {\n return children.every(isTextChildren);\n }\n if (isValidElement(children)) {\n return isTextChildren(\n (children.props as { children?: ReactNode }).children\n );\n }\n return false;\n};\n\nexport const Link: FC<LinkProps> = (props) => {\n const {\n variant = 'default',\n color = 'custom',\n roundedSize,\n children,\n label,\n className,\n isActive,\n underlined,\n locale,\n size: sizeProp,\n isExternalLink: isExternalLinkProp,\n isPageSection: isPageSectionProp,\n href: hrefProp,\n rel: relProp,\n target: targetProp,\n ...otherProps\n } = props;\n\n const isButton = variant === 'button' || variant === 'button-outlined';\n const size = sizeProp ?? (isButton ? 'md' : 'custom');\n\n const isExternalLink = isExternalLinkProp ?? checkIsExternalLink(props);\n const isPageSection = isPageSectionProp ?? hrefProp?.startsWith('#') ?? false;\n\n const isChildrenString = isTextChildren(children);\n\n /**\n * External links always carry the full safety/SEO `rel`, so a caller cannot\n * accidentally strip `nofollow`. Internal links keep whatever the caller asked for.\n */\n const rel = isExternalLink ? 'noopener noreferrer nofollow' : relProp;\n\n const target = isExternalLink ? '_blank' : (targetProp ?? '_self');\n\n const resolvedHref =\n locale && hrefProp && !isExternalLink && !isPageSection\n ? getLocalizedUrl(hrefProp, locale)\n : hrefProp;\n\n const href = resolvedHref === '' ? undefined : resolvedHref;\n\n return (\n <a\n {...otherProps}\n href={href}\n aria-label={label}\n rel={rel}\n target={target}\n aria-current={isActive ? 'page' : undefined}\n suppressHydrationWarning\n className={cn(\n linkVariants({\n variant,\n color,\n roundedSize,\n underlined,\n size,\n className,\n })\n )}\n >\n {isButton && isChildrenString ? <span>{children}</span> : children}\n\n {isExternalLink && isChildrenString && (\n <ExternalLink className=\"ml-2 inline-block size-4\" />\n )}\n {isPageSection && <MoveRight className=\"ml-2 inline-block size-4\" />}\n </a>\n );\n};\n"],"mappings":"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"}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { usePersistedStore } from "../../hooks/usePersistedStore.mjs";
|
|
4
3
|
import { Button } from "../Button/Button.mjs";
|
|
5
4
|
import { Container } from "../Container/index.mjs";
|
|
6
5
|
import { DropDown } from "../DropDown/index.mjs";
|
|
7
6
|
import { Input } from "../Input/Input.mjs";
|
|
8
7
|
import { SwitchSelector } from "../SwitchSelector/SwitchSelector.mjs";
|
|
8
|
+
import { usePersistedStore } from "../../hooks/usePersistedStore.mjs";
|
|
9
9
|
import { useLocaleSwitcherContent } from "./LocaleSwitcherContentContext.mjs";
|
|
10
10
|
import { useMemo, useRef, useState } from "react";
|
|
11
11
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -5,9 +5,9 @@ import { Button } from "../Button/Button.mjs";
|
|
|
5
5
|
import { Link } from "../Link/Link.mjs";
|
|
6
6
|
import { Container } from "../Container/index.mjs";
|
|
7
7
|
import { Modal } from "../Modal/Modal.mjs";
|
|
8
|
-
import { useState } from "react";
|
|
8
|
+
import { useEffect, useRef, useState } from "react";
|
|
9
9
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
10
|
-
import { MoveDiagonal } from "lucide-react";
|
|
10
|
+
import { MoveDiagonal, Play } from "lucide-react";
|
|
11
11
|
|
|
12
12
|
//#region src/components/MarkDownRender/MarkDownIframe.tsx
|
|
13
13
|
function embedLinkMeta(src) {
|
|
@@ -32,6 +32,107 @@ function embedLinkMeta(src) {
|
|
|
32
32
|
label: src
|
|
33
33
|
};
|
|
34
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* Reads the video id out of a YouTube embed address.
|
|
37
|
+
*
|
|
38
|
+
* @returns The video id, or `null` when the address is not a YouTube embed.
|
|
39
|
+
*/
|
|
40
|
+
const getYoutubeVideoId = (src) => {
|
|
41
|
+
if (!src || !/^https?:\/\//i.test(src)) return null;
|
|
42
|
+
try {
|
|
43
|
+
const url = new URL(src);
|
|
44
|
+
if (!/(^|\.)youtube(-nocookie)?\.com$/.test(url.hostname)) return null;
|
|
45
|
+
return url.pathname.replace(/^\/embed\//, "").split("/")[0] || null;
|
|
46
|
+
} catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Tracks whether an element has come close enough to the viewport to be worth
|
|
52
|
+
* loading.
|
|
53
|
+
*
|
|
54
|
+
* `loading="lazy"` is not enough for embeds inside tabs or carousels: the
|
|
55
|
+
* browser only defers frames that are far down the page, so a frame parked
|
|
56
|
+
* off-screen by a transform — an inactive tab panel — still loads immediately.
|
|
57
|
+
* A live-app embed costs upwards of a megabyte and seconds of main thread,
|
|
58
|
+
* which is the whole page's performance budget spent on something nobody is
|
|
59
|
+
* looking at.
|
|
60
|
+
*
|
|
61
|
+
* @returns A ref to attach to the placeholder, and whether it became visible.
|
|
62
|
+
*/
|
|
63
|
+
const useHasBecomeVisible = () => {
|
|
64
|
+
const elementRef = useRef(null);
|
|
65
|
+
const [hasBecomeVisible, setHasBecomeVisible] = useState(false);
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
const element = elementRef.current;
|
|
68
|
+
if (!element || hasBecomeVisible) return;
|
|
69
|
+
if (typeof IntersectionObserver === "undefined") {
|
|
70
|
+
setHasBecomeVisible(true);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const observer = new IntersectionObserver((entries) => {
|
|
74
|
+
if (entries.some((entry) => entry.isIntersecting)) {
|
|
75
|
+
setHasBecomeVisible(true);
|
|
76
|
+
observer.disconnect();
|
|
77
|
+
}
|
|
78
|
+
}, { rootMargin: "200px" });
|
|
79
|
+
observer.observe(element);
|
|
80
|
+
return () => observer.disconnect();
|
|
81
|
+
}, [hasBecomeVisible]);
|
|
82
|
+
return {
|
|
83
|
+
elementRef,
|
|
84
|
+
hasBecomeVisible
|
|
85
|
+
};
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* The embed itself: a poster for YouTube, deferred loading for anything else.
|
|
89
|
+
*
|
|
90
|
+
* A YouTube player pulls well over a megabyte of script before it can show a
|
|
91
|
+
* single frame. Standing in for it with its own thumbnail until the reader
|
|
92
|
+
* presses play keeps that cost on the readers who actually watch the video.
|
|
93
|
+
*/
|
|
94
|
+
const EmbeddedFrame = ({ src, title, className, frameProps }) => {
|
|
95
|
+
const [isPlayRequested, setIsPlayRequested] = useState(false);
|
|
96
|
+
const { elementRef, hasBecomeVisible } = useHasBecomeVisible();
|
|
97
|
+
const youtubeVideoId = getYoutubeVideoId(src);
|
|
98
|
+
if (youtubeVideoId && !isPlayRequested) return /* @__PURE__ */ jsxs("button", {
|
|
99
|
+
type: "button",
|
|
100
|
+
"aria-label": title ?? "Play video",
|
|
101
|
+
onClick: () => setIsPlayRequested(true),
|
|
102
|
+
className: cn("group relative block cursor-pointer border-0 bg-card p-0", className),
|
|
103
|
+
children: [/* @__PURE__ */ jsx("img", {
|
|
104
|
+
src: `https://i.ytimg.com/vi/${youtubeVideoId}/hqdefault.jpg`,
|
|
105
|
+
alt: "",
|
|
106
|
+
fetchPriority: "high",
|
|
107
|
+
decoding: "async",
|
|
108
|
+
className: "size-full object-cover"
|
|
109
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
110
|
+
className: "absolute inset-0 flex items-center justify-center",
|
|
111
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
112
|
+
className: "flex size-14 items-center justify-center rounded-full bg-text/70 text-text-opposite transition-transform group-hover:scale-110",
|
|
113
|
+
children: /* @__PURE__ */ jsx(Play, { className: "size-6 translate-x-0.5 fill-current" })
|
|
114
|
+
})
|
|
115
|
+
})]
|
|
116
|
+
});
|
|
117
|
+
if (!hasBecomeVisible && !isPlayRequested) return /* @__PURE__ */ jsx("div", {
|
|
118
|
+
ref: elementRef,
|
|
119
|
+
"aria-hidden": true,
|
|
120
|
+
className
|
|
121
|
+
});
|
|
122
|
+
const frameSrc = (() => {
|
|
123
|
+
if (!isPlayRequested || !youtubeVideoId || !src) return src;
|
|
124
|
+
const playbackSrc = new URL(src);
|
|
125
|
+
playbackSrc.searchParams.set("autoplay", "1");
|
|
126
|
+
return playbackSrc.href;
|
|
127
|
+
})();
|
|
128
|
+
return /* @__PURE__ */ jsx("iframe", {
|
|
129
|
+
...frameProps,
|
|
130
|
+
src: frameSrc,
|
|
131
|
+
title,
|
|
132
|
+
loading: "lazy",
|
|
133
|
+
className
|
|
134
|
+
});
|
|
135
|
+
};
|
|
35
136
|
const MarkDownIframe = (props) => {
|
|
36
137
|
const { src, className, title, ...rest } = props;
|
|
37
138
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
@@ -43,10 +144,10 @@ const MarkDownIframe = (props) => {
|
|
|
43
144
|
className: "overflow-hidden p-0",
|
|
44
145
|
gap: "none",
|
|
45
146
|
children: [
|
|
46
|
-
/* @__PURE__ */ jsx(
|
|
47
|
-
...rest,
|
|
147
|
+
/* @__PURE__ */ jsx(EmbeddedFrame, {
|
|
48
148
|
src,
|
|
49
149
|
title,
|
|
150
|
+
frameProps: rest,
|
|
50
151
|
className: cn("block max-h-[80vh] min-h-[12rem] w-full border-0", className)
|
|
51
152
|
}),
|
|
52
153
|
/* @__PURE__ */ jsxs("div", {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MarkDownIframe.mjs","names":[],"sources":["../../../../src/components/MarkDownRender/MarkDownIframe.tsx"],"sourcesContent":["'use client';\n\nimport { Button } from '@components/Button';\nimport { Container } from '@components/Container';\nimport { Link } from '@components/Link';\nimport { Modal } from '@components/Modal';\nimport { cn } from '@utils/cn';\nimport { MoveDiagonal } from 'lucide-react';\nimport { type ComponentProps, type FC, useState } from 'react';\n\nfunction embedLinkMeta(src: string | undefined): {\n href: string;\n label: string;\n} {\n if (!src) return { href: '', label: '' };\n if (/^https?:\\/\\//i.test(src)) {\n try {\n const url = new URL(src);\n return { href: url.href, label: url.host };\n } catch {\n return { href: src, label: src };\n }\n }\n return { href: src, label: src };\n}\n\nexport const MarkDownIframe: FC<ComponentProps<'iframe'>> = (props) => {\n const { src, className, title, ...rest } = props;\n const [isModalOpen, setIsModalOpen] = useState(false);\n const { href, label } = embedLinkMeta(src);\n\n return (\n <Container\n roundedSize=\"2xl\"\n border\n borderColor=\"neutral\"\n className=\"overflow-hidden p-0\"\n gap=\"none\"\n >\n <iframe\n {...rest}\n src={src}\n title={title}\n className={cn(\n 'block max-h-[80vh] min-h-[12rem] w-full border-0',\n className\n )}\n />\n <div className=\"flex items-center justify-between gap-3 px-3 py-1\">\n {href ? (\n <Link\n href={href}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n label=\"\"\n color=\"neutral\"\n className=\"inline-flex min-w-0 max-w-[calc(100%-3rem)] items-center gap-2 text-neutral text-xs underline-offset-2 hover:text-text hover:underline\"\n >\n {label}\n </Link>\n ) : (\n <span className=\"text-neutral text-sm\">Embedded frame</span>\n )}\n <Button\n variant=\"hoverable\"\n size=\"icon-md\"\n onClick={() => setIsModalOpen(true)}\n label=\"Open embedded page in fullscreen\"\n Icon={MoveDiagonal}\n />\n </div>\n\n <Modal\n isOpen={isModalOpen}\n onClose={() => setIsModalOpen(false)}\n size=\"unset\"\n hasCloseButton\n isScrollable\n padding=\"sm\"\n >\n {isModalOpen && src ? (\n <Container\n roundedSize=\"2xl\"\n border\n borderColor=\"neutral\"\n className=\"overflow-hidden p-0\"\n gap=\"none\"\n >\n <iframe\n {...rest}\n src={src}\n title={title ?? 'Embedded content'}\n allowFullScreen\n className=\"block min-h-[82vh] w-full border-0\"\n />\n </Container>\n ) : null}\n </Modal>\n </Container>\n );\n};\n"],"mappings":";;;;;;;;;;;;AAUA,SAAS,cAAc,KAGrB;CACA,IAAI,CAAC,KAAK,OAAO;EAAE,MAAM;EAAI,OAAO;CAAG;CACvC,IAAI,gBAAgB,KAAK,GAAG,GAC1B,IAAI;EACF,MAAM,MAAM,IAAI,IAAI,GAAG;EACvB,OAAO;GAAE,MAAM,IAAI;GAAM,OAAO,IAAI;EAAK;CAC3C,QAAQ;EACN,OAAO;GAAE,MAAM;GAAK,OAAO;EAAI;CACjC;CAEF,OAAO;EAAE,MAAM;EAAK,OAAO;CAAI;AACjC;AAEA,MAAa,kBAAgD,UAAU;CACrE,MAAM,EAAE,KAAK,WAAW,OAAO,GAAG,SAAS;CAC3C,MAAM,CAAC,aAAa,kBAAkB,SAAS,KAAK;CACpD,MAAM,EAAE,MAAM,UAAU,cAAc,GAAG;CAEzC,OACE,qBAAC,WAAD;EACE,aAAY;EACZ;EACA,aAAY;EACZ,WAAU;EACV,KAAI;YALN;GAOE,oBAAC,UAAD;IACE,GAAI;IACC;IACE;IACP,WAAW,GACT,oDACA,SACF;GACD;GACD,qBAAC,OAAD;IAAK,WAAU;cAAf,CACG,OACC,oBAAC,MAAD;KACQ;KACN,QAAO;KACP,KAAI;KACJ,OAAM;KACN,OAAM;KACN,WAAU;eAET;IACG,KAEN,oBAAC,QAAD;KAAM,WAAU;eAAuB;IAAoB,IAE7D,oBAAC,QAAD;KACE,SAAQ;KACR,MAAK;KACL,eAAe,eAAe,IAAI;KAClC,OAAM;KACN,MAAM;IACP,EACE;;GAEL,oBAAC,OAAD;IACE,QAAQ;IACR,eAAe,eAAe,KAAK;IACnC,MAAK;IACL;IACA;IACA,SAAQ;cAEP,eAAe,MACd,oBAAC,WAAD;KACE,aAAY;KACZ;KACA,aAAY;KACZ,WAAU;KACV,KAAI;eAEJ,oBAAC,UAAD;MACE,GAAI;MACC;MACL,OAAO,SAAS;MAChB;MACA,WAAU;KACX;IACQ,KACT;GACC;EACE;;AAEf"}
|
|
1
|
+
{"version":3,"file":"MarkDownIframe.mjs","names":[],"sources":["../../../../src/components/MarkDownRender/MarkDownIframe.tsx"],"sourcesContent":["'use client';\n\nimport { Button } from '@components/Button';\nimport { Container } from '@components/Container';\nimport { Link } from '@components/Link';\nimport { Modal } from '@components/Modal';\nimport { cn } from '@utils/cn';\nimport { MoveDiagonal, Play } from 'lucide-react';\nimport {\n type ComponentProps,\n type FC,\n useEffect,\n useRef,\n useState,\n} from 'react';\n\nfunction embedLinkMeta(src: string | undefined): {\n href: string;\n label: string;\n} {\n if (!src) return { href: '', label: '' };\n if (/^https?:\\/\\//i.test(src)) {\n try {\n const url = new URL(src);\n return { href: url.href, label: url.host };\n } catch {\n return { href: src, label: src };\n }\n }\n return { href: src, label: src };\n}\n\n/**\n * Reads the video id out of a YouTube embed address.\n *\n * @returns The video id, or `null` when the address is not a YouTube embed.\n */\nconst getYoutubeVideoId = (src: string | undefined): string | null => {\n if (!src || !/^https?:\\/\\//i.test(src)) return null;\n\n try {\n const url = new URL(src);\n if (!/(^|\\.)youtube(-nocookie)?\\.com$/.test(url.hostname)) return null;\n\n const videoId = url.pathname.replace(/^\\/embed\\//, '').split('/')[0];\n\n return videoId || null;\n } catch {\n return null;\n }\n};\n\n/**\n * Tracks whether an element has come close enough to the viewport to be worth\n * loading.\n *\n * `loading=\"lazy\"` is not enough for embeds inside tabs or carousels: the\n * browser only defers frames that are far down the page, so a frame parked\n * off-screen by a transform — an inactive tab panel — still loads immediately.\n * A live-app embed costs upwards of a megabyte and seconds of main thread,\n * which is the whole page's performance budget spent on something nobody is\n * looking at.\n *\n * @returns A ref to attach to the placeholder, and whether it became visible.\n */\nconst useHasBecomeVisible = <ElementType extends HTMLElement>() => {\n const elementRef = useRef<ElementType>(null);\n const [hasBecomeVisible, setHasBecomeVisible] = useState(false);\n\n useEffect(() => {\n const element = elementRef.current;\n if (!element || hasBecomeVisible) return;\n\n if (typeof IntersectionObserver === 'undefined') {\n setHasBecomeVisible(true);\n return;\n }\n\n const observer = new IntersectionObserver(\n (entries) => {\n if (entries.some((entry) => entry.isIntersecting)) {\n setHasBecomeVisible(true);\n observer.disconnect();\n }\n },\n { rootMargin: '200px' }\n );\n\n observer.observe(element);\n\n return () => observer.disconnect();\n }, [hasBecomeVisible]);\n\n return { elementRef, hasBecomeVisible };\n};\n\ntype EmbeddedFrameProps = {\n src: string | undefined;\n title: string | undefined;\n className: string;\n frameProps: Omit<ComponentProps<'iframe'>, 'src' | 'title' | 'className'>;\n};\n\n/**\n * The embed itself: a poster for YouTube, deferred loading for anything else.\n *\n * A YouTube player pulls well over a megabyte of script before it can show a\n * single frame. Standing in for it with its own thumbnail until the reader\n * presses play keeps that cost on the readers who actually watch the video.\n */\nconst EmbeddedFrame: FC<EmbeddedFrameProps> = ({\n src,\n title,\n className,\n frameProps,\n}) => {\n const [isPlayRequested, setIsPlayRequested] = useState(false);\n const { elementRef, hasBecomeVisible } =\n useHasBecomeVisible<HTMLDivElement>();\n const youtubeVideoId = getYoutubeVideoId(src);\n\n if (youtubeVideoId && !isPlayRequested) {\n return (\n <button\n type=\"button\"\n aria-label={title ?? 'Play video'}\n onClick={() => setIsPlayRequested(true)}\n className={cn(\n 'group relative block cursor-pointer border-0 bg-card p-0',\n className\n )}\n >\n {/*\n * The poster stands in for the player, so on a page that opens with a\n * video it is the largest element painted — deferring it would defer\n * the page's LCP. It is the one image here worth fetching eagerly.\n */}\n <img\n src={`https://i.ytimg.com/vi/${youtubeVideoId}/hqdefault.jpg`}\n alt=\"\"\n fetchPriority=\"high\"\n decoding=\"async\"\n className=\"size-full object-cover\"\n />\n <span className=\"absolute inset-0 flex items-center justify-center\">\n <span className=\"flex size-14 items-center justify-center rounded-full bg-text/70 text-text-opposite transition-transform group-hover:scale-110\">\n <Play className=\"size-6 translate-x-0.5 fill-current\" />\n </span>\n </span>\n </button>\n );\n }\n\n if (!hasBecomeVisible && !isPlayRequested) {\n // Same box as the frame it stands in for, so nothing shifts once it loads.\n return <div ref={elementRef} aria-hidden className={className} />;\n }\n\n const frameSrc = (() => {\n if (!isPlayRequested || !youtubeVideoId || !src) return src;\n\n const playbackSrc = new URL(src);\n playbackSrc.searchParams.set('autoplay', '1');\n\n return playbackSrc.href;\n })();\n\n return (\n <iframe\n {...frameProps}\n src={frameSrc}\n title={title}\n loading=\"lazy\"\n className={className}\n />\n );\n};\n\nexport const MarkDownIframe: FC<ComponentProps<'iframe'>> = (props) => {\n const { src, className, title, ...rest } = props;\n const [isModalOpen, setIsModalOpen] = useState(false);\n const { href, label } = embedLinkMeta(src);\n\n return (\n <Container\n roundedSize=\"2xl\"\n border\n borderColor=\"neutral\"\n className=\"overflow-hidden p-0\"\n gap=\"none\"\n >\n <EmbeddedFrame\n src={src}\n title={title}\n frameProps={rest}\n className={cn(\n 'block max-h-[80vh] min-h-[12rem] w-full border-0',\n className\n )}\n />\n <div className=\"flex items-center justify-between gap-3 px-3 py-1\">\n {href ? (\n <Link\n href={href}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n label=\"\"\n color=\"neutral\"\n className=\"inline-flex min-w-0 max-w-[calc(100%-3rem)] items-center gap-2 text-neutral text-xs underline-offset-2 hover:text-text hover:underline\"\n >\n {label}\n </Link>\n ) : (\n <span className=\"text-neutral text-sm\">Embedded frame</span>\n )}\n <Button\n variant=\"hoverable\"\n size=\"icon-md\"\n onClick={() => setIsModalOpen(true)}\n label=\"Open embedded page in fullscreen\"\n Icon={MoveDiagonal}\n />\n </div>\n\n <Modal\n isOpen={isModalOpen}\n onClose={() => setIsModalOpen(false)}\n size=\"unset\"\n hasCloseButton\n isScrollable\n padding=\"sm\"\n >\n {isModalOpen && src ? (\n <Container\n roundedSize=\"2xl\"\n border\n borderColor=\"neutral\"\n className=\"overflow-hidden p-0\"\n gap=\"none\"\n >\n <iframe\n {...rest}\n src={src}\n title={title ?? 'Embedded content'}\n allowFullScreen\n className=\"block min-h-[82vh] w-full border-0\"\n />\n </Container>\n ) : null}\n </Modal>\n </Container>\n );\n};\n"],"mappings":"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"}
|
|
@@ -47,7 +47,7 @@ const StrongRenderer = (props) => /* @__PURE__ */ jsx("strong", {
|
|
|
47
47
|
className: "text-text",
|
|
48
48
|
...props
|
|
49
49
|
});
|
|
50
|
-
const MemoizedCodeBlock = memo(({ className, children,
|
|
50
|
+
const MemoizedCodeBlock = memo(({ className, children, highlightedHtml, lang, ...rest }) => {
|
|
51
51
|
const content = String(children ?? "").replace(/\n$/, "");
|
|
52
52
|
if (!!!className) {
|
|
53
53
|
const decodedContent = content.replace(/&(?:amp;)?#(\d+);/g, (_, code) => String.fromCharCode(parseInt(code, 10)));
|
|
@@ -61,18 +61,18 @@ const MemoizedCodeBlock = memo(({ className, children, isDarkMode, lang, ...rest
|
|
|
61
61
|
...rest,
|
|
62
62
|
language,
|
|
63
63
|
showHeader: true,
|
|
64
|
-
|
|
64
|
+
highlightedHtml,
|
|
65
65
|
children: content
|
|
66
66
|
});
|
|
67
|
-
}, (prevProps, nextProps) => prevProps.children === nextProps.children && prevProps.className === nextProps.className && prevProps.
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
};
|
|
67
|
+
}, (prevProps, nextProps) => prevProps.children === nextProps.children && prevProps.className === nextProps.className && prevProps.highlightedHtml === nextProps.highlightedHtml);
|
|
68
|
+
/**
|
|
69
|
+
* Renders a fenced code block.
|
|
70
|
+
*
|
|
71
|
+
* `highlightedHtml` reaches this renderer through the code node's attributes:
|
|
72
|
+
* a pipeline that highlights the markdown ahead of time writes it there, and
|
|
73
|
+
* the block then paints without loading the highlighter in the browser.
|
|
74
|
+
*/
|
|
75
|
+
const CodeRenderer = (props) => /* @__PURE__ */ jsx(MemoizedCodeBlock, { ...props });
|
|
76
76
|
const BlockquoteRenderer = ({ className, ...props }) => /* @__PURE__ */ jsx("blockquote", {
|
|
77
77
|
className: cn("mt-5 gap-3 border-card border-l-4 pl-5 text-neutral [&_strong]:text-neutral", className),
|
|
78
78
|
...props
|
|
@@ -146,15 +146,15 @@ const staticMarkdownComponents = {
|
|
|
146
146
|
Column: ColumnRenderer,
|
|
147
147
|
iframe: Iframe
|
|
148
148
|
};
|
|
149
|
-
const createMarkdownComponents = (
|
|
149
|
+
const createMarkdownComponents = (locale) => ({
|
|
150
150
|
...staticMarkdownComponents,
|
|
151
|
-
code:
|
|
151
|
+
code: CodeRenderer,
|
|
152
152
|
a: createLinkRenderer(locale)
|
|
153
153
|
});
|
|
154
154
|
const baseMarkdownComponents = staticMarkdownComponents;
|
|
155
|
-
const getIntlayerMarkdownOptions = (
|
|
156
|
-
const MarkdownRenderer = ({ children,
|
|
157
|
-
const markdownComponents = createMarkdownComponents(
|
|
155
|
+
const getIntlayerMarkdownOptions = () => ({ components: baseMarkdownComponents });
|
|
156
|
+
const MarkdownRenderer = ({ children, locale, forceBlock, preserveFrontmatter, tagfilter, components: componentsProp, wrapper }) => {
|
|
157
|
+
const markdownComponents = createMarkdownComponents(locale);
|
|
158
158
|
const markdownContent = renderMarkdown(children, {
|
|
159
159
|
components: {
|
|
160
160
|
...markdownComponents,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MarkDownRender.mjs","names":[],"sources":["../../../../src/components/MarkDownRender/MarkDownRender.tsx"],"sourcesContent":["import type { LocalesValues } from '@intlayer/types/module_augmentation';\nimport { cn } from '@utils/cn';\nimport type { ComponentProps, ComponentPropsWithoutRef, FC } from 'react';\nimport { memo } from 'react';\nimport {\n type MarkdownRenderer as MarkdownRendererIntlayer,\n type ParsedMarkdown,\n renderMarkdown,\n} from 'react-intlayer/markdown';\nimport { H1, H2, H3, H4, H5, H6 } from '../Headers';\nimport { Code } from '../IDE/Code';\nimport { CodeProvider } from '../IDE/CodeContext';\nimport type { CodeLanguage } from '../IDE/shikiLanguages';\nimport { Link } from '../Link';\nimport { Tab } from '../Tab';\nimport { TabProvider } from '../Tab/TabContext';\nimport { Hr, SmartTable, Td, Th, Tr } from '../Table';\nimport { MarkDownIframe } from './MarkDownIframe';\n\nexport type { ParsedMarkdown };\n\n// Extracted, stable component renderers\nconst H1Renderer = (props: ComponentProps<'h1'>) => (\n <H1 isClickable className=\"mb-16 mb-8 text-text\" {...props} />\n);\nconst H2Renderer = (props: ComponentProps<'h2'>) => (\n <H2 isClickable className=\"mt-16 text-text\" {...props} />\n);\nconst H3Renderer = (props: ComponentProps<'h3'>) => (\n <H3 isClickable className=\"mt-5 text-text\" {...props} />\n);\nconst H4Renderer = (props: ComponentProps<'h4'>) => (\n <H4 isClickable className=\"mt-3 text-text\" {...props} />\n);\nconst H5Renderer = (props: ComponentProps<'h5'>) => (\n <H5 isClickable className=\"mt-3 text-text\" {...props} />\n);\nconst H6Renderer = (props: ComponentProps<'h6'>) => (\n <H6 isClickable className=\"mt-3 text-text\" {...props} />\n);\nconst StrongRenderer = (props: ComponentProps<'strong'>) => (\n <strong className=\"text-text\" {...props} />\n);\n\nconst MemoizedCodeBlock = memo(\n ({\n className,\n children,\n isDarkMode,\n lang, // Destructure html lang prop to prevent passing invalid BCP-47 language tag to Code component\n ...rest\n }: ComponentProps<'code'> & { isDarkMode?: boolean }) => {\n const content = String(children ?? '').replace(/\\n$/, '');\n const isBlock = !!className;\n\n if (!isBlock) {\n const decodedContent = content.replace(\n /&(?:amp;)?#(\\d+);/g,\n (_, code: string) => String.fromCharCode(parseInt(code, 10))\n );\n return (\n <code className=\"rounded-md border border-neutral/30 bg-card/60 box-decoration-clone px-1.5 py-0.5 font-mono text-[0.875em] leading-[inherit]\">\n {decodedContent}\n </code>\n );\n }\n\n const language = (className?.replace(/lang(?:uage)?-/, '') ||\n 'plaintext') as CodeLanguage;\n\n return (\n <Code {...rest} language={language} showHeader isDarkMode={isDarkMode}>\n {content}\n </Code>\n );\n },\n (prevProps, nextProps) =>\n prevProps.children === nextProps.children &&\n prevProps.className === nextProps.className &&\n prevProps.isDarkMode === nextProps.isDarkMode\n);\n\nconst createCodeRenderer = (isDarkMode?: boolean) => {\n return function CodeWrapper(props: ComponentProps<'code'>) {\n return <MemoizedCodeBlock {...props} isDarkMode={isDarkMode} />;\n };\n};\n\nconst BlockquoteRenderer = ({\n className,\n ...props\n}: ComponentProps<'blockquote'>) => (\n <blockquote\n className={cn(\n 'mt-5 gap-3 border-card border-l-4 pl-5 text-neutral [&_strong]:text-neutral',\n className\n )}\n {...props}\n />\n);\n\nconst UlRenderer = ({ className, ...props }: ComponentProps<'ul'>) => (\n <ul\n className={cn(\n 'mt-5 flex list-disc flex-col gap-3 pl-5 marker:text-neutral/80',\n className\n )}\n {...props}\n />\n);\n\nconst OlRenderer = ({ className, ...props }: ComponentProps<'ol'>) => (\n <ol\n className={cn(\n 'mt-5 flex list-decimal flex-col gap-3 pl-5 marker:text-neutral/80',\n className\n )}\n {...props}\n />\n);\n\nconst ImgRenderer = ({\n className,\n alt,\n src,\n ...props\n}: ComponentProps<'img'>) => (\n <img\n {...props}\n alt={alt ?? ''}\n loading=\"lazy\"\n className={cn('max-h-[80vh] max-w-full rounded-md', className)}\n src={\n src?.includes('github.com')\n ? src\n ?.replace('github.com', 'raw.githubusercontent.com')\n .replace('/blob/', '/') // GitHub raw URLs do not use /blob/\n : src\n }\n />\n);\n\nconst createLinkRenderer = (locale?: LocalesValues) => {\n return (props: ComponentProps<'a'>) => (\n <Link\n isExternalLink={props.href?.startsWith('http')}\n underlined\n locale={locale}\n label=\"\"\n color=\"text\"\n {...(props as any)}\n />\n );\n};\n\nconst PreRenderer = (props: ComponentProps<'pre'>) => <>{props.children}</>;\nconst TableRenderer = (props: ComponentProps<typeof SmartTable>) => (\n <SmartTable isRollable displayModal {...props} />\n);\n\nconst TabsRenderer = (props: ComponentProps<typeof Tab>) => (\n <Tab\n {...props}\n className=\"rounded-xl border border-card\"\n headerClassName=\"sticky rounded-xl top-24 z-5 bg-background/70 backdrop-blur overflow-x-auto\"\n />\n);\nconst ColumnsRenderer = ({\n className,\n ...props\n}: ComponentPropsWithoutRef<'div'>) => (\n <div className={cn('flex gap-4 max-md:flex-col', className)} {...props} />\n);\nconst ColumnRenderer = ({\n className,\n ...props\n}: ComponentPropsWithoutRef<'div'>) => (\n <div className={cn('flex-1', className)} {...props} />\n);\n\nconst Iframe = (props: ComponentProps<'iframe'>) => (\n <MarkDownIframe {...props} />\n);\n\n// Static configuration object for static renderers\nconst staticMarkdownComponents = {\n h1: H1Renderer,\n h2: H2Renderer,\n h3: H3Renderer,\n h4: H4Renderer,\n h5: H5Renderer,\n h6: H6Renderer,\n strong: StrongRenderer,\n blockquote: BlockquoteRenderer,\n ul: UlRenderer,\n ol: OlRenderer,\n img: ImgRenderer,\n pre: PreRenderer,\n table: TableRenderer,\n th: Th,\n tr: Tr,\n td: Td,\n hr: Hr,\n Tabs: TabsRenderer,\n Tab: Tab.Item,\n Columns: ColumnsRenderer,\n Column: ColumnRenderer,\n iframe: Iframe,\n};\n\n// Factory function to create components with dynamic props\nconst createMarkdownComponents = (\n isDarkMode?: boolean,\n locale?: LocalesValues\n) => ({\n ...staticMarkdownComponents,\n code: createCodeRenderer(isDarkMode),\n a: createLinkRenderer(locale),\n});\n\n// Export static renderers for backward compatibility\nexport const baseMarkdownComponents = staticMarkdownComponents;\n\ntype MarkdownRendererProps = {\n children: string | ParsedMarkdown;\n isDarkMode?: boolean;\n locale?: LocalesValues;\n forceBlock?: boolean;\n preserveFrontmatter?: boolean;\n tagfilter?: boolean;\n components?: ComponentProps<typeof MarkdownRendererIntlayer>['components'];\n wrapper?: ComponentProps<typeof MarkdownRendererIntlayer>['wrapper'];\n};\n\nexport const getIntlayerMarkdownOptions = (_isDarkMode?: boolean) => ({\n components: baseMarkdownComponents,\n});\n\nexport const MarkdownRenderer: FC<MarkdownRendererProps> = ({\n children,\n isDarkMode = false,\n locale,\n forceBlock,\n preserveFrontmatter,\n tagfilter,\n components: componentsProp,\n wrapper,\n}) => {\n const markdownComponents = createMarkdownComponents(isDarkMode, locale);\n\n const markdownContent = renderMarkdown(children, {\n components: {\n ...markdownComponents,\n ...componentsProp,\n },\n wrapper,\n forceBlock,\n preserveFrontmatter,\n tagfilter,\n });\n\n return (\n <CodeProvider>\n <TabProvider>{markdownContent}</TabProvider>\n </CodeProvider>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"MarkDownRender.mjs","names":[],"sources":["../../../../src/components/MarkDownRender/MarkDownRender.tsx"],"sourcesContent":["import type { LocalesValues } from '@intlayer/types/module_augmentation';\nimport { cn } from '@utils/cn';\nimport type { ComponentProps, ComponentPropsWithoutRef, FC } from 'react';\nimport { memo } from 'react';\nimport {\n type MarkdownRenderer as MarkdownRendererIntlayer,\n type ParsedMarkdown,\n renderMarkdown,\n} from 'react-intlayer/markdown';\nimport { H1, H2, H3, H4, H5, H6 } from '../Headers';\nimport { Code } from '../IDE/Code';\nimport { CodeProvider } from '../IDE/CodeContext';\nimport type { CodeLanguage } from '../IDE/shikiLanguages';\nimport { Link } from '../Link';\nimport { Tab } from '../Tab';\nimport { TabProvider } from '../Tab/TabContext';\nimport { Hr, SmartTable, Td, Th, Tr } from '../Table';\nimport { MarkDownIframe } from './MarkDownIframe';\n\nexport type { ParsedMarkdown };\n\n// Extracted, stable component renderers\nconst H1Renderer = (props: ComponentProps<'h1'>) => (\n <H1 isClickable className=\"mb-16 mb-8 text-text\" {...props} />\n);\nconst H2Renderer = (props: ComponentProps<'h2'>) => (\n <H2 isClickable className=\"mt-16 text-text\" {...props} />\n);\nconst H3Renderer = (props: ComponentProps<'h3'>) => (\n <H3 isClickable className=\"mt-5 text-text\" {...props} />\n);\nconst H4Renderer = (props: ComponentProps<'h4'>) => (\n <H4 isClickable className=\"mt-3 text-text\" {...props} />\n);\nconst H5Renderer = (props: ComponentProps<'h5'>) => (\n <H5 isClickable className=\"mt-3 text-text\" {...props} />\n);\nconst H6Renderer = (props: ComponentProps<'h6'>) => (\n <H6 isClickable className=\"mt-3 text-text\" {...props} />\n);\nconst StrongRenderer = (props: ComponentProps<'strong'>) => (\n <strong className=\"text-text\" {...props} />\n);\n\nconst MemoizedCodeBlock = memo(\n ({\n className,\n children,\n highlightedHtml,\n lang, // Destructure html lang prop to prevent passing invalid BCP-47 language tag to Code component\n ...rest\n }: ComponentProps<'code'> & { highlightedHtml?: string }) => {\n const content = String(children ?? '').replace(/\\n$/, '');\n const isBlock = !!className;\n\n if (!isBlock) {\n const decodedContent = content.replace(\n /&(?:amp;)?#(\\d+);/g,\n (_, code: string) => String.fromCharCode(parseInt(code, 10))\n );\n return (\n <code className=\"rounded-md border border-neutral/30 bg-card/60 box-decoration-clone px-1.5 py-0.5 font-mono text-[0.875em] leading-[inherit]\">\n {decodedContent}\n </code>\n );\n }\n\n const language = (className?.replace(/lang(?:uage)?-/, '') ||\n 'plaintext') as CodeLanguage;\n\n return (\n <Code\n {...rest}\n language={language}\n showHeader\n highlightedHtml={highlightedHtml}\n >\n {content}\n </Code>\n );\n },\n (prevProps, nextProps) =>\n prevProps.children === nextProps.children &&\n prevProps.className === nextProps.className &&\n prevProps.highlightedHtml === nextProps.highlightedHtml\n);\n\n/**\n * Renders a fenced code block.\n *\n * `highlightedHtml` reaches this renderer through the code node's attributes:\n * a pipeline that highlights the markdown ahead of time writes it there, and\n * the block then paints without loading the highlighter in the browser.\n */\nconst CodeRenderer = (props: ComponentProps<'code'>) => (\n <MemoizedCodeBlock {...props} />\n);\n\nconst BlockquoteRenderer = ({\n className,\n ...props\n}: ComponentProps<'blockquote'>) => (\n <blockquote\n className={cn(\n 'mt-5 gap-3 border-card border-l-4 pl-5 text-neutral [&_strong]:text-neutral',\n className\n )}\n {...props}\n />\n);\n\nconst UlRenderer = ({ className, ...props }: ComponentProps<'ul'>) => (\n <ul\n className={cn(\n 'mt-5 flex list-disc flex-col gap-3 pl-5 marker:text-neutral/80',\n className\n )}\n {...props}\n />\n);\n\nconst OlRenderer = ({ className, ...props }: ComponentProps<'ol'>) => (\n <ol\n className={cn(\n 'mt-5 flex list-decimal flex-col gap-3 pl-5 marker:text-neutral/80',\n className\n )}\n {...props}\n />\n);\n\nconst ImgRenderer = ({\n className,\n alt,\n src,\n ...props\n}: ComponentProps<'img'>) => (\n <img\n {...props}\n alt={alt ?? ''}\n loading=\"lazy\"\n className={cn('max-h-[80vh] max-w-full rounded-md', className)}\n src={\n src?.includes('github.com')\n ? src\n ?.replace('github.com', 'raw.githubusercontent.com')\n .replace('/blob/', '/') // GitHub raw URLs do not use /blob/\n : src\n }\n />\n);\n\nconst createLinkRenderer = (locale?: LocalesValues) => {\n return (props: ComponentProps<'a'>) => (\n <Link\n isExternalLink={props.href?.startsWith('http')}\n underlined\n locale={locale}\n label=\"\"\n color=\"text\"\n {...(props as any)}\n />\n );\n};\n\nconst PreRenderer = (props: ComponentProps<'pre'>) => <>{props.children}</>;\nconst TableRenderer = (props: ComponentProps<typeof SmartTable>) => (\n <SmartTable isRollable displayModal {...props} />\n);\n\nconst TabsRenderer = (props: ComponentProps<typeof Tab>) => (\n <Tab\n {...props}\n className=\"rounded-xl border border-card\"\n headerClassName=\"sticky rounded-xl top-24 z-5 bg-background/70 backdrop-blur overflow-x-auto\"\n />\n);\nconst ColumnsRenderer = ({\n className,\n ...props\n}: ComponentPropsWithoutRef<'div'>) => (\n <div className={cn('flex gap-4 max-md:flex-col', className)} {...props} />\n);\nconst ColumnRenderer = ({\n className,\n ...props\n}: ComponentPropsWithoutRef<'div'>) => (\n <div className={cn('flex-1', className)} {...props} />\n);\n\nconst Iframe = (props: ComponentProps<'iframe'>) => (\n <MarkDownIframe {...props} />\n);\n\n// Static configuration object for static renderers\nconst staticMarkdownComponents = {\n h1: H1Renderer,\n h2: H2Renderer,\n h3: H3Renderer,\n h4: H4Renderer,\n h5: H5Renderer,\n h6: H6Renderer,\n strong: StrongRenderer,\n blockquote: BlockquoteRenderer,\n ul: UlRenderer,\n ol: OlRenderer,\n img: ImgRenderer,\n pre: PreRenderer,\n table: TableRenderer,\n th: Th,\n tr: Tr,\n td: Td,\n hr: Hr,\n Tabs: TabsRenderer,\n Tab: Tab.Item,\n Columns: ColumnsRenderer,\n Column: ColumnRenderer,\n iframe: Iframe,\n};\n\n// Factory function to create components with dynamic props\nconst createMarkdownComponents = (locale?: LocalesValues) => ({\n ...staticMarkdownComponents,\n code: CodeRenderer,\n a: createLinkRenderer(locale),\n});\n\n// Export static renderers for backward compatibility\nexport const baseMarkdownComponents = staticMarkdownComponents;\n\ntype MarkdownRendererProps = {\n children: string | ParsedMarkdown;\n locale?: LocalesValues;\n forceBlock?: boolean;\n preserveFrontmatter?: boolean;\n tagfilter?: boolean;\n components?: ComponentProps<typeof MarkdownRendererIntlayer>['components'];\n wrapper?: ComponentProps<typeof MarkdownRendererIntlayer>['wrapper'];\n};\n\nexport const getIntlayerMarkdownOptions = () => ({\n components: baseMarkdownComponents,\n});\n\nexport const MarkdownRenderer: FC<MarkdownRendererProps> = ({\n children,\n locale,\n forceBlock,\n preserveFrontmatter,\n tagfilter,\n components: componentsProp,\n wrapper,\n}) => {\n const markdownComponents = createMarkdownComponents(locale);\n\n const markdownContent = renderMarkdown(children, {\n components: {\n ...markdownComponents,\n ...componentsProp,\n },\n wrapper,\n forceBlock,\n preserveFrontmatter,\n tagfilter,\n });\n\n return (\n <CodeProvider>\n <TabProvider>{markdownContent}</TabProvider>\n </CodeProvider>\n );\n};\n"],"mappings":"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"}
|
|
@@ -130,7 +130,7 @@ const MaxHeightSmoother = ({ children, isHidden, className = "", isOverable = fa
|
|
|
130
130
|
"aria-hidden": isFocusable ? isHidden : void 0,
|
|
131
131
|
tabIndex: isFocusable ? 0 : void 0,
|
|
132
132
|
role: isFocusable ? "button" : "none",
|
|
133
|
-
className: cn("group/height-smoother relative grid w-full grid-rows-[0fr] overflow-hidden transition-
|
|
133
|
+
className: cn("group/height-smoother relative grid w-full grid-rows-[0fr] overflow-hidden transition-[grid-template-rows] duration-700 ease-in-out", typeof isHidden !== "undefined" && !isHidden && "grid-rows-[1fr] overflow-x-auto", isOverable && "hover:grid-rows-[1fr] hover:overflow-x-auto", isFocusable && "focus-within:grid-rows-[1fr] focus-within:overflow-x-auto focus:grid-rows-[1fr] focus:overflow-x-auto", className),
|
|
134
134
|
...props,
|
|
135
135
|
children: /* @__PURE__ */ jsx("div", {
|
|
136
136
|
style: { minHeight: `${minHeight}px` },
|