@intlayer/design-system 9.4.1 → 9.4.3

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.
Files changed (215) hide show
  1. package/README.md +1 -1
  2. package/dist/esm/api/index.mjs +2 -2
  3. package/dist/esm/api/useUser/index.mjs +1 -1
  4. package/dist/esm/components/Avatar/index.mjs +1 -1
  5. package/dist/esm/components/Avatar/index.mjs.map +1 -1
  6. package/dist/esm/components/Badge/index.mjs +3 -3
  7. package/dist/esm/components/Badge/index.mjs.map +1 -1
  8. package/dist/esm/components/Breadcrumb/index.mjs +5 -5
  9. package/dist/esm/components/Breadcrumb/index.mjs.map +1 -1
  10. package/dist/esm/components/Browser/Browser.mjs +4 -4
  11. package/dist/esm/components/Browser/Browser.mjs.map +1 -1
  12. package/dist/esm/components/Button/Button.mjs +1 -0
  13. package/dist/esm/components/Button/Button.mjs.map +1 -1
  14. package/dist/esm/components/Carousel/index.mjs +5 -5
  15. package/dist/esm/components/Carousel/index.mjs.map +1 -1
  16. package/dist/esm/components/CollapsibleTable/CollapsibleTable.mjs +5 -5
  17. package/dist/esm/components/CollapsibleTable/CollapsibleTable.mjs.map +1 -1
  18. package/dist/esm/components/Container/index.mjs +2 -2
  19. package/dist/esm/components/Container/index.mjs.map +1 -1
  20. package/dist/esm/components/ContentEditor/ContentEditorTextArea.mjs +1 -1
  21. package/dist/esm/components/DictionaryEditor/ItemLayout.mjs +1 -1
  22. package/dist/esm/components/DictionaryEditor/ItemLayout.mjs.map +1 -1
  23. package/dist/esm/components/DictionaryFieldEditor/ContentEditorView/TextEditor.mjs +7 -7
  24. package/dist/esm/components/DictionaryFieldEditor/ContentEditorView/TextEditor.mjs.map +1 -1
  25. package/dist/esm/components/DictionaryFieldEditor/DictionaryCreationForm/DictionaryCreationForm.mjs +1 -1
  26. package/dist/esm/components/DictionaryFieldEditor/DictionaryDetails/DictionaryDetailsForm.mjs +9 -9
  27. package/dist/esm/components/DictionaryFieldEditor/DictionaryDetails/DictionaryDetailsForm.mjs.map +1 -1
  28. package/dist/esm/components/DictionaryFieldEditor/DictionaryFieldEditor.mjs +4 -4
  29. package/dist/esm/components/DictionaryFieldEditor/DictionaryFieldEditor.mjs.map +1 -1
  30. package/dist/esm/components/DictionaryFieldEditor/NavigationView/NavigationViewNode.mjs +1 -1
  31. package/dist/esm/components/DictionaryFieldEditor/{ContentEditor.mjs → NodeEditor.mjs} +9 -9
  32. package/dist/esm/components/DictionaryFieldEditor/NodeEditor.mjs.map +1 -0
  33. package/dist/esm/components/DictionaryFieldEditor/SaveForm/SaveForm.mjs +3 -3
  34. package/dist/esm/components/DictionaryFieldEditor/SaveForm/SaveForm.mjs.map +1 -1
  35. package/dist/esm/components/DictionaryFieldEditor/StructureView/StructureView.mjs +2 -2
  36. package/dist/esm/components/DictionaryFieldEditor/StructureView/StructureView.mjs.map +1 -1
  37. package/dist/esm/components/DictionaryFieldEditor/VersionSwitcherDropDown/VersionSwitcher.mjs +2 -2
  38. package/dist/esm/components/DictionaryFieldEditor/VersionSwitcherDropDown/VersionSwitcher.mjs.map +1 -1
  39. package/dist/esm/components/DictionaryFieldEditor/index.mjs +3 -3
  40. package/dist/esm/components/Footer/index.mjs +5 -3
  41. package/dist/esm/components/Footer/index.mjs.map +1 -1
  42. package/dist/esm/components/HeightResizer/index.mjs +1 -1
  43. package/dist/esm/components/HeightResizer/index.mjs.map +1 -1
  44. package/dist/esm/components/IDE/Code.mjs +1 -1
  45. package/dist/esm/components/IDE/Code.mjs.map +1 -1
  46. package/dist/esm/components/IDE/CodeBlockHighlight.mjs +1 -1
  47. package/dist/esm/components/IDE/CodeBlockHighlight.mjs.map +1 -1
  48. package/dist/esm/components/IDE/CodeBlockServer.mjs +1 -1
  49. package/dist/esm/components/IDE/CodeBlockServer.mjs.map +1 -1
  50. package/dist/esm/components/IDE/CodeBlockShiki.mjs +6 -48
  51. package/dist/esm/components/IDE/CodeBlockShiki.mjs.map +1 -1
  52. package/dist/esm/components/IDE/FileList.mjs +1 -1
  53. package/dist/esm/components/IDE/FileList.mjs.map +1 -1
  54. package/dist/esm/components/IDE/FileTree.mjs +1 -1
  55. package/dist/esm/components/IDE/FileTree.mjs.map +1 -1
  56. package/dist/esm/components/IDE/IDE.mjs +10 -7
  57. package/dist/esm/components/IDE/IDE.mjs.map +1 -1
  58. package/dist/esm/components/IDE/MarkDownRender.mjs +4 -3
  59. package/dist/esm/components/IDE/MarkDownRender.mjs.map +1 -1
  60. package/dist/esm/components/IDE/shikiBundle.mjs +61 -0
  61. package/dist/esm/components/IDE/shikiBundle.mjs.map +1 -0
  62. package/dist/esm/components/IDE/shikiLanguages.mjs +2 -39
  63. package/dist/esm/components/IDE/shikiLanguages.mjs.map +1 -1
  64. package/dist/esm/components/IDE/shikiThemes.mjs.map +1 -1
  65. package/dist/esm/components/Input/Checkbox.mjs +4 -4
  66. package/dist/esm/components/Input/Checkbox.mjs.map +1 -1
  67. package/dist/esm/components/Input/Input.mjs +2 -2
  68. package/dist/esm/components/Input/Input.mjs.map +1 -1
  69. package/dist/esm/components/Input/OTPInput.mjs +1 -1
  70. package/dist/esm/components/Input/OTPInput.mjs.map +1 -1
  71. package/dist/esm/components/KeyboardShortcut/KeyboardShortcut.mjs +2 -2
  72. package/dist/esm/components/KeyboardShortcut/KeyboardShortcut.mjs.map +1 -1
  73. package/dist/esm/components/LanguageBackground/LanguageSection.mjs +6 -8
  74. package/dist/esm/components/LanguageBackground/LanguageSection.mjs.map +1 -1
  75. package/dist/esm/components/LanguageBackground/index.mjs +2 -2
  76. package/dist/esm/components/LocaleSwitcherContentDropDown/LocaleSwitcherContent.mjs +2 -2
  77. package/dist/esm/components/LocaleSwitcherContentDropDown/LocaleSwitcherContent.mjs.map +1 -1
  78. package/dist/esm/components/MarkDownRender/MarkDownIframe.mjs +8 -3
  79. package/dist/esm/components/MarkDownRender/MarkDownIframe.mjs.map +1 -1
  80. package/dist/esm/components/MarkDownRender/MarkDownRender.mjs +9 -8
  81. package/dist/esm/components/MarkDownRender/MarkDownRender.mjs.map +1 -1
  82. package/dist/esm/components/MarkdownEditor/MarkdownEditor.mjs +2 -2
  83. package/dist/esm/components/MarkdownEditor/MarkdownEditor.mjs.map +1 -1
  84. package/dist/esm/components/MarkdownEditor/extensions.mjs +1 -1
  85. package/dist/esm/components/MarkdownEditor/extensions.mjs.map +1 -1
  86. package/dist/esm/components/MarkdownEditor/generative/AISelector.mjs +3 -3
  87. package/dist/esm/components/MarkdownEditor/generative/AISelector.mjs.map +1 -1
  88. package/dist/esm/components/MarkdownEditor/generative/AISelectorCommands.mjs +2 -2
  89. package/dist/esm/components/MarkdownEditor/generative/AISelectorCommands.mjs.map +1 -1
  90. package/dist/esm/components/MarkdownEditor/generative/GenerativeMenuSwitch.mjs +2 -2
  91. package/dist/esm/components/MarkdownEditor/generative/GenerativeMenuSwitch.mjs.map +1 -1
  92. package/dist/esm/components/MarkdownEditor/novel/extensions/code-block-shiki.mjs +197 -0
  93. package/dist/esm/components/MarkdownEditor/novel/extensions/code-block-shiki.mjs.map +1 -0
  94. package/dist/esm/components/MarkdownEditor/novel/extensions/index.mjs +2 -1
  95. package/dist/esm/components/MarkdownEditor/novel/extensions/index.mjs.map +1 -1
  96. package/dist/esm/components/MarkdownEditor/novel/index.mjs +2 -1
  97. package/dist/esm/components/Modal/Modal.mjs +0 -1
  98. package/dist/esm/components/Modal/Modal.mjs.map +1 -1
  99. package/dist/esm/components/Navbar/DesktopNavbar.mjs +3 -3
  100. package/dist/esm/components/Navbar/DesktopNavbar.mjs.map +1 -1
  101. package/dist/esm/components/Navbar/MobileNavbar.mjs +1 -1
  102. package/dist/esm/components/Navbar/MobileNavbar.mjs.map +1 -1
  103. package/dist/esm/components/Pattern/GridDistortionPattern.mjs +2 -2
  104. package/dist/esm/components/Pattern/GridDistortionPattern.mjs.map +1 -1
  105. package/dist/esm/components/Pattern/GridPattern.mjs +1 -1
  106. package/dist/esm/components/Pattern/GridPattern.mjs.map +1 -1
  107. package/dist/esm/components/RightDrawer/RightDrawer.mjs +2 -2
  108. package/dist/esm/components/RightDrawer/RightDrawer.mjs.map +1 -1
  109. package/dist/esm/components/Select/Multiselect.mjs +2 -2
  110. package/dist/esm/components/Select/Multiselect.mjs.map +1 -1
  111. package/dist/esm/components/Select/Select.mjs +2 -2
  112. package/dist/esm/components/Select/Select.mjs.map +1 -1
  113. package/dist/esm/components/Status/index.mjs +28 -0
  114. package/dist/esm/components/Status/index.mjs.map +1 -0
  115. package/dist/esm/components/Steps/index.mjs +3 -3
  116. package/dist/esm/components/Steps/index.mjs.map +1 -1
  117. package/dist/esm/components/SwitchSelector/SwitchSelector.mjs +12 -11
  118. package/dist/esm/components/SwitchSelector/SwitchSelector.mjs.map +1 -1
  119. package/dist/esm/components/SwitchSelector/VerticalSwitchSelector.mjs +6 -6
  120. package/dist/esm/components/SwitchSelector/VerticalSwitchSelector.mjs.map +1 -1
  121. package/dist/esm/components/Tab/Tab.mjs +1 -1
  122. package/dist/esm/components/Tab/Tab.mjs.map +1 -1
  123. package/dist/esm/components/TabSelector/TabSelector.mjs +5 -5
  124. package/dist/esm/components/TabSelector/TabSelector.mjs.map +1 -1
  125. package/dist/esm/components/Table/SmartTable.mjs +2 -2
  126. package/dist/esm/components/Table/SmartTable.mjs.map +1 -1
  127. package/dist/esm/components/Table/Table.mjs +1 -1
  128. package/dist/esm/components/Table/Table.mjs.map +1 -1
  129. package/dist/esm/components/Table/TableElements.mjs +2 -2
  130. package/dist/esm/components/Table/TableElements.mjs.map +1 -1
  131. package/dist/esm/components/Tag/index.mjs +1 -1
  132. package/dist/esm/components/Tag/index.mjs.map +1 -1
  133. package/dist/esm/components/TechLogo/TechLogo.mjs +2 -0
  134. package/dist/esm/components/TechLogo/TechLogo.mjs.map +1 -1
  135. package/dist/esm/components/TechLogo/logos/HTMX.mjs +31 -0
  136. package/dist/esm/components/TechLogo/logos/HTMX.mjs.map +1 -0
  137. package/dist/esm/components/TechLogo/logos/Remix.mjs +22 -0
  138. package/dist/esm/components/TechLogo/logos/Remix.mjs.map +1 -0
  139. package/dist/esm/components/TechLogo/techLogos.mjs +5 -1
  140. package/dist/esm/components/TechLogo/techLogos.mjs.map +1 -1
  141. package/dist/esm/components/Terminal/Terminal.mjs +4 -4
  142. package/dist/esm/components/Terminal/Terminal.mjs.map +1 -1
  143. package/dist/esm/components/TextArea/TextArea.mjs.map +1 -1
  144. package/dist/esm/components/Toaster/Toast.mjs +4 -4
  145. package/dist/esm/components/Toaster/Toast.mjs.map +1 -1
  146. package/dist/esm/components/index.mjs +8 -7
  147. package/dist/esm/hooks/usePersistedStore.mjs +6 -18
  148. package/dist/esm/hooks/usePersistedStore.mjs.map +1 -1
  149. package/dist/esm/providers/ReactQueryProvider.mjs +10 -2
  150. package/dist/esm/providers/ReactQueryProvider.mjs.map +1 -1
  151. package/dist/esm/providers/ThemeProvider.mjs +27 -15
  152. package/dist/esm/providers/ThemeProvider.mjs.map +1 -1
  153. package/dist/esm/routes.mjs +24 -6
  154. package/dist/esm/routes.mjs.map +1 -1
  155. package/dist/esm/structured-data/buildItemListJsonLd.mjs +27 -0
  156. package/dist/esm/structured-data/buildItemListJsonLd.mjs.map +1 -0
  157. package/dist/esm/structured-data/index.mjs +2 -1
  158. package/dist/types/components/Badge/index.d.ts.map +1 -1
  159. package/dist/types/components/Button/Button.d.ts +2 -2
  160. package/dist/types/components/Button/Button.d.ts.map +1 -1
  161. package/dist/types/components/Container/index.d.ts.map +1 -1
  162. package/dist/types/components/DictionaryFieldEditor/ContentEditorView/TextEditor.d.ts.map +1 -1
  163. package/dist/types/components/DictionaryFieldEditor/NodeEditor.d.ts +10 -0
  164. package/dist/types/components/DictionaryFieldEditor/NodeEditor.d.ts.map +1 -0
  165. package/dist/types/components/DictionaryFieldEditor/index.d.ts +2 -2
  166. package/dist/types/components/Footer/index.d.ts.map +1 -1
  167. package/dist/types/components/Form/FormBase.d.ts +1 -1
  168. package/dist/types/components/IDE/CodeBlockServer.d.ts +1 -1
  169. package/dist/types/components/IDE/CodeBlockServer.d.ts.map +1 -1
  170. package/dist/types/components/IDE/CodeBlockShiki.d.ts.map +1 -1
  171. package/dist/types/components/IDE/MarkDownRender.d.ts +1 -0
  172. package/dist/types/components/IDE/MarkDownRender.d.ts.map +1 -1
  173. package/dist/types/components/IDE/shikiBundle.d.ts +74 -0
  174. package/dist/types/components/IDE/shikiBundle.d.ts.map +1 -0
  175. package/dist/types/components/IDE/shikiLanguages.d.ts +2 -39
  176. package/dist/types/components/IDE/shikiLanguages.d.ts.map +1 -1
  177. package/dist/types/components/IDE/shikiThemes.d.ts +3 -1
  178. package/dist/types/components/IDE/shikiThemes.d.ts.map +1 -1
  179. package/dist/types/components/LanguageBackground/LanguageSection.d.ts +5 -1
  180. package/dist/types/components/LanguageBackground/LanguageSection.d.ts.map +1 -1
  181. package/dist/types/components/LanguageBackground/index.d.ts +2 -2
  182. package/dist/types/components/MarkDownRender/MarkDownIframe.d.ts.map +1 -1
  183. package/dist/types/components/MarkDownRender/MarkDownRender.d.ts.map +1 -1
  184. package/dist/types/components/MarkdownEditor/novel/extensions/code-block-shiki.d.ts +15 -0
  185. package/dist/types/components/MarkdownEditor/novel/extensions/code-block-shiki.d.ts.map +1 -0
  186. package/dist/types/components/MarkdownEditor/novel/extensions/index.d.ts +2 -1
  187. package/dist/types/components/MarkdownEditor/novel/extensions/index.d.ts.map +1 -1
  188. package/dist/types/components/MarkdownEditor/novel/index.d.ts +2 -1
  189. package/dist/types/components/Status/index.d.ts +8 -0
  190. package/dist/types/components/Status/index.d.ts.map +1 -0
  191. package/dist/types/components/SwitchSelector/SwitchSelector.d.ts +3 -3
  192. package/dist/types/components/SwitchSelector/SwitchSelector.d.ts.map +1 -1
  193. package/dist/types/components/Table/TableElements.d.ts.map +1 -1
  194. package/dist/types/components/TechLogo/TechLogo.d.ts.map +1 -1
  195. package/dist/types/components/TechLogo/logos/HTMX.d.ts +6 -0
  196. package/dist/types/components/TechLogo/logos/HTMX.d.ts.map +1 -0
  197. package/dist/types/components/TechLogo/logos/Remix.d.ts +6 -0
  198. package/dist/types/components/TechLogo/logos/Remix.d.ts.map +1 -0
  199. package/dist/types/components/TechLogo/techLogos.d.ts +4 -2
  200. package/dist/types/components/TechLogo/types.d.ts +1 -1
  201. package/dist/types/components/TextArea/TextArea.d.ts +1 -1
  202. package/dist/types/components/index.d.ts +3 -2
  203. package/dist/types/providers/ThemeProvider.d.ts.map +1 -1
  204. package/dist/types/routes.d.ts +21 -3
  205. package/dist/types/routes.d.ts.map +1 -1
  206. package/dist/types/structured-data/buildItemListJsonLd.d.ts +36 -0
  207. package/dist/types/structured-data/buildItemListJsonLd.d.ts.map +1 -0
  208. package/dist/types/structured-data/index.d.ts +2 -1
  209. package/package.json +68 -54
  210. package/src/components/MarkdownEditor/markdown-editor.css +17 -17
  211. package/styles/globals.css +71 -0
  212. package/dist/esm/components/DictionaryFieldEditor/ContentEditor.mjs.map +0 -1
  213. package/dist/types/components/DictionaryFieldEditor/ContentEditor.d.ts +0 -10
  214. package/dist/types/components/DictionaryFieldEditor/ContentEditor.d.ts.map +0 -1
  215. package/tailwind.css +0 -269
@@ -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, 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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"}
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 <picture>\n {/*\n * YouTube serves the same poster as WebP under `vi_webp`, at roughly\n * a third of the JPEG's weight. The `img` below stays as the\n * fallback for the browsers that cannot decode it.\n */}\n <source\n srcSet={`https://i.ytimg.com/vi_webp/${youtubeVideoId}/hqdefault.webp`}\n type=\"image/webp\"\n />\n <img\n src={`https://i.ytimg.com/vi/${youtubeVideoId}/hqdefault.jpg`}\n alt=\"\"\n width={480}\n height={360}\n fetchPriority=\"high\"\n decoding=\"async\"\n className=\"size-full object-cover\"\n />\n </picture>\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-muted-foreground text-xs underline-offset-2 hover:text-foreground 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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"}
@@ -15,36 +15,36 @@ import { renderMarkdown } from "react-intlayer/markdown";
15
15
  //#region src/components/MarkDownRender/MarkDownRender.tsx
16
16
  const H1Renderer = (props) => /* @__PURE__ */ jsx(H1, {
17
17
  isClickable: true,
18
- className: "mb-16 mb-8 text-text",
18
+ className: "mb-16 mb-8 text-foreground",
19
19
  ...props
20
20
  });
21
21
  const H2Renderer = (props) => /* @__PURE__ */ jsx(H2, {
22
22
  isClickable: true,
23
- className: "mt-16 text-text",
23
+ className: "mt-16 text-foreground",
24
24
  ...props
25
25
  });
26
26
  const H3Renderer = (props) => /* @__PURE__ */ jsx(H3, {
27
27
  isClickable: true,
28
- className: "mt-5 text-text",
28
+ className: "mt-5 text-foreground",
29
29
  ...props
30
30
  });
31
31
  const H4Renderer = (props) => /* @__PURE__ */ jsx(H4, {
32
32
  isClickable: true,
33
- className: "mt-3 text-text",
33
+ className: "mt-3 text-foreground",
34
34
  ...props
35
35
  });
36
36
  const H5Renderer = (props) => /* @__PURE__ */ jsx(H5, {
37
37
  isClickable: true,
38
- className: "mt-3 text-text",
38
+ className: "mt-3 text-foreground",
39
39
  ...props
40
40
  });
41
41
  const H6Renderer = (props) => /* @__PURE__ */ jsx(H6, {
42
42
  isClickable: true,
43
- className: "mt-3 text-text",
43
+ className: "mt-3 text-foreground",
44
44
  ...props
45
45
  });
46
46
  const StrongRenderer = (props) => /* @__PURE__ */ jsx("strong", {
47
- className: "text-text",
47
+ className: "text-foreground",
48
48
  ...props
49
49
  });
50
50
  const MemoizedCodeBlock = memo(({ className, children, highlightedHtml, lang, ...rest }) => {
@@ -62,6 +62,7 @@ const MemoizedCodeBlock = memo(({ className, children, highlightedHtml, lang, ..
62
62
  language,
63
63
  showHeader: true,
64
64
  highlightedHtml,
65
+ className: "border",
65
66
  children: content
66
67
  });
67
68
  }, (prevProps, nextProps) => prevProps.children === nextProps.children && prevProps.className === nextProps.className && prevProps.highlightedHtml === nextProps.highlightedHtml);
@@ -74,7 +75,7 @@ const MemoizedCodeBlock = memo(({ className, children, highlightedHtml, lang, ..
74
75
  */
75
76
  const CodeRenderer = (props) => /* @__PURE__ */ jsx(MemoizedCodeBlock, { ...props });
76
77
  const BlockquoteRenderer = ({ className, ...props }) => /* @__PURE__ */ jsx("blockquote", {
77
- className: cn("mt-5 gap-3 border-card border-l-4 pl-5 text-neutral [&_strong]:text-neutral", className),
78
+ className: cn("mt-5 gap-3 border-card border-l-4 pl-5 text-muted-foreground [&_strong]:text-neutral", className),
78
79
  ...props
79
80
  });
80
81
  const UlRenderer = ({ className, ...props }) => /* @__PURE__ */ jsx("ul", {
@@ -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 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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"}
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-foreground\" {...props} />\n);\nconst H2Renderer = (props: ComponentProps<'h2'>) => (\n <H2 isClickable className=\"mt-16 text-foreground\" {...props} />\n);\nconst H3Renderer = (props: ComponentProps<'h3'>) => (\n <H3 isClickable className=\"mt-5 text-foreground\" {...props} />\n);\nconst H4Renderer = (props: ComponentProps<'h4'>) => (\n <H4 isClickable className=\"mt-3 text-foreground\" {...props} />\n);\nconst H5Renderer = (props: ComponentProps<'h5'>) => (\n <H5 isClickable className=\"mt-3 text-foreground\" {...props} />\n);\nconst H6Renderer = (props: ComponentProps<'h6'>) => (\n <H6 isClickable className=\"mt-3 text-foreground\" {...props} />\n);\nconst StrongRenderer = (props: ComponentProps<'strong'>) => (\n <strong className=\"text-foreground\" {...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 className=\"border\"\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-muted-foreground [&_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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"}
@@ -75,7 +75,7 @@ const MarkdownEditor = ({ defaultValue, onChange, editable = true, enableAI = tr
75
75
  onUpdate: handleUpdate,
76
76
  slotAfter: /* @__PURE__ */ jsx(ImageResizer, {}),
77
77
  children: [/* @__PURE__ */ jsxs(EditorCommand, {
78
- className: "z-50 h-auto max-h-[330px] overflow-y-auto rounded-lg border border-neutral/20 bg-card px-1 py-2 text-text shadow-xl transition-all",
78
+ className: "z-50 h-auto max-h-[330px] overflow-y-auto rounded-lg border bg-card px-1 py-2 text-foreground shadow-xl transition-all",
79
79
  children: [/* @__PURE__ */ jsx(EditorCommandEmpty, {
80
80
  className: "px-2 text-muted-foreground",
81
81
  children: content.noResults.value
@@ -100,7 +100,7 @@ const MarkdownEditor = ({ defaultValue, onChange, editable = true, enableAI = tr
100
100
  children: toolbar
101
101
  }) : /* @__PURE__ */ jsx(EditorBubble, {
102
102
  options: { placement: "top" },
103
- className: "flex w-fit max-w-[90vw] rounded-lg border-[1.3px] border-neutral/20 bg-card/95 text-text shadow-xl backdrop-blur [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-xl",
103
+ className: "flex w-fit max-w-[90vw] rounded-lg border-[1.3px] bg-card/95 text-foreground shadow-xl backdrop-blur [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-xl",
104
104
  children: toolbar
105
105
  })]
106
106
  }) })
@@ -1 +1 @@
1
- {"version":3,"file":"MarkdownEditor.mjs","names":[],"sources":["../../../../src/components/MarkdownEditor/MarkdownEditor.tsx"],"sourcesContent":["'use client';\n\nimport { cn } from '@utils/cn';\nimport { type FC, useMemo, useState } from 'react';\nimport { useIntlayer } from 'react-intlayer';\nimport { defaultExtensions } from './extensions';\nimport { GenerativeMenuSwitch } from './generative/GenerativeMenuSwitch';\nimport {\n type CreateEditorUploadFnOptions,\n createEditorUploadFn,\n} from './imageUpload';\nimport {\n EditorBubble,\n EditorCommand,\n EditorCommandEmpty,\n EditorCommandItem,\n EditorCommandList,\n EditorContent,\n type EditorInstance,\n EditorRoot,\n handleCommandNavigation,\n handleImageDrop,\n handleImagePaste,\n ImageResizer,\n} from './novel';\nimport { ColorSelector } from './selectors/ColorSelector';\nimport { LinkSelector } from './selectors/LinkSelector';\nimport { NodeSelector } from './selectors/NodeSelector';\nimport { TextButtons } from './selectors/TextButtons';\nimport { createSlashCommand } from './slashCommand';\n\nconst Divider = () => <div className=\"h-full w-px shrink-0 bg-border\" />;\n\nexport type MarkdownEditorProps = {\n /** Initial markdown value. */\n defaultValue?: string;\n /** Called with the serialized markdown on every change. */\n onChange?: (markdown: string) => void;\n /** Whether the document can be edited. Defaults to `true`. */\n editable?: boolean;\n /** Enables the AI bubble menu (\"Ask AI\"). Defaults to `true`. */\n enableAI?: boolean;\n /**\n * Custom image uploader. When omitted, images are embedded inline as base64\n * data URLs (no backend required).\n */\n uploadImage?: CreateEditorUploadFnOptions['uploadImage'];\n /** Maximum allowed image size in megabytes. Defaults to `20`. */\n maxImageSizeMb?: number;\n /** Class applied to the editor surface. */\n className?: string;\n};\n\n/**\n * Notion-style WYSIWYG markdown editor built on Novel / Tiptap.\n *\n * Features a slash command menu, a formatting bubble menu (node / link / color\n * / inline marks), image upload (drop / paste / slash), and an optional\n * AI assistant wired to the Intlayer AI backend.\n *\n * The value contract is markdown: pass `defaultValue` as a markdown string and\n * read changes from `onChange`.\n */\nexport const MarkdownEditor: FC<MarkdownEditorProps> = ({\n defaultValue,\n onChange,\n editable = true,\n enableAI = true,\n uploadImage,\n maxImageSizeMb,\n className,\n}) => {\n const [openAI, setOpenAI] = useState(false);\n const content = useIntlayer('markdown-editor');\n\n const uploadFn = useMemo(\n () => createEditorUploadFn({ uploadImage, maxSizeMb: maxImageSizeMb }),\n [uploadImage, maxImageSizeMb]\n );\n\n const { extensions, suggestionItems } = useMemo(() => {\n const { slashCommand, suggestionItems: items } = createSlashCommand(\n uploadFn,\n content\n );\n\n return {\n extensions: [...defaultExtensions, slashCommand],\n suggestionItems: items,\n };\n }, [uploadFn, content]);\n\n const handleUpdate = ({ editor }: { editor: EditorInstance }) => {\n onChange?.(editor.storage.markdown.getMarkdown() as string);\n };\n\n const toolbar = (\n <>\n <NodeSelector />\n <Divider />\n <LinkSelector />\n <Divider />\n <TextButtons />\n <Divider />\n <ColorSelector />\n </>\n );\n\n return (\n <div className=\"relative w-full\">\n <EditorRoot>\n <EditorContent\n immediatelyRender={false}\n editable={editable}\n initialContent={defaultValue ?? ''}\n extensions={extensions}\n className={cn('min-h-[350px] w-full max-w-full', className)}\n editorProps={{\n handleDOMEvents: {\n keydown: (_view, event) => handleCommandNavigation(event),\n },\n handlePaste: (view, event) =>\n handleImagePaste(view, event, uploadFn),\n handleDrop: (view, event, _slice, moved) =>\n handleImageDrop(view, event, moved, uploadFn),\n attributes: {\n // Content typography (headings, lists, spacing) is defined in\n // `markdown-editor.css`, scoped to `.ProseMirror` — this project\n // has no Tailwind `prose` plugin.\n class: 'max-w-full pl-6 focus:outline-none',\n },\n }}\n onUpdate={handleUpdate}\n slotAfter={<ImageResizer />}\n >\n <EditorCommand className=\"z-50 h-auto max-h-[330px] overflow-y-auto rounded-lg border border-neutral/20 bg-card px-1 py-2 text-text shadow-xl transition-all\">\n <EditorCommandEmpty className=\"px-2 text-muted-foreground\">\n {content.noResults.value}\n </EditorCommandEmpty>\n <EditorCommandList>\n {suggestionItems.map((item) => (\n <EditorCommandItem\n value={item.title}\n onCommand={(val) => item.command?.(val)}\n className=\"flex w-full items-center space-x-2 rounded-md px-2 py-1 text-left text-sm hover:bg-accent aria-selected:bg-accent\"\n key={item.title}\n >\n <div className=\"flex size-10 items-center justify-center rounded-md border border-card bg-background\">\n {item.icon}\n </div>\n <div>\n <p className=\"font-medium\">{item.title}</p>\n <p className=\"text-muted-foreground text-xs\">\n {item.description}\n </p>\n </div>\n </EditorCommandItem>\n ))}\n </EditorCommandList>\n </EditorCommand>\n\n {enableAI ? (\n <GenerativeMenuSwitch open={openAI} onOpenChange={setOpenAI}>\n {toolbar}\n </GenerativeMenuSwitch>\n ) : (\n <EditorBubble\n options={{ placement: 'top' }}\n className=\"flex w-fit max-w-[90vw] rounded-lg border-[1.3px] border-neutral/20 bg-card/95 text-text shadow-xl backdrop-blur [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-xl\"\n >\n {toolbar}\n </EditorBubble>\n )}\n </EditorContent>\n </EditorRoot>\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AA+BA,MAAM,gBAAgB,oBAAC,OAAD,EAAK,WAAU,iCAAkC;;;;;;;;;;;AAgCvE,MAAa,kBAA2C,EACtD,cACA,UACA,WAAW,MACX,WAAW,MACX,aACA,gBACA,gBACI;CACJ,MAAM,CAAC,QAAQ,aAAa,SAAS,KAAK;CAC1C,MAAM,UAAU,YAAY,iBAAiB;CAE7C,MAAM,WAAW,cACT,qBAAqB;EAAE;EAAa,WAAW;CAAe,CAAC,GACrE,CAAC,aAAa,cAAc,CAC9B;CAEA,MAAM,EAAE,YAAY,oBAAoB,cAAc;EACpD,MAAM,EAAE,cAAc,iBAAiB,UAAU,mBAC/C,UACA,OACF;EAEA,OAAO;GACL,YAAY,CAAC,GAAG,mBAAmB,YAAY;GAC/C,iBAAiB;EACnB;CACF,GAAG,CAAC,UAAU,OAAO,CAAC;CAEtB,MAAM,gBAAgB,EAAE,aAAyC;EAC/D,WAAW,OAAO,QAAQ,SAAS,YAAY,CAAW;CAC5D;CAEA,MAAM,UACJ;EACE,oBAAC,cAAD,CAAe;EACf,oBAAC,SAAD,CAAU;EACV,oBAAC,cAAD,CAAe;EACf,oBAAC,SAAD,CAAU;EACV,oBAAC,aAAD,CAAc;EACd,oBAAC,SAAD,CAAU;EACV,oBAAC,eAAD,CAAgB;CAChB;CAGJ,OACE,oBAAC,OAAD;EAAK,WAAU;YACb,oBAAC,YAAD,YACE,qBAAC,eAAD;GACE,mBAAmB;GACT;GACV,gBAAgB,gBAAgB;GACpB;GACZ,WAAW,GAAG,mCAAmC,SAAS;GAC1D,aAAa;IACX,iBAAiB,EACf,UAAU,OAAO,UAAU,wBAAwB,KAAK,EAC1D;IACA,cAAc,MAAM,UAClB,iBAAiB,MAAM,OAAO,QAAQ;IACxC,aAAa,MAAM,OAAO,QAAQ,UAChC,gBAAgB,MAAM,OAAO,OAAO,QAAQ;IAC9C,YAAY,EAIV,OAAO,qCACT;GACF;GACA,UAAU;GACV,WAAW,oBAAC,cAAD,CAAe;aAtB5B,CAwBE,qBAAC,eAAD;IAAe,WAAU;cAAzB,CACE,oBAAC,oBAAD;KAAoB,WAAU;eAC3B,QAAQ,UAAU;IACD,IACpB,oBAAC,mBAAD,YACG,gBAAgB,KAAK,SACpB,qBAAC,mBAAD;KACE,OAAO,KAAK;KACZ,YAAY,QAAQ,KAAK,UAAU,GAAG;KACtC,WAAU;eAHZ,CAME,oBAAC,OAAD;MAAK,WAAU;gBACZ,KAAK;KACH,IACL,qBAAC,OAAD,aACE,oBAAC,KAAD;MAAG,WAAU;gBAAe,KAAK;KAAS,IAC1C,oBAAC,KAAD;MAAG,WAAU;gBACV,KAAK;KACL,EACA,IACY;OAXZ,KAAK,KAWO,CACpB,EACgB,EACN;OAEd,WACC,oBAAC,sBAAD;IAAsB,MAAM;IAAQ,cAAc;cAC/C;GACmB,KAEtB,oBAAC,cAAD;IACE,SAAS,EAAE,WAAW,MAAM;IAC5B,WAAU;cAET;GACW,EAEH;KACL;CACT;AAET"}
1
+ {"version":3,"file":"MarkdownEditor.mjs","names":[],"sources":["../../../../src/components/MarkdownEditor/MarkdownEditor.tsx"],"sourcesContent":["'use client';\n\nimport { cn } from '@utils/cn';\nimport { type FC, useMemo, useState } from 'react';\nimport { useIntlayer } from 'react-intlayer';\nimport { defaultExtensions } from './extensions';\nimport { GenerativeMenuSwitch } from './generative/GenerativeMenuSwitch';\nimport {\n type CreateEditorUploadFnOptions,\n createEditorUploadFn,\n} from './imageUpload';\nimport {\n EditorBubble,\n EditorCommand,\n EditorCommandEmpty,\n EditorCommandItem,\n EditorCommandList,\n EditorContent,\n type EditorInstance,\n EditorRoot,\n handleCommandNavigation,\n handleImageDrop,\n handleImagePaste,\n ImageResizer,\n} from './novel';\nimport { ColorSelector } from './selectors/ColorSelector';\nimport { LinkSelector } from './selectors/LinkSelector';\nimport { NodeSelector } from './selectors/NodeSelector';\nimport { TextButtons } from './selectors/TextButtons';\nimport { createSlashCommand } from './slashCommand';\n\nconst Divider = () => <div className=\"h-full w-px shrink-0 bg-border\" />;\n\nexport type MarkdownEditorProps = {\n /** Initial markdown value. */\n defaultValue?: string;\n /** Called with the serialized markdown on every change. */\n onChange?: (markdown: string) => void;\n /** Whether the document can be edited. Defaults to `true`. */\n editable?: boolean;\n /** Enables the AI bubble menu (\"Ask AI\"). Defaults to `true`. */\n enableAI?: boolean;\n /**\n * Custom image uploader. When omitted, images are embedded inline as base64\n * data URLs (no backend required).\n */\n uploadImage?: CreateEditorUploadFnOptions['uploadImage'];\n /** Maximum allowed image size in megabytes. Defaults to `20`. */\n maxImageSizeMb?: number;\n /** Class applied to the editor surface. */\n className?: string;\n};\n\n/**\n * Notion-style WYSIWYG markdown editor built on Novel / Tiptap.\n *\n * Features a slash command menu, a formatting bubble menu (node / link / color\n * / inline marks), image upload (drop / paste / slash), and an optional\n * AI assistant wired to the Intlayer AI backend.\n *\n * The value contract is markdown: pass `defaultValue` as a markdown string and\n * read changes from `onChange`.\n */\nexport const MarkdownEditor: FC<MarkdownEditorProps> = ({\n defaultValue,\n onChange,\n editable = true,\n enableAI = true,\n uploadImage,\n maxImageSizeMb,\n className,\n}) => {\n const [openAI, setOpenAI] = useState(false);\n const content = useIntlayer('markdown-editor');\n\n const uploadFn = useMemo(\n () => createEditorUploadFn({ uploadImage, maxSizeMb: maxImageSizeMb }),\n [uploadImage, maxImageSizeMb]\n );\n\n const { extensions, suggestionItems } = useMemo(() => {\n const { slashCommand, suggestionItems: items } = createSlashCommand(\n uploadFn,\n content\n );\n\n return {\n extensions: [...defaultExtensions, slashCommand],\n suggestionItems: items,\n };\n }, [uploadFn, content]);\n\n const handleUpdate = ({ editor }: { editor: EditorInstance }) => {\n onChange?.(editor.storage.markdown.getMarkdown() as string);\n };\n\n const toolbar = (\n <>\n <NodeSelector />\n <Divider />\n <LinkSelector />\n <Divider />\n <TextButtons />\n <Divider />\n <ColorSelector />\n </>\n );\n\n return (\n <div className=\"relative w-full\">\n <EditorRoot>\n <EditorContent\n immediatelyRender={false}\n editable={editable}\n initialContent={defaultValue ?? ''}\n extensions={extensions}\n className={cn('min-h-[350px] w-full max-w-full', className)}\n editorProps={{\n handleDOMEvents: {\n keydown: (_view, event) => handleCommandNavigation(event),\n },\n handlePaste: (view, event) =>\n handleImagePaste(view, event, uploadFn),\n handleDrop: (view, event, _slice, moved) =>\n handleImageDrop(view, event, moved, uploadFn),\n attributes: {\n // Content typography (headings, lists, spacing) is defined in\n // `markdown-editor.css`, scoped to `.ProseMirror` — this project\n // has no Tailwind `prose` plugin.\n class: 'max-w-full pl-6 focus:outline-none',\n },\n }}\n onUpdate={handleUpdate}\n slotAfter={<ImageResizer />}\n >\n <EditorCommand className=\"z-50 h-auto max-h-[330px] overflow-y-auto rounded-lg border bg-card px-1 py-2 text-foreground shadow-xl transition-all\">\n <EditorCommandEmpty className=\"px-2 text-muted-foreground\">\n {content.noResults.value}\n </EditorCommandEmpty>\n <EditorCommandList>\n {suggestionItems.map((item) => (\n <EditorCommandItem\n value={item.title}\n onCommand={(val) => item.command?.(val)}\n className=\"flex w-full items-center space-x-2 rounded-md px-2 py-1 text-left text-sm hover:bg-accent aria-selected:bg-accent\"\n key={item.title}\n >\n <div className=\"flex size-10 items-center justify-center rounded-md border border-card bg-background\">\n {item.icon}\n </div>\n <div>\n <p className=\"font-medium\">{item.title}</p>\n <p className=\"text-muted-foreground text-xs\">\n {item.description}\n </p>\n </div>\n </EditorCommandItem>\n ))}\n </EditorCommandList>\n </EditorCommand>\n\n {enableAI ? (\n <GenerativeMenuSwitch open={openAI} onOpenChange={setOpenAI}>\n {toolbar}\n </GenerativeMenuSwitch>\n ) : (\n <EditorBubble\n options={{ placement: 'top' }}\n className=\"flex w-fit max-w-[90vw] rounded-lg border-[1.3px] bg-card/95 text-foreground shadow-xl backdrop-blur [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-xl\"\n >\n {toolbar}\n </EditorBubble>\n )}\n </EditorContent>\n </EditorRoot>\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AA+BA,MAAM,gBAAgB,oBAAC,OAAD,EAAK,WAAU,iCAAkC;;;;;;;;;;;AAgCvE,MAAa,kBAA2C,EACtD,cACA,UACA,WAAW,MACX,WAAW,MACX,aACA,gBACA,gBACI;CACJ,MAAM,CAAC,QAAQ,aAAa,SAAS,KAAK;CAC1C,MAAM,UAAU,YAAY,iBAAiB;CAE7C,MAAM,WAAW,cACT,qBAAqB;EAAE;EAAa,WAAW;CAAe,CAAC,GACrE,CAAC,aAAa,cAAc,CAC9B;CAEA,MAAM,EAAE,YAAY,oBAAoB,cAAc;EACpD,MAAM,EAAE,cAAc,iBAAiB,UAAU,mBAC/C,UACA,OACF;EAEA,OAAO;GACL,YAAY,CAAC,GAAG,mBAAmB,YAAY;GAC/C,iBAAiB;EACnB;CACF,GAAG,CAAC,UAAU,OAAO,CAAC;CAEtB,MAAM,gBAAgB,EAAE,aAAyC;EAC/D,WAAW,OAAO,QAAQ,SAAS,YAAY,CAAW;CAC5D;CAEA,MAAM,UACJ;EACE,oBAAC,cAAD,CAAe;EACf,oBAAC,SAAD,CAAU;EACV,oBAAC,cAAD,CAAe;EACf,oBAAC,SAAD,CAAU;EACV,oBAAC,aAAD,CAAc;EACd,oBAAC,SAAD,CAAU;EACV,oBAAC,eAAD,CAAgB;CAChB;CAGJ,OACE,oBAAC,OAAD;EAAK,WAAU;YACb,oBAAC,YAAD,YACE,qBAAC,eAAD;GACE,mBAAmB;GACT;GACV,gBAAgB,gBAAgB;GACpB;GACZ,WAAW,GAAG,mCAAmC,SAAS;GAC1D,aAAa;IACX,iBAAiB,EACf,UAAU,OAAO,UAAU,wBAAwB,KAAK,EAC1D;IACA,cAAc,MAAM,UAClB,iBAAiB,MAAM,OAAO,QAAQ;IACxC,aAAa,MAAM,OAAO,QAAQ,UAChC,gBAAgB,MAAM,OAAO,OAAO,QAAQ;IAC9C,YAAY,EAIV,OAAO,qCACT;GACF;GACA,UAAU;GACV,WAAW,oBAAC,cAAD,CAAe;aAtB5B,CAwBE,qBAAC,eAAD;IAAe,WAAU;cAAzB,CACE,oBAAC,oBAAD;KAAoB,WAAU;eAC3B,QAAQ,UAAU;IACD,IACpB,oBAAC,mBAAD,YACG,gBAAgB,KAAK,SACpB,qBAAC,mBAAD;KACE,OAAO,KAAK;KACZ,YAAY,QAAQ,KAAK,UAAU,GAAG;KACtC,WAAU;eAHZ,CAME,oBAAC,OAAD;MAAK,WAAU;gBACZ,KAAK;KACH,IACL,qBAAC,OAAD,aACE,oBAAC,KAAD;MAAG,WAAU;gBAAe,KAAK;KAAS,IAC1C,oBAAC,KAAD;MAAG,WAAU;gBACV,KAAK;KACL,EACA,IACY;OAXZ,KAAK,KAWO,CACpB,EACgB,EACN;OAEd,WACC,oBAAC,sBAAD;IAAsB,MAAM;IAAQ,cAAc;cAC/C;GACmB,KAEtB,oBAAC,cAAD;IACE,SAAS,EAAE,WAAW,MAAM;IAC5B,WAAU;cAET;GACW,EAEH;KACL;CACT;AAET"}
@@ -1,10 +1,10 @@
1
1
  import CustomKeymap from "./novel/extensions/custom-keymap.mjs";
2
2
  import UpdatedImage from "./novel/extensions/updated-image.mjs";
3
3
  import { AIHighlight } from "./novel/extensions/ai-highlight.mjs";
4
+ import { CodeBlockShiki } from "./novel/extensions/code-block-shiki.mjs";
4
5
  import { CharacterCount, Color, GlobalDragHandle, HighlightExtension, HorizontalRule as Horizontal, MarkdownExtension, Placeholder as PlaceholderExtension, StarterKit, TaskItem, TaskList, TextStyle, TiptapImage, TiptapLink, TiptapUnderline, Youtube } from "./novel/extensions/index.mjs";
5
6
  import { UploadImagesPlugin } from "./novel/plugins/upload-images.mjs";
6
7
  import { cx } from "class-variance-authority";
7
- import { CodeBlockShiki } from "tiptap-extension-code-block-shiki";
8
8
 
9
9
  //#region src/components/MarkdownEditor/extensions.ts
10
10
  const aiHighlight = AIHighlight;
@@ -1 +1 @@
1
- {"version":3,"file":"extensions.mjs","names":["HorizontalRule","Placeholder"],"sources":["../../../../src/components/MarkdownEditor/extensions.ts"],"sourcesContent":["import { cx } from 'class-variance-authority';\nimport { CodeBlockShiki } from 'tiptap-extension-code-block-shiki';\nimport {\n AIHighlight,\n CharacterCount,\n Color,\n CustomKeymap,\n GlobalDragHandle,\n HighlightExtension,\n HorizontalRule,\n MarkdownExtension,\n Placeholder,\n StarterKit,\n TaskItem,\n TaskList,\n TextStyle,\n TiptapImage,\n TiptapLink,\n TiptapUnderline,\n UpdatedImage,\n UploadImagesPlugin,\n Youtube,\n} from './novel';\n\nconst aiHighlight = AIHighlight;\n\nconst tiptapLink = TiptapLink.configure({\n HTMLAttributes: {\n class: cx(\n 'text-muted-foreground underline underline-offset-[3px] hover:text-primary transition-colors cursor-pointer'\n ),\n },\n});\n\nconst tiptapImage = TiptapImage.extend({\n addProseMirrorPlugins() {\n return [\n UploadImagesPlugin({\n imageClass: cx('opacity-40 rounded-lg border border-stone-200'),\n }),\n ];\n },\n}).configure({\n allowBase64: true,\n HTMLAttributes: {\n class: cx('rounded-lg border border-muted'),\n },\n});\n\nconst updatedImage = UpdatedImage.configure({\n HTMLAttributes: {\n class: cx('rounded-lg border border-muted'),\n },\n});\n\nconst taskList = TaskList.configure({\n HTMLAttributes: {\n class: cx('not-prose pl-2'),\n },\n});\n\nconst taskItem = TaskItem.configure({\n HTMLAttributes: {\n class: cx('flex gap-2 items-start my-4'),\n },\n nested: true,\n});\n\nconst horizontalRule = HorizontalRule.configure({\n HTMLAttributes: {\n class: cx('mt-4 mb-6 border-t border-muted-foreground'),\n },\n});\n\nconst starterKit = StarterKit.configure({\n bulletList: {\n HTMLAttributes: {\n class: cx('list-disc list-outside leading-3 -mt-2'),\n },\n },\n orderedList: {\n HTMLAttributes: {\n class: cx('list-decimal list-outside leading-3 -mt-2'),\n },\n },\n listItem: {\n HTMLAttributes: {\n class: cx('leading-normal -mb-2'),\n },\n },\n blockquote: {\n HTMLAttributes: {\n class: cx('border-l-4 border-primary'),\n },\n },\n // Disabled in favor of the shiki-powered `codeBlock` extension below.\n codeBlock: false,\n code: {\n HTMLAttributes: {\n class: cx('rounded-md bg-muted px-1.5 py-1 font-mono font-medium'),\n spellcheck: 'false',\n },\n },\n horizontalRule: false,\n dropcursor: {\n color: '#DBEAFE',\n width: 4,\n },\n gapcursor: false,\n});\n\n/**\n * Code block with syntax highlighting powered by shiki (the same highlighter\n * the rest of the design-system uses in `IDE/CodeBlockShiki` and\n * `MarkDownRender`). Languages are lazy-loaded on demand and the dual\n * `github-light` / `github-dark` themes emit `--shiki-dark` CSS variables that\n * `markdown-editor.css` toggles under `[data-theme=\"dark\"]`.\n */\nconst codeBlock = CodeBlockShiki.configure({\n defaultLanguage: null,\n defaultTheme: 'github-light',\n themes: {\n light: 'github-light',\n dark: 'github-dark',\n },\n HTMLAttributes: {\n class: cx(\n 'rounded-md bg-muted text-muted-foreground border p-5 font-mono font-medium'\n ),\n },\n});\n\nconst youtube = Youtube.configure({\n HTMLAttributes: {\n class: cx('rounded-lg border border-muted'),\n },\n inline: false,\n});\n\nconst characterCount = CharacterCount.configure();\n\nconst markdownExtension = MarkdownExtension.configure({\n html: true,\n tightLists: true,\n tightListClass: 'tight',\n bulletListMarker: '-',\n linkify: false,\n breaks: false,\n transformPastedText: false,\n transformCopiedText: false,\n});\n\n/**\n * Default Tiptap/Novel extensions powering the MarkdownEditor.\n *\n * The slash-command extension is intentionally not included here because it\n * needs a runtime image-upload handler; it is appended by the editor itself.\n */\nexport const defaultExtensions = [\n starterKit,\n Placeholder,\n tiptapLink,\n tiptapImage,\n updatedImage,\n taskList,\n taskItem,\n horizontalRule,\n aiHighlight,\n codeBlock,\n youtube,\n characterCount,\n TiptapUnderline,\n markdownExtension,\n HighlightExtension,\n TextStyle,\n Color,\n CustomKeymap,\n GlobalDragHandle,\n];\n"],"mappings":";;;;;;;;;AAwBA,MAAM,cAAc;AAEpB,MAAM,aAAa,WAAW,UAAU,EACtC,gBAAgB,EACd,OAAO,GACL,4GACF,EACF,EACF,CAAC;AAED,MAAM,cAAc,YAAY,OAAO,EACrC,wBAAwB;CACtB,OAAO,CACL,mBAAmB,EACjB,YAAY,GAAG,+CAA+C,EAChE,CAAC,CACH;AACF,EACF,CAAC,CAAC,CAAC,UAAU;CACX,aAAa;CACb,gBAAgB,EACd,OAAO,GAAG,gCAAgC,EAC5C;AACF,CAAC;AAED,MAAM,eAAe,aAAa,UAAU,EAC1C,gBAAgB,EACd,OAAO,GAAG,gCAAgC,EAC5C,EACF,CAAC;AAED,MAAM,WAAW,SAAS,UAAU,EAClC,gBAAgB,EACd,OAAO,GAAG,gBAAgB,EAC5B,EACF,CAAC;AAED,MAAM,WAAW,SAAS,UAAU;CAClC,gBAAgB,EACd,OAAO,GAAG,6BAA6B,EACzC;CACA,QAAQ;AACV,CAAC;AAED,MAAM,iBAAiBA,WAAe,UAAU,EAC9C,gBAAgB,EACd,OAAO,GAAG,4CAA4C,EACxD,EACF,CAAC;AAED,MAAM,aAAa,WAAW,UAAU;CACtC,YAAY,EACV,gBAAgB,EACd,OAAO,GAAG,wCAAwC,EACpD,EACF;CACA,aAAa,EACX,gBAAgB,EACd,OAAO,GAAG,2CAA2C,EACvD,EACF;CACA,UAAU,EACR,gBAAgB,EACd,OAAO,GAAG,sBAAsB,EAClC,EACF;CACA,YAAY,EACV,gBAAgB,EACd,OAAO,GAAG,2BAA2B,EACvC,EACF;CAEA,WAAW;CACX,MAAM,EACJ,gBAAgB;EACd,OAAO,GAAG,uDAAuD;EACjE,YAAY;CACd,EACF;CACA,gBAAgB;CAChB,YAAY;EACV,OAAO;EACP,OAAO;CACT;CACA,WAAW;AACb,CAAC;;;;;;;;AASD,MAAM,YAAY,eAAe,UAAU;CACzC,iBAAiB;CACjB,cAAc;CACd,QAAQ;EACN,OAAO;EACP,MAAM;CACR;CACA,gBAAgB,EACd,OAAO,GACL,4EACF,EACF;AACF,CAAC;AAED,MAAM,UAAU,QAAQ,UAAU;CAChC,gBAAgB,EACd,OAAO,GAAG,gCAAgC,EAC5C;CACA,QAAQ;AACV,CAAC;AAED,MAAM,iBAAiB,eAAe,UAAU;AAEhD,MAAM,oBAAoB,kBAAkB,UAAU;CACpD,MAAM;CACN,YAAY;CACZ,gBAAgB;CAChB,kBAAkB;CAClB,SAAS;CACT,QAAQ;CACR,qBAAqB;CACrB,qBAAqB;AACvB,CAAC;;;;;;;AAQD,MAAa,oBAAoB;CAC/B;CACAC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF"}
1
+ {"version":3,"file":"extensions.mjs","names":["HorizontalRule","Placeholder"],"sources":["../../../../src/components/MarkdownEditor/extensions.ts"],"sourcesContent":["import { cx } from 'class-variance-authority';\nimport {\n AIHighlight,\n CharacterCount,\n CodeBlockShiki,\n Color,\n CustomKeymap,\n GlobalDragHandle,\n HighlightExtension,\n HorizontalRule,\n MarkdownExtension,\n Placeholder,\n StarterKit,\n TaskItem,\n TaskList,\n TextStyle,\n TiptapImage,\n TiptapLink,\n TiptapUnderline,\n UpdatedImage,\n UploadImagesPlugin,\n Youtube,\n} from './novel';\n\nconst aiHighlight = AIHighlight;\n\nconst tiptapLink = TiptapLink.configure({\n HTMLAttributes: {\n class: cx(\n 'text-muted-foreground underline underline-offset-[3px] hover:text-primary transition-colors cursor-pointer'\n ),\n },\n});\n\nconst tiptapImage = TiptapImage.extend({\n addProseMirrorPlugins() {\n return [\n UploadImagesPlugin({\n imageClass: cx('opacity-40 rounded-lg border border-stone-200'),\n }),\n ];\n },\n}).configure({\n allowBase64: true,\n HTMLAttributes: {\n class: cx('rounded-lg border border-muted'),\n },\n});\n\nconst updatedImage = UpdatedImage.configure({\n HTMLAttributes: {\n class: cx('rounded-lg border border-muted'),\n },\n});\n\nconst taskList = TaskList.configure({\n HTMLAttributes: {\n class: cx('not-prose pl-2'),\n },\n});\n\nconst taskItem = TaskItem.configure({\n HTMLAttributes: {\n class: cx('flex gap-2 items-start my-4'),\n },\n nested: true,\n});\n\nconst horizontalRule = HorizontalRule.configure({\n HTMLAttributes: {\n class: cx('mt-4 mb-6 border-t border-muted-foreground'),\n },\n});\n\nconst starterKit = StarterKit.configure({\n bulletList: {\n HTMLAttributes: {\n class: cx('list-disc list-outside leading-3 -mt-2'),\n },\n },\n orderedList: {\n HTMLAttributes: {\n class: cx('list-decimal list-outside leading-3 -mt-2'),\n },\n },\n listItem: {\n HTMLAttributes: {\n class: cx('leading-normal -mb-2'),\n },\n },\n blockquote: {\n HTMLAttributes: {\n class: cx('border-l-4 border-primary'),\n },\n },\n // Disabled in favor of the shiki-powered `codeBlock` extension below.\n codeBlock: false,\n code: {\n HTMLAttributes: {\n class: cx('rounded-md bg-muted px-1.5 py-1 font-mono font-medium'),\n spellcheck: 'false',\n },\n },\n horizontalRule: false,\n dropcursor: {\n color: '#DBEAFE',\n width: 4,\n },\n gapcursor: false,\n});\n\n/**\n * Code block with syntax highlighting powered by shiki (the same highlighter\n * the rest of the design-system uses in `IDE/CodeBlockShiki` and\n * `MarkDownRender`). Languages are lazy-loaded on demand and the dual\n * `github-light` / `github-dark` themes emit `--shiki-dark` CSS variables that\n * `markdown-editor.css` toggles under `[data-theme=\"dark\"]`.\n */\nconst codeBlock = CodeBlockShiki.configure({\n defaultLanguage: null,\n defaultTheme: 'github-light',\n themes: {\n light: 'github-light',\n dark: 'github-dark',\n },\n HTMLAttributes: {\n class: cx(\n 'rounded-md bg-muted text-muted-foreground border p-5 font-mono font-medium'\n ),\n },\n});\n\nconst youtube = Youtube.configure({\n HTMLAttributes: {\n class: cx('rounded-lg border border-muted'),\n },\n inline: false,\n});\n\nconst characterCount = CharacterCount.configure();\n\nconst markdownExtension = MarkdownExtension.configure({\n html: true,\n tightLists: true,\n tightListClass: 'tight',\n bulletListMarker: '-',\n linkify: false,\n breaks: false,\n transformPastedText: false,\n transformCopiedText: false,\n});\n\n/**\n * Default Tiptap/Novel extensions powering the MarkdownEditor.\n *\n * The slash-command extension is intentionally not included here because it\n * needs a runtime image-upload handler; it is appended by the editor itself.\n */\nexport const defaultExtensions = [\n starterKit,\n Placeholder,\n tiptapLink,\n tiptapImage,\n updatedImage,\n taskList,\n taskItem,\n horizontalRule,\n aiHighlight,\n codeBlock,\n youtube,\n characterCount,\n TiptapUnderline,\n markdownExtension,\n HighlightExtension,\n TextStyle,\n Color,\n CustomKeymap,\n GlobalDragHandle,\n];\n"],"mappings":";;;;;;;;;AAwBA,MAAM,cAAc;AAEpB,MAAM,aAAa,WAAW,UAAU,EACtC,gBAAgB,EACd,OAAO,GACL,4GACF,EACF,EACF,CAAC;AAED,MAAM,cAAc,YAAY,OAAO,EACrC,wBAAwB;CACtB,OAAO,CACL,mBAAmB,EACjB,YAAY,GAAG,+CAA+C,EAChE,CAAC,CACH;AACF,EACF,CAAC,CAAC,CAAC,UAAU;CACX,aAAa;CACb,gBAAgB,EACd,OAAO,GAAG,gCAAgC,EAC5C;AACF,CAAC;AAED,MAAM,eAAe,aAAa,UAAU,EAC1C,gBAAgB,EACd,OAAO,GAAG,gCAAgC,EAC5C,EACF,CAAC;AAED,MAAM,WAAW,SAAS,UAAU,EAClC,gBAAgB,EACd,OAAO,GAAG,gBAAgB,EAC5B,EACF,CAAC;AAED,MAAM,WAAW,SAAS,UAAU;CAClC,gBAAgB,EACd,OAAO,GAAG,6BAA6B,EACzC;CACA,QAAQ;AACV,CAAC;AAED,MAAM,iBAAiBA,WAAe,UAAU,EAC9C,gBAAgB,EACd,OAAO,GAAG,4CAA4C,EACxD,EACF,CAAC;AAED,MAAM,aAAa,WAAW,UAAU;CACtC,YAAY,EACV,gBAAgB,EACd,OAAO,GAAG,wCAAwC,EACpD,EACF;CACA,aAAa,EACX,gBAAgB,EACd,OAAO,GAAG,2CAA2C,EACvD,EACF;CACA,UAAU,EACR,gBAAgB,EACd,OAAO,GAAG,sBAAsB,EAClC,EACF;CACA,YAAY,EACV,gBAAgB,EACd,OAAO,GAAG,2BAA2B,EACvC,EACF;CAEA,WAAW;CACX,MAAM,EACJ,gBAAgB;EACd,OAAO,GAAG,uDAAuD;EACjE,YAAY;CACd,EACF;CACA,gBAAgB;CAChB,YAAY;EACV,OAAO;EACP,OAAO;CACT;CACA,WAAW;AACb,CAAC;;;;;;;;AASD,MAAM,YAAY,eAAe,UAAU;CACzC,iBAAiB;CACjB,cAAc;CACd,QAAQ;EACN,OAAO;EACP,MAAM;CACR;CACA,gBAAgB,EACd,OAAO,GACL,4EACF,EACF;AACF,CAAC;AAED,MAAM,UAAU,QAAQ,UAAU;CAChC,gBAAgB,EACd,OAAO,GAAG,gCAAgC,EAC5C;CACA,QAAQ;AACV,CAAC;AAED,MAAM,iBAAiB,eAAe,UAAU;AAEhD,MAAM,oBAAoB,kBAAkB,UAAU;CACpD,MAAM;CACN,YAAY;CACZ,gBAAgB;CAChB,kBAAkB;CAClB,SAAS;CACT,QAAQ;CACR,qBAAqB;CACrB,qBAAqB;AACvB,CAAC;;;;;;;AAQD,MAAa,oBAAoB;CAC/B;CACAC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF"}
@@ -4,7 +4,7 @@ import { toast } from "../../Toaster/useToast.mjs";
4
4
  import { Loader } from "../../Loader/index.mjs";
5
5
  import { Button } from "../../Button/Button.mjs";
6
6
  import { Command, CommandRoot } from "../../Command/index.mjs";
7
- import { MarkdownRenderer as MarkdownRenderer$1 } from "../../MarkDownRender/MarkDownRender.mjs";
7
+ import { MarkdownRenderer } from "../../MarkDownRender/MarkDownRender.mjs";
8
8
  import { useEditor } from "../novel/components/index.mjs";
9
9
  import { addAIHighlight } from "../novel/extensions/ai-highlight.mjs";
10
10
  import { AICompletionCommands } from "./AICompletionCommands.mjs";
@@ -45,12 +45,12 @@ const AISelector = ({ onOpenChange, sendButtonVariant = "default", sendButtonCol
45
45
  className: "relative overflow-y-auto",
46
46
  children: /* @__PURE__ */ jsx("div", {
47
47
  className: "prose prose-sm dark:prose-invert p-2 px-4",
48
- children: /* @__PURE__ */ jsx(MarkdownRenderer$1, { children: completion })
48
+ children: /* @__PURE__ */ jsx(MarkdownRenderer, { children: completion })
49
49
  })
50
50
  })
51
51
  }),
52
52
  isLoading && /* @__PURE__ */ jsxs("div", {
53
- className: "flex h-12 w-full items-center px-4 font-medium text-sm text-text",
53
+ className: "flex h-12 w-full items-center px-4 font-medium text-foreground text-sm",
54
54
  children: [
55
55
  /* @__PURE__ */ jsx(Sparkles, { className: "mr-2 size-4 shrink-0" }),
56
56
  content.aiThinking,
@@ -1 +1 @@
1
- {"version":3,"file":"AISelector.mjs","names":["MarkdownRenderer"],"sources":["../../../../../src/components/MarkdownEditor/generative/AISelector.tsx"],"sourcesContent":["'use client';\n\nimport {\n Button,\n type ButtonColor,\n type ButtonVariant,\n} from '@components/Button';\nimport { Command, CommandRoot } from '@components/Command';\nimport { Loader } from '@components/Loader';\nimport { MarkdownRenderer } from '@components/MarkDownRender';\nimport { toast } from '@components/Toaster';\nimport { ArrowUp, Sparkles } from 'lucide-react';\nimport { useState } from 'react';\nimport { useIntlayer } from 'react-intlayer';\nimport { addAIHighlight, useEditor } from '../novel';\nimport { AICompletionCommands } from './AICompletionCommands';\nimport { AISelectorCommands } from './AISelectorCommands';\nimport { useAICompletion } from './useAICompletion';\n\nexport type AISelectorProps = {\n onOpenChange: (open: boolean) => void;\n /** Visual variant for the send button. @default \"default\" */\n sendButtonVariant?: ButtonVariant;\n /** Color theme for the send button. @default \"text\" */\n sendButtonColor?: ButtonColor;\n};\n\n/**\n * The AI command palette shown inside the bubble menu. Lets the user run a\n * preset transformation or a free-form prompt against the current selection,\n * preview the markdown result, then replace / insert / discard it.\n */\nexport const AISelector = ({\n onOpenChange,\n sendButtonVariant = 'default',\n sendButtonColor = 'text',\n}: AISelectorProps) => {\n const { editor } = useEditor();\n const [inputValue, setInputValue] = useState('');\n const content = useIntlayer('markdown-editor');\n\n const { completion, complete, isLoading, reset } = useAICompletion((error) =>\n toast({\n variant: 'error',\n title: content.aiError.value,\n description: error.message,\n })\n );\n\n if (!editor) return null;\n\n const hasCompletion = completion.length > 0;\n\n const getSelectionMarkdown = () => {\n const slice = editor.state.selection.content();\n return editor.storage.markdown.serializer.serialize(\n slice.content\n ) as string;\n };\n\n return (\n <CommandRoot className=\"w-[350px]\">\n {hasCompletion && (\n <div className=\"flex max-h-[400px]\">\n <div className=\"relative overflow-y-auto\">\n <div className=\"prose prose-sm dark:prose-invert p-2 px-4\">\n <MarkdownRenderer>{completion}</MarkdownRenderer>\n </div>\n </div>\n </div>\n )}\n\n {isLoading && (\n <div className=\"flex h-12 w-full items-center px-4 font-medium text-sm text-text\">\n <Sparkles className=\"mr-2 size-4 shrink-0\" />\n {content.aiThinking}\n <div className=\"mt-1 ml-2\">\n <Loader />\n </div>\n </div>\n )}\n\n {!isLoading && (\n <>\n <div className=\"flex items-center gap-2 px-3 py-3\">\n <Command.Input\n value={inputValue}\n onValueChange={setInputValue}\n autoFocus\n placeholder={\n hasCompletion ? content.tellAI.value : content.askAIToEdit.value\n }\n onFocus={() => addAIHighlight(editor)}\n />\n <Button\n label={content.send.value}\n size=\"icon-sm\"\n roundedSize=\"full\"\n variant={sendButtonVariant}\n color={sendButtonColor}\n className=\"size-7 shrink-0\"\n Icon={ArrowUp}\n onClick={() => {\n const text = completion || getSelectionMarkdown();\n complete(text, {\n body: { option: 'zap', command: inputValue },\n }).then(() => setInputValue(''));\n }}\n />\n </div>\n {hasCompletion ? (\n <AICompletionCommands\n completion={completion}\n onDiscard={() => {\n editor.chain().unsetHighlight().focus().run();\n reset();\n onOpenChange(false);\n }}\n />\n ) : (\n <AISelectorCommands\n onSelect={(value, option) =>\n complete(value, { body: { option } })\n }\n />\n )}\n </>\n )}\n </CommandRoot>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAgCA,MAAa,cAAc,EACzB,cACA,oBAAoB,WACpB,kBAAkB,aACG;CACrB,MAAM,EAAE,WAAW,UAAU;CAC7B,MAAM,CAAC,YAAY,iBAAiB,SAAS,EAAE;CAC/C,MAAM,UAAU,YAAY,iBAAiB;CAE7C,MAAM,EAAE,YAAY,UAAU,WAAW,UAAU,iBAAiB,UAClE,MAAM;EACJ,SAAS;EACT,OAAO,QAAQ,QAAQ;EACvB,aAAa,MAAM;CACrB,CAAC,CACH;CAEA,IAAI,CAAC,QAAQ,OAAO;CAEpB,MAAM,gBAAgB,WAAW,SAAS;CAE1C,MAAM,6BAA6B;EACjC,MAAM,QAAQ,OAAO,MAAM,UAAU,QAAQ;EAC7C,OAAO,OAAO,QAAQ,SAAS,WAAW,UACxC,MAAM,OACR;CACF;CAEA,OACE,qBAAC,aAAD;EAAa,WAAU;YAAvB;GACG,iBACC,oBAAC,OAAD;IAAK,WAAU;cACb,oBAAC,OAAD;KAAK,WAAU;eACb,oBAAC,OAAD;MAAK,WAAU;gBACb,oBAACA,oBAAD,YAAmB,WAA6B;KAC7C;IACF;GACF;GAGN,aACC,qBAAC,OAAD;IAAK,WAAU;cAAf;KACE,oBAAC,UAAD,EAAU,WAAU,uBAAwB;KAC3C,QAAQ;KACT,oBAAC,OAAD;MAAK,WAAU;gBACb,oBAAC,QAAD,CAAS;KACN;IACF;;GAGN,CAAC,aACA,8CACE,qBAAC,OAAD;IAAK,WAAU;cAAf,CACE,oBAAC,QAAQ,OAAT;KACE,OAAO;KACP,eAAe;KACf;KACA,aACE,gBAAgB,QAAQ,OAAO,QAAQ,QAAQ,YAAY;KAE7D,eAAe,eAAe,MAAM;IACrC,IACD,oBAAC,QAAD;KACE,OAAO,QAAQ,KAAK;KACpB,MAAK;KACL,aAAY;KACZ,SAAS;KACT,OAAO;KACP,WAAU;KACV,MAAM;KACN,eAAe;MACb,MAAM,OAAO,cAAc,qBAAqB;MAChD,SAAS,MAAM,EACb,MAAM;OAAE,QAAQ;OAAO,SAAS;MAAW,EAC7C,CAAC,CAAC,CAAC,WAAW,cAAc,EAAE,CAAC;KACjC;IACD,EACE;OACJ,gBACC,oBAAC,sBAAD;IACc;IACZ,iBAAiB;KACf,OAAO,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI;KAC5C,MAAM;KACN,aAAa,KAAK;IACpB;GACD,KAED,oBAAC,oBAAD,EACE,WAAW,OAAO,WAChB,SAAS,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,EAEvC,EAEH;EAEO;;AAEjB"}
1
+ {"version":3,"file":"AISelector.mjs","names":[],"sources":["../../../../../src/components/MarkdownEditor/generative/AISelector.tsx"],"sourcesContent":["'use client';\n\nimport {\n Button,\n type ButtonColor,\n type ButtonVariant,\n} from '@components/Button';\nimport { Command, CommandRoot } from '@components/Command';\nimport { Loader } from '@components/Loader';\nimport { MarkdownRenderer } from '@components/MarkDownRender';\nimport { toast } from '@components/Toaster';\nimport { ArrowUp, Sparkles } from 'lucide-react';\nimport { useState } from 'react';\nimport { useIntlayer } from 'react-intlayer';\nimport { addAIHighlight, useEditor } from '../novel';\nimport { AICompletionCommands } from './AICompletionCommands';\nimport { AISelectorCommands } from './AISelectorCommands';\nimport { useAICompletion } from './useAICompletion';\n\nexport type AISelectorProps = {\n onOpenChange: (open: boolean) => void;\n /** Visual variant for the send button. @default \"default\" */\n sendButtonVariant?: ButtonVariant;\n /** Color theme for the send button. @default \"text\" */\n sendButtonColor?: ButtonColor;\n};\n\n/**\n * The AI command palette shown inside the bubble menu. Lets the user run a\n * preset transformation or a free-form prompt against the current selection,\n * preview the markdown result, then replace / insert / discard it.\n */\nexport const AISelector = ({\n onOpenChange,\n sendButtonVariant = 'default',\n sendButtonColor = 'text',\n}: AISelectorProps) => {\n const { editor } = useEditor();\n const [inputValue, setInputValue] = useState('');\n const content = useIntlayer('markdown-editor');\n\n const { completion, complete, isLoading, reset } = useAICompletion((error) =>\n toast({\n variant: 'error',\n title: content.aiError.value,\n description: error.message,\n })\n );\n\n if (!editor) return null;\n\n const hasCompletion = completion.length > 0;\n\n const getSelectionMarkdown = () => {\n const slice = editor.state.selection.content();\n return editor.storage.markdown.serializer.serialize(\n slice.content\n ) as string;\n };\n\n return (\n <CommandRoot className=\"w-[350px]\">\n {hasCompletion && (\n <div className=\"flex max-h-[400px]\">\n <div className=\"relative overflow-y-auto\">\n <div className=\"prose prose-sm dark:prose-invert p-2 px-4\">\n <MarkdownRenderer>{completion}</MarkdownRenderer>\n </div>\n </div>\n </div>\n )}\n\n {isLoading && (\n <div className=\"flex h-12 w-full items-center px-4 font-medium text-foreground text-sm\">\n <Sparkles className=\"mr-2 size-4 shrink-0\" />\n {content.aiThinking}\n <div className=\"mt-1 ml-2\">\n <Loader />\n </div>\n </div>\n )}\n\n {!isLoading && (\n <>\n <div className=\"flex items-center gap-2 px-3 py-3\">\n <Command.Input\n value={inputValue}\n onValueChange={setInputValue}\n autoFocus\n placeholder={\n hasCompletion ? content.tellAI.value : content.askAIToEdit.value\n }\n onFocus={() => addAIHighlight(editor)}\n />\n <Button\n label={content.send.value}\n size=\"icon-sm\"\n roundedSize=\"full\"\n variant={sendButtonVariant}\n color={sendButtonColor}\n className=\"size-7 shrink-0\"\n Icon={ArrowUp}\n onClick={() => {\n const text = completion || getSelectionMarkdown();\n complete(text, {\n body: { option: 'zap', command: inputValue },\n }).then(() => setInputValue(''));\n }}\n />\n </div>\n {hasCompletion ? (\n <AICompletionCommands\n completion={completion}\n onDiscard={() => {\n editor.chain().unsetHighlight().focus().run();\n reset();\n onOpenChange(false);\n }}\n />\n ) : (\n <AISelectorCommands\n onSelect={(value, option) =>\n complete(value, { body: { option } })\n }\n />\n )}\n </>\n )}\n </CommandRoot>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAgCA,MAAa,cAAc,EACzB,cACA,oBAAoB,WACpB,kBAAkB,aACG;CACrB,MAAM,EAAE,WAAW,UAAU;CAC7B,MAAM,CAAC,YAAY,iBAAiB,SAAS,EAAE;CAC/C,MAAM,UAAU,YAAY,iBAAiB;CAE7C,MAAM,EAAE,YAAY,UAAU,WAAW,UAAU,iBAAiB,UAClE,MAAM;EACJ,SAAS;EACT,OAAO,QAAQ,QAAQ;EACvB,aAAa,MAAM;CACrB,CAAC,CACH;CAEA,IAAI,CAAC,QAAQ,OAAO;CAEpB,MAAM,gBAAgB,WAAW,SAAS;CAE1C,MAAM,6BAA6B;EACjC,MAAM,QAAQ,OAAO,MAAM,UAAU,QAAQ;EAC7C,OAAO,OAAO,QAAQ,SAAS,WAAW,UACxC,MAAM,OACR;CACF;CAEA,OACE,qBAAC,aAAD;EAAa,WAAU;YAAvB;GACG,iBACC,oBAAC,OAAD;IAAK,WAAU;cACb,oBAAC,OAAD;KAAK,WAAU;eACb,oBAAC,OAAD;MAAK,WAAU;gBACb,oBAAC,kBAAD,YAAmB,WAA6B;KAC7C;IACF;GACF;GAGN,aACC,qBAAC,OAAD;IAAK,WAAU;cAAf;KACE,oBAAC,UAAD,EAAU,WAAU,uBAAwB;KAC3C,QAAQ;KACT,oBAAC,OAAD;MAAK,WAAU;gBACb,oBAAC,QAAD,CAAS;KACN;IACF;;GAGN,CAAC,aACA,8CACE,qBAAC,OAAD;IAAK,WAAU;cAAf,CACE,oBAAC,QAAQ,OAAT;KACE,OAAO;KACP,eAAe;KACf;KACA,aACE,gBAAgB,QAAQ,OAAO,QAAQ,QAAQ,YAAY;KAE7D,eAAe,eAAe,MAAM;IACrC,IACD,oBAAC,QAAD;KACE,OAAO,QAAQ,KAAK;KACpB,MAAK;KACL,aAAY;KACZ,SAAS;KACT,OAAO;KACP,WAAU;KACV,MAAM;KACN,eAAe;MACb,MAAM,OAAO,cAAc,qBAAqB;MAChD,SAAS,MAAM,EACb,MAAM;OAAE,QAAQ;OAAO,SAAS;MAAW,EAC7C,CAAC,CAAC,CAAC,WAAW,cAAc,EAAE,CAAC;KACjC;IACD,EACE;OACJ,gBACC,oBAAC,sBAAD;IACc;IACZ,iBAAiB;KACf,OAAO,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI;KAC5C,MAAM;KACN,aAAa,KAAK;IACpB;GACD,KAED,oBAAC,oBAAD,EACE,WAAW,OAAO,WAChB,SAAS,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,EAEvC,EAEH;EAEO;;AAEjB"}
@@ -57,7 +57,7 @@ const AISelectorCommands = ({ onSelect }) => {
57
57
  const slice = editor.state.selection.content();
58
58
  onSelect(editor.storage.markdown.serializer.serialize(slice.content), value);
59
59
  },
60
- children: [/* @__PURE__ */ jsx(option.icon, { className: "size-4 text-text" }), getOptionLabel(option.value)]
60
+ children: [/* @__PURE__ */ jsx(option.icon, { className: "size-4 text-foreground" }), getOptionLabel(option.value)]
61
61
  }, option.value))
62
62
  }),
63
63
  /* @__PURE__ */ jsx(Command.Separator, {}),
@@ -70,7 +70,7 @@ const AISelectorCommands = ({ onSelect }) => {
70
70
  const pos = editor.state.selection.from;
71
71
  onSelect(getPrevText(editor, pos), "continue");
72
72
  },
73
- children: [/* @__PURE__ */ jsx(StepForward, { className: "size-4 text-text" }), content.continueWriting]
73
+ children: [/* @__PURE__ */ jsx(StepForward, { className: "size-4 text-foreground" }), content.continueWriting]
74
74
  })
75
75
  })
76
76
  ] });
@@ -1 +1 @@
1
- {"version":3,"file":"AISelectorCommands.mjs","names":[],"sources":["../../../../../src/components/MarkdownEditor/generative/AISelectorCommands.tsx"],"sourcesContent":["'use client';\n\nimport { Command } from '@components/Command';\nimport {\n ArrowDownWideNarrow,\n CheckCheck,\n RefreshCcwDot,\n StepForward,\n WrapText,\n} from 'lucide-react';\nimport { useIntlayer } from 'react-intlayer';\nimport { getPrevText, useEditor } from '../novel';\nimport type { AICompletionOption } from './useAICompletion';\n\nconst options: {\n value: Exclude<AICompletionOption, 'zap'>;\n label: string;\n icon: typeof RefreshCcwDot;\n}[] = [\n { value: 'improve', label: 'Improve writing', icon: RefreshCcwDot },\n { value: 'fix', label: 'Fix grammar', icon: CheckCheck },\n { value: 'shorter', label: 'Make shorter', icon: ArrowDownWideNarrow },\n { value: 'longer', label: 'Make longer', icon: WrapText },\n];\n\nexport type AISelectorCommandsProps = {\n onSelect: (value: string, option: AICompletionOption) => void;\n};\n\n/**\n * The default command list shown in the AI bubble before a completion exists:\n * transforms applied to the current selection plus \"continue writing\".\n */\nexport const AISelectorCommands = ({ onSelect }: AISelectorCommandsProps) => {\n const { editor } = useEditor();\n const content = useIntlayer('markdown-editor');\n\n if (!editor) return null;\n\n const getOptionLabel = (value: string) => {\n switch (value) {\n case 'improve':\n return content.improveWriting.value;\n case 'fix':\n return content.fixGrammar.value;\n case 'shorter':\n return content.makeShorter.value;\n case 'longer':\n return content.makeLonger.value;\n default:\n return value;\n }\n };\n\n return (\n <>\n <Command.Group heading={content.editHeading.value}>\n {options.map((option) => (\n <Command.Item\n key={option.value}\n value={option.value}\n className=\"flex gap-2 px-4\"\n onSelect={(value) => {\n const slice = editor.state.selection.content();\n const text = editor.storage.markdown.serializer.serialize(\n slice.content\n );\n onSelect(text, value as AICompletionOption);\n }}\n >\n <option.icon className=\"size-4 text-text\" />\n {getOptionLabel(option.value)}\n </Command.Item>\n ))}\n </Command.Group>\n <Command.Separator />\n <Command.Group heading={content.moreHeading.value}>\n <Command.Item\n value=\"continue\"\n className=\"gap-2 px-4\"\n onSelect={() => {\n const pos = editor.state.selection.from;\n const text = getPrevText(editor, pos);\n onSelect(text, 'continue');\n }}\n >\n <StepForward className=\"size-4 text-text\" />\n {content.continueWriting}\n </Command.Item>\n </Command.Group>\n </>\n );\n};\n"],"mappings":";;;;;;;;;;AAcA,MAAM,UAIA;CACJ;EAAE,OAAO;EAAW,OAAO;EAAmB,MAAM;CAAc;CAClE;EAAE,OAAO;EAAO,OAAO;EAAe,MAAM;CAAW;CACvD;EAAE,OAAO;EAAW,OAAO;EAAgB,MAAM;CAAoB;CACrE;EAAE,OAAO;EAAU,OAAO;EAAe,MAAM;CAAS;AAC1D;;;;;AAUA,MAAa,sBAAsB,EAAE,eAAwC;CAC3E,MAAM,EAAE,WAAW,UAAU;CAC7B,MAAM,UAAU,YAAY,iBAAiB;CAE7C,IAAI,CAAC,QAAQ,OAAO;CAEpB,MAAM,kBAAkB,UAAkB;EACxC,QAAQ,OAAR;GACE,KAAK,WACH,OAAO,QAAQ,eAAe;GAChC,KAAK,OACH,OAAO,QAAQ,WAAW;GAC5B,KAAK,WACH,OAAO,QAAQ,YAAY;GAC7B,KAAK,UACH,OAAO,QAAQ,WAAW;GAC5B,SACE,OAAO;EACX;CACF;CAEA,OACE;EACE,oBAAC,QAAQ,OAAT;GAAe,SAAS,QAAQ,YAAY;aACzC,QAAQ,KAAK,WACZ,qBAAC,QAAQ,MAAT;IAEE,OAAO,OAAO;IACd,WAAU;IACV,WAAW,UAAU;KACnB,MAAM,QAAQ,OAAO,MAAM,UAAU,QAAQ;KAI7C,SAHa,OAAO,QAAQ,SAAS,WAAW,UAC9C,MAAM,OAEI,GAAG,KAA2B;IAC5C;cAVF,CAYE,oBAAC,OAAO,MAAR,EAAa,WAAU,mBAAoB,IAC1C,eAAe,OAAO,KAAK,CAChB;MAbP,OAAO,KAaA,CACf;EACY;EACf,oBAAC,QAAQ,WAAT,CAAoB;EACpB,oBAAC,QAAQ,OAAT;GAAe,SAAS,QAAQ,YAAY;aAC1C,qBAAC,QAAQ,MAAT;IACE,OAAM;IACN,WAAU;IACV,gBAAgB;KACd,MAAM,MAAM,OAAO,MAAM,UAAU;KAEnC,SADa,YAAY,QAAQ,GACrB,GAAG,UAAU;IAC3B;cAPF,CASE,oBAAC,aAAD,EAAa,WAAU,mBAAoB,IAC1C,QAAQ,eACG;;EACD;CACf;AAEN"}
1
+ {"version":3,"file":"AISelectorCommands.mjs","names":[],"sources":["../../../../../src/components/MarkdownEditor/generative/AISelectorCommands.tsx"],"sourcesContent":["'use client';\n\nimport { Command } from '@components/Command';\nimport {\n ArrowDownWideNarrow,\n CheckCheck,\n RefreshCcwDot,\n StepForward,\n WrapText,\n} from 'lucide-react';\nimport { useIntlayer } from 'react-intlayer';\nimport { getPrevText, useEditor } from '../novel';\nimport type { AICompletionOption } from './useAICompletion';\n\nconst options: {\n value: Exclude<AICompletionOption, 'zap'>;\n label: string;\n icon: typeof RefreshCcwDot;\n}[] = [\n { value: 'improve', label: 'Improve writing', icon: RefreshCcwDot },\n { value: 'fix', label: 'Fix grammar', icon: CheckCheck },\n { value: 'shorter', label: 'Make shorter', icon: ArrowDownWideNarrow },\n { value: 'longer', label: 'Make longer', icon: WrapText },\n];\n\nexport type AISelectorCommandsProps = {\n onSelect: (value: string, option: AICompletionOption) => void;\n};\n\n/**\n * The default command list shown in the AI bubble before a completion exists:\n * transforms applied to the current selection plus \"continue writing\".\n */\nexport const AISelectorCommands = ({ onSelect }: AISelectorCommandsProps) => {\n const { editor } = useEditor();\n const content = useIntlayer('markdown-editor');\n\n if (!editor) return null;\n\n const getOptionLabel = (value: string) => {\n switch (value) {\n case 'improve':\n return content.improveWriting.value;\n case 'fix':\n return content.fixGrammar.value;\n case 'shorter':\n return content.makeShorter.value;\n case 'longer':\n return content.makeLonger.value;\n default:\n return value;\n }\n };\n\n return (\n <>\n <Command.Group heading={content.editHeading.value}>\n {options.map((option) => (\n <Command.Item\n key={option.value}\n value={option.value}\n className=\"flex gap-2 px-4\"\n onSelect={(value) => {\n const slice = editor.state.selection.content();\n const text = editor.storage.markdown.serializer.serialize(\n slice.content\n );\n onSelect(text, value as AICompletionOption);\n }}\n >\n <option.icon className=\"size-4 text-foreground\" />\n {getOptionLabel(option.value)}\n </Command.Item>\n ))}\n </Command.Group>\n <Command.Separator />\n <Command.Group heading={content.moreHeading.value}>\n <Command.Item\n value=\"continue\"\n className=\"gap-2 px-4\"\n onSelect={() => {\n const pos = editor.state.selection.from;\n const text = getPrevText(editor, pos);\n onSelect(text, 'continue');\n }}\n >\n <StepForward className=\"size-4 text-foreground\" />\n {content.continueWriting}\n </Command.Item>\n </Command.Group>\n </>\n );\n};\n"],"mappings":";;;;;;;;;;AAcA,MAAM,UAIA;CACJ;EAAE,OAAO;EAAW,OAAO;EAAmB,MAAM;CAAc;CAClE;EAAE,OAAO;EAAO,OAAO;EAAe,MAAM;CAAW;CACvD;EAAE,OAAO;EAAW,OAAO;EAAgB,MAAM;CAAoB;CACrE;EAAE,OAAO;EAAU,OAAO;EAAe,MAAM;CAAS;AAC1D;;;;;AAUA,MAAa,sBAAsB,EAAE,eAAwC;CAC3E,MAAM,EAAE,WAAW,UAAU;CAC7B,MAAM,UAAU,YAAY,iBAAiB;CAE7C,IAAI,CAAC,QAAQ,OAAO;CAEpB,MAAM,kBAAkB,UAAkB;EACxC,QAAQ,OAAR;GACE,KAAK,WACH,OAAO,QAAQ,eAAe;GAChC,KAAK,OACH,OAAO,QAAQ,WAAW;GAC5B,KAAK,WACH,OAAO,QAAQ,YAAY;GAC7B,KAAK,UACH,OAAO,QAAQ,WAAW;GAC5B,SACE,OAAO;EACX;CACF;CAEA,OACE;EACE,oBAAC,QAAQ,OAAT;GAAe,SAAS,QAAQ,YAAY;aACzC,QAAQ,KAAK,WACZ,qBAAC,QAAQ,MAAT;IAEE,OAAO,OAAO;IACd,WAAU;IACV,WAAW,UAAU;KACnB,MAAM,QAAQ,OAAO,MAAM,UAAU,QAAQ;KAI7C,SAHa,OAAO,QAAQ,SAAS,WAAW,UAC9C,MAAM,OAEI,GAAG,KAA2B;IAC5C;cAVF,CAYE,oBAAC,OAAO,MAAR,EAAa,WAAU,yBAA0B,IAChD,eAAe,OAAO,KAAK,CAChB;MAbP,OAAO,KAaA,CACf;EACY;EACf,oBAAC,QAAQ,WAAT,CAAoB;EACpB,oBAAC,QAAQ,OAAT;GAAe,SAAS,QAAQ,YAAY;aAC1C,qBAAC,QAAQ,MAAT;IACE,OAAM;IACN,WAAU;IACV,gBAAgB;KACd,MAAM,MAAM,OAAO,MAAM,UAAU;KAEnC,SADa,YAAY,QAAQ,GACrB,GAAG,UAAU;IAC3B;cAPF,CASE,oBAAC,aAAD,EAAa,WAAU,yBAA0B,IAChD,QAAQ,eACG;;EACD;CACf;AAEN"}
@@ -23,7 +23,7 @@ const GenerativeMenuSwitch = ({ children, open, onOpenChange, sendButtonVariant,
23
23
  }, [open, editor]);
24
24
  return /* @__PURE__ */ jsxs(EditorBubble, {
25
25
  options: { placement: open ? "bottom-start" : "top" },
26
- className: "flex w-fit max-w-[90vw] rounded-lg border-[1.3px] border-neutral/20 bg-card/95 text-text shadow-xl backdrop-blur [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-xl",
26
+ className: "flex w-fit max-w-[90vw] rounded-lg border-[1.3px] bg-card/95 text-foreground shadow-xl backdrop-blur [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-xl",
27
27
  children: [open && /* @__PURE__ */ jsx(AISelector, {
28
28
  onOpenChange,
29
29
  sendButtonVariant,
@@ -34,7 +34,7 @@ const GenerativeMenuSwitch = ({ children, open, onOpenChange, sendButtonVariant,
34
34
  color: "text",
35
35
  size: "sm",
36
36
  roundedSize: "none",
37
- className: "gap-1 text-text",
37
+ className: "gap-1 text-foreground",
38
38
  Icon: Sparkles,
39
39
  onClick: () => onOpenChange(true),
40
40
  children: content.askAI
@@ -1 +1 @@
1
- {"version":3,"file":"GenerativeMenuSwitch.mjs","names":[],"sources":["../../../../../src/components/MarkdownEditor/generative/GenerativeMenuSwitch.tsx"],"sourcesContent":["'use client';\n\nimport {\n Button,\n type ButtonColor,\n type ButtonVariant,\n} from '@components/Button';\nimport { Sparkles } from 'lucide-react';\nimport { Fragment, type ReactNode, useEffect } from 'react';\nimport { useIntlayer } from 'react-intlayer';\nimport { EditorBubble, removeAIHighlight, useEditor } from '../novel';\nimport { AISelector } from './AISelector';\n\nexport type GenerativeMenuSwitchProps = {\n children: ReactNode;\n open: boolean;\n onOpenChange: (open: boolean) => void;\n /** Visual variant forwarded to the AI send button. @default \"default\" */\n sendButtonVariant?: ButtonVariant;\n /** Color theme forwarded to the AI send button. @default \"text\" */\n sendButtonColor?: ButtonColor;\n};\n\n/**\n * Bubble menu that toggles between the regular formatting toolbar (its\n * `children`) and the AI command palette.\n */\nexport const GenerativeMenuSwitch = ({\n children,\n open,\n onOpenChange,\n sendButtonVariant,\n sendButtonColor,\n}: GenerativeMenuSwitchProps) => {\n const { editor } = useEditor();\n const content = useIntlayer('markdown-editor');\n\n useEffect(() => {\n if (!open && editor) removeAIHighlight(editor);\n }, [open, editor]);\n\n return (\n <EditorBubble\n options={{ placement: open ? 'bottom-start' : 'top' }}\n className=\"flex w-fit max-w-[90vw] rounded-lg border-[1.3px] border-neutral/20 bg-card/95 text-text shadow-xl backdrop-blur [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-xl\"\n >\n {open && (\n <AISelector\n onOpenChange={onOpenChange}\n sendButtonVariant={sendButtonVariant}\n sendButtonColor={sendButtonColor}\n />\n )}\n {!open && (\n <Fragment>\n <Button\n label={content.askAI.value}\n variant=\"hoverable\"\n color=\"text\"\n size=\"sm\"\n roundedSize=\"none\"\n className=\"gap-1 text-text\"\n Icon={Sparkles}\n onClick={() => onOpenChange(true)}\n >\n {content.askAI}\n </Button>\n {children}\n </Fragment>\n )}\n </EditorBubble>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;AA2BA,MAAa,wBAAwB,EACnC,UACA,MACA,cACA,mBACA,sBAC+B;CAC/B,MAAM,EAAE,WAAW,UAAU;CAC7B,MAAM,UAAU,YAAY,iBAAiB;CAE7C,gBAAgB;EACd,IAAI,CAAC,QAAQ,QAAQ,kBAAkB,MAAM;CAC/C,GAAG,CAAC,MAAM,MAAM,CAAC;CAEjB,OACE,qBAAC,cAAD;EACE,SAAS,EAAE,WAAW,OAAO,iBAAiB,MAAM;EACpD,WAAU;YAFZ,CAIG,QACC,oBAAC,YAAD;GACgB;GACK;GACF;EAClB,IAEF,CAAC,QACA,qBAAC,UAAD,aACE,oBAAC,QAAD;GACE,OAAO,QAAQ,MAAM;GACrB,SAAQ;GACR,OAAM;GACN,MAAK;GACL,aAAY;GACZ,WAAU;GACV,MAAM;GACN,eAAe,aAAa,IAAI;aAE/B,QAAQ;EACH,IACP,QACO,IAEA;;AAElB"}
1
+ {"version":3,"file":"GenerativeMenuSwitch.mjs","names":[],"sources":["../../../../../src/components/MarkdownEditor/generative/GenerativeMenuSwitch.tsx"],"sourcesContent":["'use client';\n\nimport {\n Button,\n type ButtonColor,\n type ButtonVariant,\n} from '@components/Button';\nimport { Sparkles } from 'lucide-react';\nimport { Fragment, type ReactNode, useEffect } from 'react';\nimport { useIntlayer } from 'react-intlayer';\nimport { EditorBubble, removeAIHighlight, useEditor } from '../novel';\nimport { AISelector } from './AISelector';\n\nexport type GenerativeMenuSwitchProps = {\n children: ReactNode;\n open: boolean;\n onOpenChange: (open: boolean) => void;\n /** Visual variant forwarded to the AI send button. @default \"default\" */\n sendButtonVariant?: ButtonVariant;\n /** Color theme forwarded to the AI send button. @default \"text\" */\n sendButtonColor?: ButtonColor;\n};\n\n/**\n * Bubble menu that toggles between the regular formatting toolbar (its\n * `children`) and the AI command palette.\n */\nexport const GenerativeMenuSwitch = ({\n children,\n open,\n onOpenChange,\n sendButtonVariant,\n sendButtonColor,\n}: GenerativeMenuSwitchProps) => {\n const { editor } = useEditor();\n const content = useIntlayer('markdown-editor');\n\n useEffect(() => {\n if (!open && editor) removeAIHighlight(editor);\n }, [open, editor]);\n\n return (\n <EditorBubble\n options={{ placement: open ? 'bottom-start' : 'top' }}\n className=\"flex w-fit max-w-[90vw] rounded-lg border-[1.3px] bg-card/95 text-foreground shadow-xl backdrop-blur [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-xl\"\n >\n {open && (\n <AISelector\n onOpenChange={onOpenChange}\n sendButtonVariant={sendButtonVariant}\n sendButtonColor={sendButtonColor}\n />\n )}\n {!open && (\n <Fragment>\n <Button\n label={content.askAI.value}\n variant=\"hoverable\"\n color=\"text\"\n size=\"sm\"\n roundedSize=\"none\"\n className=\"gap-1 text-foreground\"\n Icon={Sparkles}\n onClick={() => onOpenChange(true)}\n >\n {content.askAI}\n </Button>\n {children}\n </Fragment>\n )}\n </EditorBubble>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;AA2BA,MAAa,wBAAwB,EACnC,UACA,MACA,cACA,mBACA,sBAC+B;CAC/B,MAAM,EAAE,WAAW,UAAU;CAC7B,MAAM,UAAU,YAAY,iBAAiB;CAE7C,gBAAgB;EACd,IAAI,CAAC,QAAQ,QAAQ,kBAAkB,MAAM;CAC/C,GAAG,CAAC,MAAM,MAAM,CAAC;CAEjB,OACE,qBAAC,cAAD;EACE,SAAS,EAAE,WAAW,OAAO,iBAAiB,MAAM;EACpD,WAAU;YAFZ,CAIG,QACC,oBAAC,YAAD;GACgB;GACK;GACF;EAClB,IAEF,CAAC,QACA,qBAAC,UAAD,aACE,oBAAC,QAAD;GACE,OAAO,QAAQ,MAAM;GACrB,SAAQ;GACR,OAAM;GACN,MAAK;GACL,aAAY;GACZ,WAAU;GACV,MAAM;GACN,eAAe,aAAa,IAAI;aAE/B,QAAQ;EACH,IACP,QACO,IAEA;;AAElB"}
@@ -0,0 +1,197 @@
1
+ import { bundledLanguages, bundledThemes, getSingletonHighlighter } from "../../../IDE/shikiBundle.mjs";
2
+ import { findChildren } from "@tiptap/core";
3
+ import { CodeBlock } from "@tiptap/extension-code-block";
4
+ import { Plugin, PluginKey } from "@tiptap/pm/state";
5
+ import { Decoration, DecorationSet } from "@tiptap/pm/view";
6
+
7
+ //#region src/components/MarkdownEditor/novel/extensions/code-block-shiki.ts
8
+ const formatStyles = (styles) => Object.entries(styles).filter(([_, value]) => Boolean(value)).map(([key, value]) => `${key}:${value}`).join(";");
9
+ let sharedHighlighter = null;
10
+ const loadingLangs = /* @__PURE__ */ new Set();
11
+ const loadingThemes = /* @__PURE__ */ new Set();
12
+ const renderDecorations = ({ doc, name, defaultLanguage, defaultTheme, themes, highlighter }) => {
13
+ if (!highlighter) return DecorationSet.empty;
14
+ const decorations = [];
15
+ const loadedLangs = highlighter.getLoadedLanguages();
16
+ const loadedThemes = highlighter.getLoadedThemes();
17
+ const resolveTheme = (themeName) => loadedThemes.includes(themeName) ? themeName : loadedThemes[0] ?? "github-light";
18
+ findChildren(doc, (node) => node.type.name === name).forEach((block) => {
19
+ let from = block.pos + 1;
20
+ let language = block.node.attrs.language || defaultLanguage;
21
+ const blockTheme = block.node.attrs.theme || defaultTheme;
22
+ const lightTheme = block.node.attrs.themes?.light || themes?.light || blockTheme;
23
+ const darkTheme = block.node.attrs.themes?.dark || themes?.dark || blockTheme;
24
+ if (!loadedLangs.includes(language)) language = "plaintext";
25
+ if (themes) {
26
+ const tokensResult = highlighter.codeToTokens(block.node.textContent, {
27
+ lang: language,
28
+ themes: {
29
+ light: resolveTheme(lightTheme),
30
+ dark: resolveTheme(darkTheme)
31
+ }
32
+ });
33
+ const preStyles = {};
34
+ if (tokensResult.bg) preStyles["background-color"] = tokensResult.bg;
35
+ if (tokensResult.fg) preStyles.color = tokensResult.fg;
36
+ decorations.push(Decoration.node(block.pos, block.pos + block.node.nodeSize, {
37
+ style: formatStyles(preStyles),
38
+ class: "shiki"
39
+ }));
40
+ for (const line of tokensResult.tokens) {
41
+ for (const token of line) {
42
+ const to = from + token.content.length;
43
+ const style = formatStyles(token.htmlStyle || {});
44
+ decorations.push(Decoration.inline(from, to, { style }));
45
+ from = to;
46
+ }
47
+ from += 1;
48
+ }
49
+ } else {
50
+ const activeTheme = resolveTheme(blockTheme);
51
+ const tokensResult = highlighter.codeToTokens(block.node.textContent, {
52
+ lang: language,
53
+ theme: activeTheme
54
+ });
55
+ const themeObj = highlighter.getTheme(activeTheme);
56
+ decorations.push(Decoration.node(block.pos, block.pos + block.node.nodeSize, {
57
+ style: formatStyles({ "background-color": themeObj.bg }),
58
+ class: "shiki"
59
+ }));
60
+ for (const line of tokensResult.tokens) {
61
+ for (const token of line) {
62
+ const to = from + token.content.length;
63
+ const style = formatStyles({ color: token.color || "inherit" });
64
+ decorations.push(Decoration.inline(from, to, { style }));
65
+ from = to;
66
+ }
67
+ from += 1;
68
+ }
69
+ }
70
+ });
71
+ return DecorationSet.create(doc, decorations);
72
+ };
73
+ const createShikiPlugin = ({ name, defaultLanguage, defaultTheme, themes }) => {
74
+ const loadMissing = async (doc) => {
75
+ if (!sharedHighlighter) return false;
76
+ const blocks = findChildren(doc, (node) => node.type.name === name);
77
+ const loadedLangs = sharedHighlighter.getLoadedLanguages();
78
+ const loadedThemes = sharedHighlighter.getLoadedThemes();
79
+ const langsToLoad = /* @__PURE__ */ new Set();
80
+ const themesToLoad = /* @__PURE__ */ new Set();
81
+ for (const block of blocks) {
82
+ const lang = block.node.attrs.language || defaultLanguage;
83
+ if (lang && !loadedLangs.includes(lang) && lang in bundledLanguages && !loadingLangs.has(lang)) langsToLoad.add(lang);
84
+ if (themes) {
85
+ const light = block.node.attrs.themes?.light || themes.light;
86
+ const dark = block.node.attrs.themes?.dark || themes.dark;
87
+ if (light && !loadedThemes.includes(light) && light in bundledThemes && !loadingThemes.has(light)) themesToLoad.add(light);
88
+ if (dark && !loadedThemes.includes(dark) && dark in bundledThemes && !loadingThemes.has(dark)) themesToLoad.add(dark);
89
+ } else {
90
+ const theme = block.node.attrs.theme || defaultTheme;
91
+ if (theme && !loadedThemes.includes(theme) && theme in bundledThemes && !loadingThemes.has(theme)) themesToLoad.add(theme);
92
+ }
93
+ }
94
+ if (langsToLoad.size === 0 && themesToLoad.size === 0) return false;
95
+ for (const l of langsToLoad) loadingLangs.add(l);
96
+ for (const t of themesToLoad) loadingThemes.add(t);
97
+ try {
98
+ await Promise.all([...Array.from(langsToLoad).map((l) => sharedHighlighter.loadLanguage(l)), ...Array.from(themesToLoad).map((t) => sharedHighlighter.loadTheme(t))]);
99
+ return true;
100
+ } finally {
101
+ for (const l of langsToLoad) loadingLangs.delete(l);
102
+ for (const t of themesToLoad) loadingThemes.delete(t);
103
+ }
104
+ };
105
+ const plugin = new Plugin({
106
+ key: new PluginKey("shiki"),
107
+ view(editorView) {
108
+ let isDestroyed = false;
109
+ const init = async () => {
110
+ if (!sharedHighlighter) sharedHighlighter = await getSingletonHighlighter();
111
+ if (isDestroyed) return;
112
+ await loadMissing(editorView.state.doc);
113
+ if (isDestroyed) return;
114
+ editorView.dispatch(editorView.state.tr.setMeta("shikiPluginForceDecoration", true));
115
+ };
116
+ init();
117
+ return {
118
+ update(view) {
119
+ loadMissing(view.state.doc).then((hasLoadedNew) => {
120
+ if (hasLoadedNew && !isDestroyed) view.dispatch(view.state.tr.setMeta("shikiPluginForceDecoration", true));
121
+ });
122
+ },
123
+ destroy() {
124
+ isDestroyed = true;
125
+ }
126
+ };
127
+ },
128
+ state: {
129
+ init: (_, { doc }) => renderDecorations({
130
+ doc,
131
+ name,
132
+ defaultLanguage,
133
+ defaultTheme,
134
+ themes,
135
+ highlighter: sharedHighlighter
136
+ }),
137
+ apply: (tr, decorationSet, oldState, newState) => {
138
+ const oldHead = oldState.selection.$head.parent.type.name;
139
+ const newHead = newState.selection.$head.parent.type.name;
140
+ const oldBlocks = findChildren(oldState.doc, (node) => node.type.name === name);
141
+ const newBlocks = findChildren(newState.doc, (node) => node.type.name === name);
142
+ const docChanged = tr.docChanged && ([oldHead, newHead].includes(name) || oldBlocks.length !== newBlocks.length || tr.steps.some((step) => {
143
+ const { from, to } = step;
144
+ return from !== void 0 && to !== void 0 && oldBlocks.some((block) => block.pos >= from && block.pos + block.node.nodeSize <= to);
145
+ }));
146
+ if (tr.getMeta("shikiPluginForceDecoration") || docChanged) return renderDecorations({
147
+ doc: tr.doc,
148
+ name,
149
+ defaultLanguage,
150
+ defaultTheme,
151
+ themes,
152
+ highlighter: sharedHighlighter
153
+ });
154
+ return decorationSet.map(tr.mapping, tr.doc);
155
+ }
156
+ },
157
+ props: { decorations(state) {
158
+ return plugin.getState(state);
159
+ } }
160
+ });
161
+ return plugin;
162
+ };
163
+ const CodeBlockShiki = CodeBlock.extend({
164
+ addOptions() {
165
+ return {
166
+ ...this.parent?.(),
167
+ defaultLanguage: null,
168
+ defaultTheme: "github-light",
169
+ themes: null
170
+ };
171
+ },
172
+ markdownTokenName: "code",
173
+ parseMarkdown(rawNode, builder) {
174
+ if (rawNode.raw?.startsWith("```") === false && rawNode.raw?.startsWith("~~~") === false && rawNode.codeBlockStyle !== "indented") return [];
175
+ return builder.createNode("codeBlock", { language: rawNode.lang || null }, rawNode.text ? [builder.createTextNode(rawNode.text)] : []);
176
+ },
177
+ renderMarkdown(node, builder) {
178
+ const lang = node.attrs?.language || "";
179
+ return node.content ? [
180
+ `\`\`\`${lang}`,
181
+ builder.renderChildren(node.content),
182
+ "```"
183
+ ].join("\n") : `\`\`\`${lang}\n\n\`\`\``;
184
+ },
185
+ addProseMirrorPlugins() {
186
+ return [...this.parent?.() || [], createShikiPlugin({
187
+ name: this.name,
188
+ defaultLanguage: this.options.defaultLanguage,
189
+ defaultTheme: this.options.defaultTheme,
190
+ themes: this.options.themes
191
+ })];
192
+ }
193
+ });
194
+
195
+ //#endregion
196
+ export { CodeBlockShiki };
197
+ //# sourceMappingURL=code-block-shiki.mjs.map