@intlayer/design-system 9.0.0-canary.0 → 9.0.0-canary.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/api/hooks/ai.mjs +20 -1
- package/dist/esm/api/hooks/ai.mjs.map +1 -1
- package/dist/esm/api/hooks/asset.mjs +78 -0
- package/dist/esm/api/hooks/asset.mjs.map +1 -0
- package/dist/esm/api/index.mjs +6 -5
- package/dist/esm/api/useIntlayerAPI.mjs +6 -1
- package/dist/esm/api/useIntlayerAPI.mjs.map +1 -1
- package/dist/esm/components/Command/index.mjs +8 -8
- package/dist/esm/components/Command/index.mjs.map +1 -1
- package/dist/esm/components/ContentEditor/ContentEditorTextArea.mjs +1 -1
- package/dist/esm/components/DictionaryFieldEditor/ContentEditor.mjs +132 -35
- package/dist/esm/components/DictionaryFieldEditor/ContentEditor.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/ContentEditorView/TextEditor.mjs +33 -11
- package/dist/esm/components/DictionaryFieldEditor/ContentEditorView/TextEditor.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/DictionaryCreationForm/DictionaryCreationForm.mjs +146 -35
- package/dist/esm/components/DictionaryFieldEditor/DictionaryCreationForm/DictionaryCreationForm.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/DictionaryCreationForm/dictionaryCreationForm.content.mjs +308 -0
- package/dist/esm/components/DictionaryFieldEditor/DictionaryCreationForm/dictionaryCreationForm.content.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/DictionaryCreationForm/useDictionaryFormSchema.mjs +12 -2
- package/dist/esm/components/DictionaryFieldEditor/DictionaryCreationForm/useDictionaryFormSchema.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/DictionaryDetails/DictionaryDetailsForm.mjs +460 -20
- package/dist/esm/components/DictionaryFieldEditor/DictionaryDetails/DictionaryDetailsForm.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/DictionaryDetails/dictionaryDetails.content.mjs +506 -0
- package/dist/esm/components/DictionaryFieldEditor/DictionaryDetails/dictionaryDetails.content.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/DictionaryFieldEditor.mjs +10 -6
- package/dist/esm/components/DictionaryFieldEditor/DictionaryFieldEditor.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/NavigationView/NavigationViewNode.mjs +1 -1
- package/dist/esm/components/DictionaryFieldEditor/SaveForm/SaveForm.mjs +9 -9
- package/dist/esm/components/DictionaryFieldEditor/SaveForm/SaveForm.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/StructureView/StructureView.mjs +2 -2
- package/dist/esm/components/DictionaryFieldEditor/StructureView/StructureView.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/contentEditor.content.mjs +79 -0
- package/dist/esm/components/DictionaryFieldEditor/contentEditor.content.mjs.map +1 -0
- package/dist/esm/components/Form/Form.mjs +3 -43
- package/dist/esm/components/Form/Form.mjs.map +1 -1
- package/dist/esm/components/Form/elements/FormElement.mjs +2 -3
- package/dist/esm/components/Form/elements/FormElement.mjs.map +1 -1
- package/dist/esm/components/Form/elements/MultiselectElement.mjs +2 -3
- package/dist/esm/components/Form/elements/MultiselectElement.mjs.map +1 -1
- package/dist/esm/components/Form/elements/OTPElement.mjs +3 -4
- package/dist/esm/components/Form/elements/OTPElement.mjs.map +1 -1
- package/dist/esm/components/Form/elements/SelectElement.mjs +2 -3
- package/dist/esm/components/Form/elements/SelectElement.mjs.map +1 -1
- package/dist/esm/components/Form/elements/SwitchSelectorElement.mjs +2 -3
- package/dist/esm/components/Form/elements/SwitchSelectorElement.mjs.map +1 -1
- package/dist/esm/components/Form/index.mjs +21 -2
- package/dist/esm/components/Form/layout/FormItemLayout.mjs +8 -5
- package/dist/esm/components/Form/layout/FormItemLayout.mjs.map +1 -1
- package/dist/esm/components/LanguageBackground/LanguageBackground.mjs +3 -8
- package/dist/esm/components/LanguageBackground/LanguageBackground.mjs.map +1 -1
- package/dist/esm/components/LocaleSwitcherContentDropDown/LocaleSwitcherContent.mjs +1 -1
- package/dist/esm/components/MarkDownRender/MarkDownRender.mjs +1 -1
- package/dist/esm/components/MarkDownRender/MarkDownRender.mjs.map +1 -1
- package/dist/esm/components/MarkdownEditor/MarkdownEditor.mjs +113 -0
- package/dist/esm/components/MarkdownEditor/MarkdownEditor.mjs.map +1 -0
- package/dist/esm/components/MarkdownEditor/extensions.mjs +103 -0
- package/dist/esm/components/MarkdownEditor/extensions.mjs.map +1 -0
- package/dist/esm/components/MarkdownEditor/generative/AICompletionCommands.mjs +51 -0
- package/dist/esm/components/MarkdownEditor/generative/AICompletionCommands.mjs.map +1 -0
- package/dist/esm/components/MarkdownEditor/generative/AISelector.mjs +100 -0
- package/dist/esm/components/MarkdownEditor/generative/AISelector.mjs.map +1 -0
- package/dist/esm/components/MarkdownEditor/generative/AISelectorCommands.mjs +81 -0
- package/dist/esm/components/MarkdownEditor/generative/AISelectorCommands.mjs.map +1 -0
- package/dist/esm/components/MarkdownEditor/generative/GenerativeMenuSwitch.mjs +47 -0
- package/dist/esm/components/MarkdownEditor/generative/GenerativeMenuSwitch.mjs.map +1 -0
- package/dist/esm/components/MarkdownEditor/generative/useAICompletion.mjs +83 -0
- package/dist/esm/components/MarkdownEditor/generative/useAICompletion.mjs.map +1 -0
- package/dist/esm/components/MarkdownEditor/imageUpload.mjs +47 -0
- package/dist/esm/components/MarkdownEditor/imageUpload.mjs.map +1 -0
- package/dist/esm/components/MarkdownEditor/index.mjs +4 -0
- package/dist/esm/components/MarkdownEditor/markdownEditor.content.mjs +1140 -0
- package/dist/esm/components/MarkdownEditor/markdownEditor.content.mjs.map +1 -0
- package/dist/esm/components/MarkdownEditor/novel/components/editor-bubble-item.mjs +20 -0
- package/dist/esm/components/MarkdownEditor/novel/components/editor-bubble-item.mjs.map +1 -0
- package/dist/esm/components/MarkdownEditor/novel/components/editor-bubble.mjs +39 -0
- package/dist/esm/components/MarkdownEditor/novel/components/editor-bubble.mjs.map +1 -0
- package/dist/esm/components/MarkdownEditor/novel/components/editor-command-item.mjs +25 -0
- package/dist/esm/components/MarkdownEditor/novel/components/editor-command-item.mjs.map +1 -0
- package/dist/esm/components/MarkdownEditor/novel/components/editor-command.mjs +61 -0
- package/dist/esm/components/MarkdownEditor/novel/components/editor-command.mjs.map +1 -0
- package/dist/esm/components/MarkdownEditor/novel/components/editor.mjs +27 -0
- package/dist/esm/components/MarkdownEditor/novel/components/editor.mjs.map +1 -0
- package/dist/esm/components/MarkdownEditor/novel/components/index.mjs +8 -0
- package/dist/esm/components/MarkdownEditor/novel/extensions/ai-highlight.mjs +74 -0
- package/dist/esm/components/MarkdownEditor/novel/extensions/ai-highlight.mjs.map +1 -0
- package/dist/esm/components/MarkdownEditor/novel/extensions/custom-keymap.mjs +37 -0
- package/dist/esm/components/MarkdownEditor/novel/extensions/custom-keymap.mjs.map +1 -0
- package/dist/esm/components/MarkdownEditor/novel/extensions/image-resizer.mjs +49 -0
- package/dist/esm/components/MarkdownEditor/novel/extensions/image-resizer.mjs.map +1 -0
- package/dist/esm/components/MarkdownEditor/novel/extensions/index.mjs +51 -0
- package/dist/esm/components/MarkdownEditor/novel/extensions/index.mjs.map +1 -0
- package/dist/esm/components/MarkdownEditor/novel/extensions/slash-command.mjs +79 -0
- package/dist/esm/components/MarkdownEditor/novel/extensions/slash-command.mjs.map +1 -0
- package/dist/esm/components/MarkdownEditor/novel/extensions/updated-image.mjs +17 -0
- package/dist/esm/components/MarkdownEditor/novel/extensions/updated-image.mjs.map +1 -0
- package/dist/esm/components/MarkdownEditor/novel/index.mjs +17 -0
- package/dist/esm/components/MarkdownEditor/novel/plugins/index.mjs +3 -0
- package/dist/esm/components/MarkdownEditor/novel/plugins/upload-images.mjs +92 -0
- package/dist/esm/components/MarkdownEditor/novel/plugins/upload-images.mjs.map +1 -0
- package/dist/esm/components/MarkdownEditor/novel/utils/atoms.mjs +39 -0
- package/dist/esm/components/MarkdownEditor/novel/utils/atoms.mjs.map +1 -0
- package/dist/esm/components/MarkdownEditor/novel/utils/index.mjs +38 -0
- package/dist/esm/components/MarkdownEditor/novel/utils/index.mjs.map +1 -0
- package/dist/esm/components/MarkdownEditor/selectors/ColorSelector.mjs +176 -0
- package/dist/esm/components/MarkdownEditor/selectors/ColorSelector.mjs.map +1 -0
- package/dist/esm/components/MarkdownEditor/selectors/LinkSelector.mjs +95 -0
- package/dist/esm/components/MarkdownEditor/selectors/LinkSelector.mjs.map +1 -0
- package/dist/esm/components/MarkdownEditor/selectors/NodeSelector.mjs +139 -0
- package/dist/esm/components/MarkdownEditor/selectors/NodeSelector.mjs.map +1 -0
- package/dist/esm/components/MarkdownEditor/selectors/TextButtons.mjs +81 -0
- package/dist/esm/components/MarkdownEditor/selectors/TextButtons.mjs.map +1 -0
- package/dist/esm/components/MarkdownEditor/slashCommand.mjs +161 -0
- package/dist/esm/components/MarkdownEditor/slashCommand.mjs.map +1 -0
- package/dist/esm/components/Modal/Modal.mjs +2 -2
- package/dist/esm/components/Navbar/MobileNavbar.mjs +1 -1
- package/dist/esm/components/Pagination/Pagination.mjs +1 -1
- package/dist/esm/components/Pattern/GridDistortionPattern.mjs +275 -0
- package/dist/esm/components/Pattern/GridDistortionPattern.mjs.map +1 -0
- package/dist/esm/components/Pattern/index.mjs +2 -1
- package/dist/esm/components/RightDrawer/RightDrawer.mjs +3 -3
- package/dist/esm/components/Tab/Tab.mjs +1 -1
- package/dist/esm/components/Table/Table.mjs +1 -1
- package/dist/esm/components/Table/Table.mjs.map +1 -1
- package/dist/esm/components/Tag/index.mjs +0 -9
- package/dist/esm/components/Tag/index.mjs.map +1 -1
- package/dist/esm/components/Toaster/Toast.mjs +1 -1
- package/dist/esm/components/Toaster/Toast.mjs.map +1 -1
- package/dist/esm/components/index.mjs +27 -6
- package/dist/esm/hooks/index.mjs +8 -8
- package/dist/esm/hooks/useIsMounted.mjs +4 -2
- package/dist/esm/hooks/useIsMounted.mjs.map +1 -1
- package/dist/esm/routes.mjs +2 -1
- package/dist/esm/routes.mjs.map +1 -1
- package/dist/types/api/hooks/ai.d.ts +5 -1
- package/dist/types/api/hooks/ai.d.ts.map +1 -1
- package/dist/types/api/hooks/asset.d.ts +27 -0
- package/dist/types/api/hooks/asset.d.ts.map +1 -0
- package/dist/types/api/hooks/audit.d.ts +1 -1
- package/dist/types/api/index.d.ts +4 -3
- package/dist/types/api/useIntlayerAPI.d.ts +15 -3
- package/dist/types/api/useIntlayerAPI.d.ts.map +1 -1
- package/dist/types/components/Badge/index.d.ts +1 -1
- package/dist/types/components/Breadcrumb/index.d.ts +1 -1
- package/dist/types/components/Button/Button.d.ts +3 -3
- package/dist/types/components/CollapsibleTable/CollapsibleTable.d.ts +2 -2
- package/dist/types/components/Command/index.d.ts.map +1 -1
- package/dist/types/components/Container/index.d.ts +1 -1
- package/dist/types/components/DictionaryFieldEditor/ContentEditor.d.ts.map +1 -1
- package/dist/types/components/DictionaryFieldEditor/ContentEditorView/TextEditor.d.ts.map +1 -1
- package/dist/types/components/DictionaryFieldEditor/DictionaryCreationForm/DictionaryCreationForm.d.ts.map +1 -1
- package/dist/types/components/DictionaryFieldEditor/DictionaryCreationForm/dictionaryCreationForm.content.d.ts +308 -0
- package/dist/types/components/DictionaryFieldEditor/DictionaryCreationForm/dictionaryCreationForm.content.d.ts.map +1 -1
- package/dist/types/components/DictionaryFieldEditor/DictionaryCreationForm/useDictionaryFormSchema.d.ts +12 -1
- package/dist/types/components/DictionaryFieldEditor/DictionaryCreationForm/useDictionaryFormSchema.d.ts.map +1 -1
- package/dist/types/components/DictionaryFieldEditor/DictionaryDetails/DictionaryDetailsForm.d.ts +2 -0
- package/dist/types/components/DictionaryFieldEditor/DictionaryDetails/DictionaryDetailsForm.d.ts.map +1 -1
- package/dist/types/components/DictionaryFieldEditor/DictionaryDetails/dictionaryDetails.content.d.ts +506 -0
- package/dist/types/components/DictionaryFieldEditor/DictionaryDetails/dictionaryDetails.content.d.ts.map +1 -1
- package/dist/types/components/DictionaryFieldEditor/DictionaryFieldEditor.d.ts +2 -1
- package/dist/types/components/DictionaryFieldEditor/DictionaryFieldEditor.d.ts.map +1 -1
- package/dist/types/components/DictionaryFieldEditor/contentEditor.content.d.ts +80 -0
- package/dist/types/components/DictionaryFieldEditor/contentEditor.content.d.ts.map +1 -0
- package/dist/types/components/Form/Form.d.ts +27 -47
- package/dist/types/components/Form/Form.d.ts.map +1 -1
- package/dist/types/components/Form/elements/FormElement.d.ts.map +1 -1
- package/dist/types/components/Form/elements/MultiselectElement.d.ts.map +1 -1
- package/dist/types/components/Form/elements/OTPElement.d.ts.map +1 -1
- package/dist/types/components/Form/elements/SelectElement.d.ts.map +1 -1
- package/dist/types/components/Form/elements/SwitchSelectorElement.d.ts.map +1 -1
- package/dist/types/components/Form/index.d.ts +22 -3
- package/dist/types/components/Form/layout/FormItemLayout.d.ts.map +1 -1
- package/dist/types/components/Input/Checkbox.d.ts +3 -3
- package/dist/types/components/Input/Input.d.ts +1 -1
- package/dist/types/components/Input/Radio.d.ts +3 -3
- package/dist/types/components/LanguageBackground/LanguageBackground.d.ts.map +1 -1
- package/dist/types/components/Link/Link.d.ts +1 -1
- package/dist/types/components/MarkdownEditor/MarkdownEditor.d.ts +31 -0
- package/dist/types/components/MarkdownEditor/MarkdownEditor.d.ts.map +1 -0
- package/dist/types/components/MarkdownEditor/extensions.d.ts +11 -0
- package/dist/types/components/MarkdownEditor/extensions.d.ts.map +1 -0
- package/dist/types/components/MarkdownEditor/generative/AICompletionCommands.d.ts +18 -0
- package/dist/types/components/MarkdownEditor/generative/AICompletionCommands.d.ts.map +1 -0
- package/dist/types/components/MarkdownEditor/generative/AISelector.d.ts +22 -0
- package/dist/types/components/MarkdownEditor/generative/AISelector.d.ts.map +1 -0
- package/dist/types/components/MarkdownEditor/generative/AISelectorCommands.d.ts +17 -0
- package/dist/types/components/MarkdownEditor/generative/AISelectorCommands.d.ts.map +1 -0
- package/dist/types/components/MarkdownEditor/generative/GenerativeMenuSwitch.d.ts +26 -0
- package/dist/types/components/MarkdownEditor/generative/GenerativeMenuSwitch.d.ts.map +1 -0
- package/dist/types/components/MarkdownEditor/generative/useAICompletion.d.ts +30 -0
- package/dist/types/components/MarkdownEditor/generative/useAICompletion.d.ts.map +1 -0
- package/dist/types/components/MarkdownEditor/imageUpload.d.ts +29 -0
- package/dist/types/components/MarkdownEditor/imageUpload.d.ts.map +1 -0
- package/dist/types/components/MarkdownEditor/index.d.ts +3 -0
- package/dist/types/components/MarkdownEditor/markdownEditor.content.d.ts +1135 -0
- package/dist/types/components/MarkdownEditor/markdownEditor.content.d.ts.map +1 -0
- package/dist/types/components/MarkdownEditor/novel/components/editor-bubble-item.d.ts +14 -0
- package/dist/types/components/MarkdownEditor/novel/components/editor-bubble-item.d.ts.map +1 -0
- package/dist/types/components/MarkdownEditor/novel/components/editor-bubble.d.ts +18 -0
- package/dist/types/components/MarkdownEditor/novel/components/editor-bubble.d.ts.map +1 -0
- package/dist/types/components/MarkdownEditor/novel/components/editor-command-item.d.ts +28 -0
- package/dist/types/components/MarkdownEditor/novel/components/editor-command-item.d.ts.map +1 -0
- package/dist/types/components/MarkdownEditor/novel/components/editor-command.d.ts +34 -0
- package/dist/types/components/MarkdownEditor/novel/components/editor-command.d.ts.map +1 -0
- package/dist/types/components/MarkdownEditor/novel/components/editor.d.ts +23 -0
- package/dist/types/components/MarkdownEditor/novel/components/editor.d.ts.map +1 -0
- package/dist/types/components/MarkdownEditor/novel/components/index.d.ts +8 -0
- package/dist/types/components/MarkdownEditor/novel/extensions/ai-highlight.d.ts +36 -0
- package/dist/types/components/MarkdownEditor/novel/extensions/ai-highlight.d.ts.map +1 -0
- package/dist/types/components/MarkdownEditor/novel/extensions/custom-keymap.d.ts +17 -0
- package/dist/types/components/MarkdownEditor/novel/extensions/custom-keymap.d.ts.map +1 -0
- package/dist/types/components/MarkdownEditor/novel/extensions/image-resizer.d.ts +7 -0
- package/dist/types/components/MarkdownEditor/novel/extensions/image-resizer.d.ts.map +1 -0
- package/dist/types/components/MarkdownEditor/novel/extensions/index.d.ts +30 -0
- package/dist/types/components/MarkdownEditor/novel/extensions/index.d.ts.map +1 -0
- package/dist/types/components/MarkdownEditor/novel/extensions/slash-command.d.ts +38 -0
- package/dist/types/components/MarkdownEditor/novel/extensions/slash-command.d.ts.map +1 -0
- package/dist/types/components/MarkdownEditor/novel/extensions/updated-image.d.ts +8 -0
- package/dist/types/components/MarkdownEditor/novel/extensions/updated-image.d.ts.map +1 -0
- package/dist/types/components/MarkdownEditor/novel/index.d.ts +16 -0
- package/dist/types/components/MarkdownEditor/novel/plugins/index.d.ts +2 -0
- package/dist/types/components/MarkdownEditor/novel/plugins/upload-images.d.ts +22 -0
- package/dist/types/components/MarkdownEditor/novel/plugins/upload-images.d.ts.map +1 -0
- package/dist/types/components/MarkdownEditor/novel/utils/atoms.d.ts +21 -0
- package/dist/types/components/MarkdownEditor/novel/utils/atoms.d.ts.map +1 -0
- package/dist/types/components/MarkdownEditor/novel/utils/index.d.ts +10 -0
- package/dist/types/components/MarkdownEditor/novel/utils/index.d.ts.map +1 -0
- package/dist/types/components/MarkdownEditor/selectors/ColorSelector.d.ts +17 -0
- package/dist/types/components/MarkdownEditor/selectors/ColorSelector.d.ts.map +1 -0
- package/dist/types/components/MarkdownEditor/selectors/LinkSelector.d.ts +15 -0
- package/dist/types/components/MarkdownEditor/selectors/LinkSelector.d.ts.map +1 -0
- package/dist/types/components/MarkdownEditor/selectors/NodeSelector.d.ts +22 -0
- package/dist/types/components/MarkdownEditor/selectors/NodeSelector.d.ts.map +1 -0
- package/dist/types/components/MarkdownEditor/selectors/TextButtons.d.ts +11 -0
- package/dist/types/components/MarkdownEditor/selectors/TextButtons.d.ts.map +1 -0
- package/dist/types/components/MarkdownEditor/slashCommand.d.ts +29 -0
- package/dist/types/components/MarkdownEditor/slashCommand.d.ts.map +1 -0
- package/dist/types/components/Pattern/GridDistortionPattern.d.ts +15 -0
- package/dist/types/components/Pattern/GridDistortionPattern.d.ts.map +1 -0
- package/dist/types/components/Pattern/index.d.ts +2 -1
- package/dist/types/components/SwitchSelector/SwitchSelector.d.ts +1 -1
- package/dist/types/components/SwitchSelector/VerticalSwitchSelector.d.ts +1 -1
- package/dist/types/components/Tag/index.d.ts +2 -2
- package/dist/types/components/Tag/index.d.ts.map +1 -1
- package/dist/types/components/Toaster/Toast.d.ts +1 -1
- package/dist/types/components/index.d.ts +24 -3
- package/dist/types/routes.d.ts +2 -1
- package/dist/types/routes.d.ts.map +1 -1
- package/package.json +63 -32
- package/src/components/MarkdownEditor/markdown-editor.css +297 -0
|
@@ -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';\n\nexport type { ParsedMarkdown };\n\nimport type { BundledLanguage } from 'shiki/bundle/web';\nimport { H1, H2, H3, H4, H5, H6 } from '../Headers';\nimport { Code } from '../IDE/Code';\nimport { CodeProvider } from '../IDE/CodeContext';\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\n// Extracted, stable component renderers\nconst H1Renderer = (props: ComponentProps<'h1'>) => (\n <H1 isClickable className=\"mb-16 mb-8 text-text\" {...props} />\n);\nconst H2Renderer = (props: ComponentProps<'h2'>) => (\n <H2 isClickable className=\"mt-16 text-text\" {...props} />\n);\nconst H3Renderer = (props: ComponentProps<'h3'>) => (\n <H3 isClickable className=\"mt-5 text-text\" {...props} />\n);\nconst H4Renderer = (props: ComponentProps<'h4'>) => (\n <H4 isClickable className=\"mt-3 text-text\" {...props} />\n);\nconst H5Renderer = (props: ComponentProps<'h5'>) => (\n <H5 isClickable className=\"mt-3 text-text\" {...props} />\n);\nconst H6Renderer = (props: ComponentProps<'h6'>) => (\n <H6 isClickable className=\"mt-3 text-text\" {...props} />\n);\nconst StrongRenderer = (props: ComponentProps<'strong'>) => (\n <strong className=\"text-text\" {...props} />\n);\n\nconst MemoizedCodeBlock = memo(\n ({\n className,\n children,\n isDarkMode,\n lang, // Destructure html lang prop to prevent passing invalid BCP-47 language tag to Code component\n ...rest\n }: ComponentProps<'code'> & { isDarkMode?: boolean }) => {\n const content = String(children ?? '').replace(/\\n$/, '');\n const isBlock = !!className;\n\n if (!isBlock) {\n const decodedContent = content.replace(\n /&(?:amp;)?#(\\d+);/g,\n (_, code: string) => String.fromCharCode(parseInt(code, 10))\n );\n return (\n <code className=\"rounded-md border border-neutral/30 bg-card/60 box-decoration-clone px-1.5 py-0.5 font-mono text-sm\">\n {decodedContent}\n </code>\n );\n }\n\n const language = (className?.replace(/lang(?:uage)?-/, '') ||\n 'plaintext') as BundledLanguage;\n\n return (\n <Code {...rest} language={language} showHeader isDarkMode={isDarkMode}>\n {content}\n </Code>\n );\n },\n (prevProps, nextProps) =>\n prevProps.children === nextProps.children &&\n prevProps.className === nextProps.className &&\n prevProps.isDarkMode === nextProps.isDarkMode\n);\n\nconst createCodeRenderer = (isDarkMode?: boolean) => {\n return function CodeWrapper(props: ComponentProps<'code'>) {\n return <MemoizedCodeBlock {...props} isDarkMode={isDarkMode} />;\n };\n};\n\nconst BlockquoteRenderer = ({\n className,\n ...props\n}: ComponentProps<'blockquote'>) => (\n <blockquote\n className={cn(\n 'mt-5 gap-3 border-card border-l-4 pl-5 text-neutral [&_strong]:text-neutral',\n className\n )}\n {...props}\n />\n);\n\nconst UlRenderer = ({ className, ...props }: ComponentProps<'ul'>) => (\n <ul\n className={cn(\n 'mt-5 flex list-disc flex-col gap-3 pl-5 marker:text-neutral/80',\n className\n )}\n {...props}\n />\n);\n\nconst OlRenderer = ({ className, ...props }: ComponentProps<'ol'>) => (\n <ol\n className={cn(\n 'mt-5 flex list-decimal flex-col gap-3 pl-5 marker:text-neutral/80',\n className\n )}\n {...props}\n />\n);\n\nconst ImgRenderer = ({\n className,\n alt,\n src,\n ...props\n}: ComponentProps<'img'>) => (\n <img\n {...props}\n alt={alt ?? ''}\n loading=\"lazy\"\n className={cn('max-h-[80vh] max-w-full rounded-md', className)}\n src={\n src?.includes('github.com')\n ? src\n ?.replace('github.com', 'raw.githubusercontent.com')\n .replace('/blob/', '/') // GitHub raw URLs do not use /blob/\n : src\n }\n />\n);\n\nconst createLinkRenderer = (locale?: LocalesValues) => {\n return (props: ComponentProps<'a'>) => (\n <Link\n isExternalLink={props.href?.startsWith('http')}\n underlined\n locale={locale}\n label=\"\"\n color=\"text\"\n {...(props as any)}\n />\n );\n};\n\nconst PreRenderer = (props: ComponentProps<'pre'>) => <>{props.children}</>;\nconst TableRenderer = (props: ComponentProps<typeof SmartTable>) => (\n <SmartTable isRollable displayModal {...props} />\n);\n\nconst TabsRenderer = (props: ComponentProps<typeof Tab>) => (\n <Tab\n {...props}\n className=\"rounded-xl border border-card\"\n headerClassName=\"sticky rounded-xl top-24 z-5 bg-background/70 backdrop-blur overflow-x-auto\"\n />\n);\nconst ColumnsRenderer = ({\n className,\n ...props\n}: ComponentPropsWithoutRef<'div'>) => (\n <div className={cn('flex gap-4 max-md:flex-col', className)} {...props} />\n);\nconst ColumnRenderer = ({\n className,\n ...props\n}: ComponentPropsWithoutRef<'div'>) => (\n <div className={cn('flex-1', className)} {...props} />\n);\n\nconst Iframe = (props: ComponentProps<'iframe'>) => (\n <MarkDownIframe {...props} />\n);\n\n// Static configuration object for static renderers\nconst staticMarkdownComponents = {\n h1: H1Renderer,\n h2: H2Renderer,\n h3: H3Renderer,\n h4: H4Renderer,\n h5: H5Renderer,\n h6: H6Renderer,\n strong: StrongRenderer,\n blockquote: BlockquoteRenderer,\n ul: UlRenderer,\n ol: OlRenderer,\n img: ImgRenderer,\n pre: PreRenderer,\n table: TableRenderer,\n th: Th,\n tr: Tr,\n td: Td,\n hr: Hr,\n Tabs: TabsRenderer,\n Tab: Tab.Item,\n Columns: ColumnsRenderer,\n Column: ColumnRenderer,\n iframe: Iframe,\n};\n\n// Factory function to create components with dynamic props\nconst createMarkdownComponents = (\n isDarkMode?: boolean,\n locale?: LocalesValues\n) => ({\n ...staticMarkdownComponents,\n code: createCodeRenderer(isDarkMode),\n a: createLinkRenderer(locale),\n});\n\n// Export static renderers for backward compatibility\nexport const baseMarkdownComponents = staticMarkdownComponents;\n\ntype MarkdownRendererProps = {\n children: string | ParsedMarkdown;\n isDarkMode?: boolean;\n locale?: LocalesValues;\n forceBlock?: boolean;\n preserveFrontmatter?: boolean;\n tagfilter?: boolean;\n components?: ComponentProps<typeof MarkdownRendererIntlayer>['components'];\n wrapper?: ComponentProps<typeof MarkdownRendererIntlayer>['wrapper'];\n};\n\nexport const getIntlayerMarkdownOptions = (_isDarkMode?: boolean) => ({\n components: baseMarkdownComponents,\n});\n\nexport const MarkdownRenderer: FC<MarkdownRendererProps> = ({\n children,\n isDarkMode = false,\n locale,\n forceBlock,\n preserveFrontmatter,\n tagfilter,\n components: componentsProp,\n wrapper,\n}) => {\n const markdownComponents = createMarkdownComponents(isDarkMode, locale);\n\n const markdownContent = renderMarkdown(children, {\n components: {\n ...markdownComponents,\n ...componentsProp,\n },\n wrapper,\n forceBlock,\n preserveFrontmatter,\n tagfilter,\n });\n\n return (\n <CodeProvider>\n <TabProvider>{markdownContent}</TabProvider>\n </CodeProvider>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"MarkDownRender.mjs","names":[],"sources":["../../../../src/components/MarkDownRender/MarkDownRender.tsx"],"sourcesContent":["import type { LocalesValues } from '@intlayer/types/module_augmentation';\nimport { cn } from '@utils/cn';\nimport type { ComponentProps, ComponentPropsWithoutRef, FC } from 'react';\nimport { memo } from 'react';\nimport {\n type MarkdownRenderer as MarkdownRendererIntlayer,\n type ParsedMarkdown,\n renderMarkdown,\n} from 'react-intlayer/markdown';\n\nexport type { ParsedMarkdown };\n\nimport type { BundledLanguage } from 'shiki/bundle/web';\nimport { H1, H2, H3, H4, H5, H6 } from '../Headers';\nimport { Code } from '../IDE/Code';\nimport { CodeProvider } from '../IDE/CodeContext';\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\n// Extracted, stable component renderers\nconst H1Renderer = (props: ComponentProps<'h1'>) => (\n <H1 isClickable className=\"mb-16 mb-8 text-text\" {...props} />\n);\nconst H2Renderer = (props: ComponentProps<'h2'>) => (\n <H2 isClickable className=\"mt-16 text-text\" {...props} />\n);\nconst H3Renderer = (props: ComponentProps<'h3'>) => (\n <H3 isClickable className=\"mt-5 text-text\" {...props} />\n);\nconst H4Renderer = (props: ComponentProps<'h4'>) => (\n <H4 isClickable className=\"mt-3 text-text\" {...props} />\n);\nconst H5Renderer = (props: ComponentProps<'h5'>) => (\n <H5 isClickable className=\"mt-3 text-text\" {...props} />\n);\nconst H6Renderer = (props: ComponentProps<'h6'>) => (\n <H6 isClickable className=\"mt-3 text-text\" {...props} />\n);\nconst StrongRenderer = (props: ComponentProps<'strong'>) => (\n <strong className=\"text-text\" {...props} />\n);\n\nconst MemoizedCodeBlock = memo(\n ({\n className,\n children,\n isDarkMode,\n lang, // Destructure html lang prop to prevent passing invalid BCP-47 language tag to Code component\n ...rest\n }: ComponentProps<'code'> & { isDarkMode?: boolean }) => {\n const content = String(children ?? '').replace(/\\n$/, '');\n const isBlock = !!className;\n\n if (!isBlock) {\n const decodedContent = content.replace(\n /&(?:amp;)?#(\\d+);/g,\n (_, code: string) => String.fromCharCode(parseInt(code, 10))\n );\n return (\n <code className=\"scale-80 rounded-md border border-neutral/30 bg-card/60 box-decoration-clone px-1.5 py-0.5 font-mono\">\n {decodedContent}\n </code>\n );\n }\n\n const language = (className?.replace(/lang(?:uage)?-/, '') ||\n 'plaintext') as BundledLanguage;\n\n return (\n <Code {...rest} language={language} showHeader isDarkMode={isDarkMode}>\n {content}\n </Code>\n );\n },\n (prevProps, nextProps) =>\n prevProps.children === nextProps.children &&\n prevProps.className === nextProps.className &&\n prevProps.isDarkMode === nextProps.isDarkMode\n);\n\nconst createCodeRenderer = (isDarkMode?: boolean) => {\n return function CodeWrapper(props: ComponentProps<'code'>) {\n return <MemoizedCodeBlock {...props} isDarkMode={isDarkMode} />;\n };\n};\n\nconst BlockquoteRenderer = ({\n className,\n ...props\n}: ComponentProps<'blockquote'>) => (\n <blockquote\n className={cn(\n 'mt-5 gap-3 border-card border-l-4 pl-5 text-neutral [&_strong]:text-neutral',\n className\n )}\n {...props}\n />\n);\n\nconst UlRenderer = ({ className, ...props }: ComponentProps<'ul'>) => (\n <ul\n className={cn(\n 'mt-5 flex list-disc flex-col gap-3 pl-5 marker:text-neutral/80',\n className\n )}\n {...props}\n />\n);\n\nconst OlRenderer = ({ className, ...props }: ComponentProps<'ol'>) => (\n <ol\n className={cn(\n 'mt-5 flex list-decimal flex-col gap-3 pl-5 marker:text-neutral/80',\n className\n )}\n {...props}\n />\n);\n\nconst ImgRenderer = ({\n className,\n alt,\n src,\n ...props\n}: ComponentProps<'img'>) => (\n <img\n {...props}\n alt={alt ?? ''}\n loading=\"lazy\"\n className={cn('max-h-[80vh] max-w-full rounded-md', className)}\n src={\n src?.includes('github.com')\n ? src\n ?.replace('github.com', 'raw.githubusercontent.com')\n .replace('/blob/', '/') // GitHub raw URLs do not use /blob/\n : src\n }\n />\n);\n\nconst createLinkRenderer = (locale?: LocalesValues) => {\n return (props: ComponentProps<'a'>) => (\n <Link\n isExternalLink={props.href?.startsWith('http')}\n underlined\n locale={locale}\n label=\"\"\n color=\"text\"\n {...(props as any)}\n />\n );\n};\n\nconst PreRenderer = (props: ComponentProps<'pre'>) => <>{props.children}</>;\nconst TableRenderer = (props: ComponentProps<typeof SmartTable>) => (\n <SmartTable isRollable displayModal {...props} />\n);\n\nconst TabsRenderer = (props: ComponentProps<typeof Tab>) => (\n <Tab\n {...props}\n className=\"rounded-xl border border-card\"\n headerClassName=\"sticky rounded-xl top-24 z-5 bg-background/70 backdrop-blur overflow-x-auto\"\n />\n);\nconst ColumnsRenderer = ({\n className,\n ...props\n}: ComponentPropsWithoutRef<'div'>) => (\n <div className={cn('flex gap-4 max-md:flex-col', className)} {...props} />\n);\nconst ColumnRenderer = ({\n className,\n ...props\n}: ComponentPropsWithoutRef<'div'>) => (\n <div className={cn('flex-1', className)} {...props} />\n);\n\nconst Iframe = (props: ComponentProps<'iframe'>) => (\n <MarkDownIframe {...props} />\n);\n\n// Static configuration object for static renderers\nconst staticMarkdownComponents = {\n h1: H1Renderer,\n h2: H2Renderer,\n h3: H3Renderer,\n h4: H4Renderer,\n h5: H5Renderer,\n h6: H6Renderer,\n strong: StrongRenderer,\n blockquote: BlockquoteRenderer,\n ul: UlRenderer,\n ol: OlRenderer,\n img: ImgRenderer,\n pre: PreRenderer,\n table: TableRenderer,\n th: Th,\n tr: Tr,\n td: Td,\n hr: Hr,\n Tabs: TabsRenderer,\n Tab: Tab.Item,\n Columns: ColumnsRenderer,\n Column: ColumnRenderer,\n iframe: Iframe,\n};\n\n// Factory function to create components with dynamic props\nconst createMarkdownComponents = (\n isDarkMode?: boolean,\n locale?: LocalesValues\n) => ({\n ...staticMarkdownComponents,\n code: createCodeRenderer(isDarkMode),\n a: createLinkRenderer(locale),\n});\n\n// Export static renderers for backward compatibility\nexport const baseMarkdownComponents = staticMarkdownComponents;\n\ntype MarkdownRendererProps = {\n children: string | ParsedMarkdown;\n isDarkMode?: boolean;\n locale?: LocalesValues;\n forceBlock?: boolean;\n preserveFrontmatter?: boolean;\n tagfilter?: boolean;\n components?: ComponentProps<typeof MarkdownRendererIntlayer>['components'];\n wrapper?: ComponentProps<typeof MarkdownRendererIntlayer>['wrapper'];\n};\n\nexport const getIntlayerMarkdownOptions = (_isDarkMode?: boolean) => ({\n components: baseMarkdownComponents,\n});\n\nexport const MarkdownRenderer: FC<MarkdownRendererProps> = ({\n children,\n isDarkMode = false,\n locale,\n forceBlock,\n preserveFrontmatter,\n tagfilter,\n components: componentsProp,\n wrapper,\n}) => {\n const markdownComponents = createMarkdownComponents(isDarkMode, locale);\n\n const markdownContent = renderMarkdown(children, {\n components: {\n ...markdownComponents,\n ...componentsProp,\n },\n wrapper,\n forceBlock,\n preserveFrontmatter,\n tagfilter,\n });\n\n return (\n <CodeProvider>\n <TabProvider>{markdownContent}</TabProvider>\n </CodeProvider>\n );\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { cn } from "../../utils/cn.mjs";
|
|
4
|
+
import { EditorCommand, EditorCommandList } from "./novel/components/editor-command.mjs";
|
|
5
|
+
import { EditorContent, EditorRoot } from "./novel/components/editor.mjs";
|
|
6
|
+
import { EditorBubble } from "./novel/components/editor-bubble.mjs";
|
|
7
|
+
import { EditorCommandEmpty, EditorCommandItem } from "./novel/components/editor-command-item.mjs";
|
|
8
|
+
import { ImageResizer } from "./novel/extensions/image-resizer.mjs";
|
|
9
|
+
import { handleCommandNavigation } from "./novel/extensions/slash-command.mjs";
|
|
10
|
+
import { handleImageDrop, handleImagePaste } from "./novel/plugins/upload-images.mjs";
|
|
11
|
+
import { createEditorUploadFn } from "./imageUpload.mjs";
|
|
12
|
+
import { defaultExtensions } from "./extensions.mjs";
|
|
13
|
+
import { GenerativeMenuSwitch } from "./generative/GenerativeMenuSwitch.mjs";
|
|
14
|
+
import { ColorSelector } from "./selectors/ColorSelector.mjs";
|
|
15
|
+
import { LinkSelector } from "./selectors/LinkSelector.mjs";
|
|
16
|
+
import { NodeSelector } from "./selectors/NodeSelector.mjs";
|
|
17
|
+
import { TextButtons } from "./selectors/TextButtons.mjs";
|
|
18
|
+
import { createSlashCommand } from "./slashCommand.mjs";
|
|
19
|
+
import { useMemo, useState } from "react";
|
|
20
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
21
|
+
import { useIntlayer } from "react-intlayer";
|
|
22
|
+
|
|
23
|
+
//#region src/components/MarkdownEditor/MarkdownEditor.tsx
|
|
24
|
+
const Divider = () => /* @__PURE__ */ jsx("div", { className: "h-full w-px shrink-0 bg-border" });
|
|
25
|
+
/**
|
|
26
|
+
* Notion-style WYSIWYG markdown editor built on Novel / Tiptap.
|
|
27
|
+
*
|
|
28
|
+
* Features a slash command menu, a formatting bubble menu (node / link / color
|
|
29
|
+
* / inline marks), image upload (drop / paste / slash), and an optional
|
|
30
|
+
* AI assistant wired to the Intlayer AI backend.
|
|
31
|
+
*
|
|
32
|
+
* The value contract is markdown: pass `defaultValue` as a markdown string and
|
|
33
|
+
* read changes from `onChange`.
|
|
34
|
+
*/
|
|
35
|
+
const MarkdownEditor = ({ defaultValue, onChange, editable = true, enableAI = true, uploadImage, maxImageSizeMb, className }) => {
|
|
36
|
+
const [openAI, setOpenAI] = useState(false);
|
|
37
|
+
const content = useIntlayer("markdown-editor");
|
|
38
|
+
const uploadFn = useMemo(() => createEditorUploadFn({
|
|
39
|
+
uploadImage,
|
|
40
|
+
maxSizeMb: maxImageSizeMb
|
|
41
|
+
}), [uploadImage, maxImageSizeMb]);
|
|
42
|
+
const { extensions, suggestionItems } = useMemo(() => {
|
|
43
|
+
const { slashCommand, suggestionItems: items } = createSlashCommand(uploadFn, content);
|
|
44
|
+
return {
|
|
45
|
+
extensions: [...defaultExtensions, slashCommand],
|
|
46
|
+
suggestionItems: items
|
|
47
|
+
};
|
|
48
|
+
}, [uploadFn, content]);
|
|
49
|
+
const initialContent = defaultValue ?? "";
|
|
50
|
+
const handleUpdate = ({ editor }) => {
|
|
51
|
+
onChange?.(editor.storage.markdown.getMarkdown());
|
|
52
|
+
};
|
|
53
|
+
const toolbar = /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
54
|
+
/* @__PURE__ */ jsx(NodeSelector, {}),
|
|
55
|
+
/* @__PURE__ */ jsx(Divider, {}),
|
|
56
|
+
/* @__PURE__ */ jsx(LinkSelector, {}),
|
|
57
|
+
/* @__PURE__ */ jsx(Divider, {}),
|
|
58
|
+
/* @__PURE__ */ jsx(TextButtons, {}),
|
|
59
|
+
/* @__PURE__ */ jsx(Divider, {}),
|
|
60
|
+
/* @__PURE__ */ jsx(ColorSelector, {})
|
|
61
|
+
] });
|
|
62
|
+
return /* @__PURE__ */ jsx("div", {
|
|
63
|
+
className: "relative w-full",
|
|
64
|
+
children: /* @__PURE__ */ jsx(EditorRoot, { children: /* @__PURE__ */ jsxs(EditorContent, {
|
|
65
|
+
immediatelyRender: false,
|
|
66
|
+
editable,
|
|
67
|
+
initialContent,
|
|
68
|
+
extensions,
|
|
69
|
+
className: cn("min-h-[350px] w-full max-w-full", className),
|
|
70
|
+
editorProps: {
|
|
71
|
+
handleDOMEvents: { keydown: (_view, event) => handleCommandNavigation(event) },
|
|
72
|
+
handlePaste: (view, event) => handleImagePaste(view, event, uploadFn),
|
|
73
|
+
handleDrop: (view, event, _slice, moved) => handleImageDrop(view, event, moved, uploadFn),
|
|
74
|
+
attributes: { class: "max-w-full pl-6 focus:outline-none" }
|
|
75
|
+
},
|
|
76
|
+
onUpdate: handleUpdate,
|
|
77
|
+
slotAfter: /* @__PURE__ */ jsx(ImageResizer, {}),
|
|
78
|
+
children: [/* @__PURE__ */ jsxs(EditorCommand, {
|
|
79
|
+
className: "z-50 h-auto max-h-[330px] overflow-y-auto px-1 py-2 shadow-md transition-all",
|
|
80
|
+
children: [/* @__PURE__ */ jsx(EditorCommandEmpty, {
|
|
81
|
+
className: "px-2 text-muted-foreground",
|
|
82
|
+
children: content.noResults.value
|
|
83
|
+
}), /* @__PURE__ */ jsx(EditorCommandList, { children: suggestionItems.map((item) => /* @__PURE__ */ jsxs(EditorCommandItem, {
|
|
84
|
+
value: item.title,
|
|
85
|
+
onCommand: (val) => item.command?.(val),
|
|
86
|
+
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",
|
|
87
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
88
|
+
className: "flex size-10 items-center justify-center rounded-md border border-card bg-background",
|
|
89
|
+
children: item.icon
|
|
90
|
+
}), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("p", {
|
|
91
|
+
className: "font-medium",
|
|
92
|
+
children: item.title
|
|
93
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
94
|
+
className: "text-muted-foreground text-xs",
|
|
95
|
+
children: item.description
|
|
96
|
+
})] })]
|
|
97
|
+
}, item.title)) })]
|
|
98
|
+
}), enableAI ? /* @__PURE__ */ jsx(GenerativeMenuSwitch, {
|
|
99
|
+
open: openAI,
|
|
100
|
+
onOpenChange: setOpenAI,
|
|
101
|
+
children: toolbar
|
|
102
|
+
}) : /* @__PURE__ */ jsx(EditorBubble, {
|
|
103
|
+
options: { placement: "top" },
|
|
104
|
+
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",
|
|
105
|
+
children: toolbar
|
|
106
|
+
})]
|
|
107
|
+
}) })
|
|
108
|
+
});
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
//#endregion
|
|
112
|
+
export { MarkdownEditor };
|
|
113
|
+
//# sourceMappingURL=MarkdownEditor.mjs.map
|
|
@@ -0,0 +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 type JSONContent,\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 initialContent = (defaultValue ?? '') as unknown as JSONContent;\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={initialContent}\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 px-1 py-2 shadow-md 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":";;;;;;;;;;;;;;;;;;;;;;;AAgCA,MAAM,gBAAgB,oBAAC,OAAD,EAAK,WAAU,kCAAmC;;;;;;;;;;;AAgCxE,MAAa,kBAA2C,EACtD,cACA,UACA,WAAW,MACX,WAAW,MACX,aACA,gBACA,gBACI;CACJ,MAAM,CAAC,QAAQ,aAAa,SAAS,MAAM;CAC3C,MAAM,UAAU,YAAY,kBAAkB;CAE9C,MAAM,WAAW,cACT,qBAAqB;EAAE;EAAa,WAAW;EAAgB,CAAC,EACtE,CAAC,aAAa,eAAe,CAC9B;CAED,MAAM,EAAE,YAAY,oBAAoB,cAAc;EACpD,MAAM,EAAE,cAAc,iBAAiB,UAAU,mBAC/C,UACA,QACD;AAED,SAAO;GACL,YAAY,CAAC,GAAG,mBAAmB,aAAa;GAChD,iBAAiB;GAClB;IACA,CAAC,UAAU,QAAQ,CAAC;CAEvB,MAAM,iBAAkB,gBAAgB;CAExC,MAAM,gBAAgB,EAAE,aAAyC;AAC/D,aAAW,OAAO,QAAQ,SAAS,aAAa,CAAW;;CAG7D,MAAM,UACJ;EACE,oBAAC,cAAD,EAAgB;EAChB,oBAAC,SAAD,EAAW;EACX,oBAAC,cAAD,EAAgB;EAChB,oBAAC,SAAD,EAAW;EACX,oBAAC,aAAD,EAAe;EACf,oBAAC,SAAD,EAAW;EACX,oBAAC,eAAD,EAAiB;EAChB;AAGL,QACE,oBAAC,OAAD;EAAK,WAAU;YACb,oBAAC,YAAD,YACE,qBAAC,eAAD;GACE,mBAAmB;GACT;GACM;GACJ;GACZ,WAAW,GAAG,mCAAmC,UAAU;GAC3D,aAAa;IACX,iBAAiB,EACf,UAAU,OAAO,UAAU,wBAAwB,MAAM,EAC1D;IACD,cAAc,MAAM,UAClB,iBAAiB,MAAM,OAAO,SAAS;IACzC,aAAa,MAAM,OAAO,QAAQ,UAChC,gBAAgB,MAAM,OAAO,OAAO,SAAS;IAC/C,YAAY,EAIV,OAAO,sCACR;IACF;GACD,UAAU;GACV,WAAW,oBAAC,cAAD,EAAgB;aAtB7B,CAwBE,qBAAC,eAAD;IAAe,WAAU;cAAzB,CACE,oBAAC,oBAAD;KAAoB,WAAU;eAC3B,QAAQ,UAAU;KACA,GACrB,oBAAC,mBAAD,YACG,gBAAgB,KAAK,SACpB,qBAAC,mBAAD;KACE,OAAO,KAAK;KACZ,YAAY,QAAQ,KAAK,UAAU,IAAI;KACvC,WAAU;eAHZ,CAME,oBAAC,OAAD;MAAK,WAAU;gBACZ,KAAK;MACF,GACN,qBAAC,OAAD,aACE,oBAAC,KAAD;MAAG,WAAU;gBAAe,KAAK;MAAU,GAC3C,oBAAC,KAAD;MAAG,WAAU;gBACV,KAAK;MACJ,EACA,IACY;OAXb,KAAK,MAWQ,CACpB,EACgB,EACN;OAEf,WACC,oBAAC,sBAAD;IAAsB,MAAM;IAAQ,cAAc;cAC/C;IACoB,IAEvB,oBAAC,cAAD;IACE,SAAS,EAAE,WAAW,OAAO;IAC7B,WAAU;cAET;IACY,EAEH;MACL;EACT"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import CustomKeymap from "./novel/extensions/custom-keymap.mjs";
|
|
2
|
+
import UpdatedImage from "./novel/extensions/updated-image.mjs";
|
|
3
|
+
import { AIHighlight } from "./novel/extensions/ai-highlight.mjs";
|
|
4
|
+
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
|
+
import { UploadImagesPlugin } from "./novel/plugins/upload-images.mjs";
|
|
6
|
+
import { cx } from "class-variance-authority";
|
|
7
|
+
import { CodeBlockShiki } from "tiptap-extension-code-block-shiki";
|
|
8
|
+
|
|
9
|
+
//#region src/components/MarkdownEditor/extensions.ts
|
|
10
|
+
const aiHighlight = AIHighlight;
|
|
11
|
+
const tiptapLink = TiptapLink.configure({ HTMLAttributes: { class: cx("text-muted-foreground underline underline-offset-[3px] hover:text-primary transition-colors cursor-pointer") } });
|
|
12
|
+
const tiptapImage = TiptapImage.extend({ addProseMirrorPlugins() {
|
|
13
|
+
return [UploadImagesPlugin({ imageClass: cx("opacity-40 rounded-lg border border-stone-200") })];
|
|
14
|
+
} }).configure({
|
|
15
|
+
allowBase64: true,
|
|
16
|
+
HTMLAttributes: { class: cx("rounded-lg border border-muted") }
|
|
17
|
+
});
|
|
18
|
+
const updatedImage = UpdatedImage.configure({ HTMLAttributes: { class: cx("rounded-lg border border-muted") } });
|
|
19
|
+
const taskList = TaskList.configure({ HTMLAttributes: { class: cx("not-prose pl-2") } });
|
|
20
|
+
const taskItem = TaskItem.configure({
|
|
21
|
+
HTMLAttributes: { class: cx("flex gap-2 items-start my-4") },
|
|
22
|
+
nested: true
|
|
23
|
+
});
|
|
24
|
+
const horizontalRule = Horizontal.configure({ HTMLAttributes: { class: cx("mt-4 mb-6 border-t border-muted-foreground") } });
|
|
25
|
+
const starterKit = StarterKit.configure({
|
|
26
|
+
bulletList: { HTMLAttributes: { class: cx("list-disc list-outside leading-3 -mt-2") } },
|
|
27
|
+
orderedList: { HTMLAttributes: { class: cx("list-decimal list-outside leading-3 -mt-2") } },
|
|
28
|
+
listItem: { HTMLAttributes: { class: cx("leading-normal -mb-2") } },
|
|
29
|
+
blockquote: { HTMLAttributes: { class: cx("border-l-4 border-primary") } },
|
|
30
|
+
codeBlock: false,
|
|
31
|
+
code: { HTMLAttributes: {
|
|
32
|
+
class: cx("rounded-md bg-muted px-1.5 py-1 font-mono font-medium"),
|
|
33
|
+
spellcheck: "false"
|
|
34
|
+
} },
|
|
35
|
+
horizontalRule: false,
|
|
36
|
+
dropcursor: {
|
|
37
|
+
color: "#DBEAFE",
|
|
38
|
+
width: 4
|
|
39
|
+
},
|
|
40
|
+
gapcursor: false
|
|
41
|
+
});
|
|
42
|
+
/**
|
|
43
|
+
* Code block with syntax highlighting powered by shiki (the same highlighter
|
|
44
|
+
* the rest of the design-system uses in `IDE/CodeBlockShiki` and
|
|
45
|
+
* `MarkDownRender`). Languages are lazy-loaded on demand and the dual
|
|
46
|
+
* `github-light` / `github-dark` themes emit `--shiki-dark` CSS variables that
|
|
47
|
+
* `markdown-editor.css` toggles under `[data-theme="dark"]`.
|
|
48
|
+
*/
|
|
49
|
+
const codeBlock = CodeBlockShiki.configure({
|
|
50
|
+
defaultLanguage: null,
|
|
51
|
+
defaultTheme: "github-light",
|
|
52
|
+
themes: {
|
|
53
|
+
light: "github-light",
|
|
54
|
+
dark: "github-dark"
|
|
55
|
+
},
|
|
56
|
+
HTMLAttributes: { class: cx("rounded-md bg-muted text-muted-foreground border p-5 font-mono font-medium") }
|
|
57
|
+
});
|
|
58
|
+
const youtube = Youtube.configure({
|
|
59
|
+
HTMLAttributes: { class: cx("rounded-lg border border-muted") },
|
|
60
|
+
inline: false
|
|
61
|
+
});
|
|
62
|
+
const characterCount = CharacterCount.configure();
|
|
63
|
+
const markdownExtension = MarkdownExtension.configure({
|
|
64
|
+
html: true,
|
|
65
|
+
tightLists: true,
|
|
66
|
+
tightListClass: "tight",
|
|
67
|
+
bulletListMarker: "-",
|
|
68
|
+
linkify: false,
|
|
69
|
+
breaks: false,
|
|
70
|
+
transformPastedText: false,
|
|
71
|
+
transformCopiedText: false
|
|
72
|
+
});
|
|
73
|
+
/**
|
|
74
|
+
* Default Tiptap/Novel extensions powering the MarkdownEditor.
|
|
75
|
+
*
|
|
76
|
+
* The slash-command extension is intentionally not included here because it
|
|
77
|
+
* needs a runtime image-upload handler; it is appended by the editor itself.
|
|
78
|
+
*/
|
|
79
|
+
const defaultExtensions = [
|
|
80
|
+
starterKit,
|
|
81
|
+
PlaceholderExtension,
|
|
82
|
+
tiptapLink,
|
|
83
|
+
tiptapImage,
|
|
84
|
+
updatedImage,
|
|
85
|
+
taskList,
|
|
86
|
+
taskItem,
|
|
87
|
+
horizontalRule,
|
|
88
|
+
aiHighlight,
|
|
89
|
+
codeBlock,
|
|
90
|
+
youtube,
|
|
91
|
+
characterCount,
|
|
92
|
+
TiptapUnderline,
|
|
93
|
+
markdownExtension,
|
|
94
|
+
HighlightExtension,
|
|
95
|
+
TextStyle,
|
|
96
|
+
Color,
|
|
97
|
+
CustomKeymap,
|
|
98
|
+
GlobalDragHandle
|
|
99
|
+
];
|
|
100
|
+
|
|
101
|
+
//#endregion
|
|
102
|
+
export { defaultExtensions };
|
|
103
|
+
//# sourceMappingURL=extensions.mjs.map
|
|
@@ -0,0 +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,6GACD,EACF,EACF,CAAC;AAEF,MAAM,cAAc,YAAY,OAAO,EACrC,wBAAwB;AACtB,QAAO,CACL,mBAAmB,EACjB,YAAY,GAAG,gDAAgD,EAChE,CAAC,CACH;GAEJ,CAAC,CAAC,UAAU;CACX,aAAa;CACb,gBAAgB,EACd,OAAO,GAAG,iCAAiC,EAC5C;CACF,CAAC;AAEF,MAAM,eAAe,aAAa,UAAU,EAC1C,gBAAgB,EACd,OAAO,GAAG,iCAAiC,EAC5C,EACF,CAAC;AAEF,MAAM,WAAW,SAAS,UAAU,EAClC,gBAAgB,EACd,OAAO,GAAG,iBAAiB,EAC5B,EACF,CAAC;AAEF,MAAM,WAAW,SAAS,UAAU;CAClC,gBAAgB,EACd,OAAO,GAAG,8BAA8B,EACzC;CACD,QAAQ;CACT,CAAC;AAEF,MAAM,iBAAiBA,WAAe,UAAU,EAC9C,gBAAgB,EACd,OAAO,GAAG,6CAA6C,EACxD,EACF,CAAC;AAEF,MAAM,aAAa,WAAW,UAAU;CACtC,YAAY,EACV,gBAAgB,EACd,OAAO,GAAG,yCAAyC,EACpD,EACF;CACD,aAAa,EACX,gBAAgB,EACd,OAAO,GAAG,4CAA4C,EACvD,EACF;CACD,UAAU,EACR,gBAAgB,EACd,OAAO,GAAG,uBAAuB,EAClC,EACF;CACD,YAAY,EACV,gBAAgB,EACd,OAAO,GAAG,4BAA4B,EACvC,EACF;CAED,WAAW;CACX,MAAM,EACJ,gBAAgB;EACd,OAAO,GAAG,wDAAwD;EAClE,YAAY;EACb,EACF;CACD,gBAAgB;CAChB,YAAY;EACV,OAAO;EACP,OAAO;EACR;CACD,WAAW;CACZ,CAAC;;;;;;;;AASF,MAAM,YAAY,eAAe,UAAU;CACzC,iBAAiB;CACjB,cAAc;CACd,QAAQ;EACN,OAAO;EACP,MAAM;EACP;CACD,gBAAgB,EACd,OAAO,GACL,6EACD,EACF;CACF,CAAC;AAEF,MAAM,UAAU,QAAQ,UAAU;CAChC,gBAAgB,EACd,OAAO,GAAG,iCAAiC,EAC5C;CACD,QAAQ;CACT,CAAC;AAEF,MAAM,iBAAiB,eAAe,WAAW;AAEjD,MAAM,oBAAoB,kBAAkB,UAAU;CACpD,MAAM;CACN,YAAY;CACZ,gBAAgB;CAChB,kBAAkB;CAClB,SAAS;CACT,QAAQ;CACR,qBAAqB;CACrB,qBAAqB;CACtB,CAAC;;;;;;;AAQF,MAAa,oBAAoB;CAC/B;CACAC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Command } from "../../Command/index.mjs";
|
|
4
|
+
import { useEditor } from "../novel/components/index.mjs";
|
|
5
|
+
import { Check, TextQuote, TrashIcon } from "lucide-react";
|
|
6
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
import { useIntlayer } from "react-intlayer";
|
|
8
|
+
|
|
9
|
+
//#region src/components/MarkdownEditor/generative/AICompletionCommands.tsx
|
|
10
|
+
/**
|
|
11
|
+
* Command list shown once the AI has produced a completion: replace the
|
|
12
|
+
* selection, insert below, or discard.
|
|
13
|
+
*/
|
|
14
|
+
const AICompletionCommands = ({ completion, onDiscard }) => {
|
|
15
|
+
const { editor } = useEditor();
|
|
16
|
+
const content = useIntlayer("markdown-editor");
|
|
17
|
+
if (!editor) return null;
|
|
18
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
19
|
+
/* @__PURE__ */ jsxs(Command.Group, { children: [/* @__PURE__ */ jsxs(Command.Item, {
|
|
20
|
+
value: "replace",
|
|
21
|
+
className: "gap-2 px-4",
|
|
22
|
+
onSelect: () => {
|
|
23
|
+
const { selection } = editor.view.state;
|
|
24
|
+
editor.chain().focus().insertContentAt({
|
|
25
|
+
from: selection.from,
|
|
26
|
+
to: selection.to
|
|
27
|
+
}, completion).run();
|
|
28
|
+
},
|
|
29
|
+
children: [/* @__PURE__ */ jsx(Check, { className: "size-4 text-muted-foreground" }), content.replaceSelection]
|
|
30
|
+
}), /* @__PURE__ */ jsxs(Command.Item, {
|
|
31
|
+
value: "insert",
|
|
32
|
+
className: "gap-2 px-4",
|
|
33
|
+
onSelect: () => {
|
|
34
|
+
const { selection } = editor.view.state;
|
|
35
|
+
editor.chain().focus().insertContentAt(selection.to + 1, completion).run();
|
|
36
|
+
},
|
|
37
|
+
children: [/* @__PURE__ */ jsx(TextQuote, { className: "size-4 text-muted-foreground" }), content.insertBelow]
|
|
38
|
+
})] }),
|
|
39
|
+
/* @__PURE__ */ jsx(Command.Separator, {}),
|
|
40
|
+
/* @__PURE__ */ jsx(Command.Group, { children: /* @__PURE__ */ jsxs(Command.Item, {
|
|
41
|
+
value: "discard",
|
|
42
|
+
className: "gap-2 px-4",
|
|
43
|
+
onSelect: onDiscard,
|
|
44
|
+
children: [/* @__PURE__ */ jsx(TrashIcon, { className: "size-4 text-muted-foreground" }), content.discard]
|
|
45
|
+
}) })
|
|
46
|
+
] });
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
//#endregion
|
|
50
|
+
export { AICompletionCommands };
|
|
51
|
+
//# sourceMappingURL=AICompletionCommands.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AICompletionCommands.mjs","names":[],"sources":["../../../../../src/components/MarkdownEditor/generative/AICompletionCommands.tsx"],"sourcesContent":["'use client';\n\nimport { Command } from '@components/Command';\nimport { Check, TextQuote, TrashIcon } from 'lucide-react';\nimport { useIntlayer } from 'react-intlayer';\nimport { useEditor } from '../novel';\n\nexport type AICompletionCommandsProps = {\n completion: string;\n onDiscard: () => void;\n};\n\n/**\n * Command list shown once the AI has produced a completion: replace the\n * selection, insert below, or discard.\n */\nexport const AICompletionCommands = ({\n completion,\n onDiscard,\n}: AICompletionCommandsProps) => {\n const { editor } = useEditor();\n const content = useIntlayer('markdown-editor');\n\n if (!editor) return null;\n\n return (\n <>\n <Command.Group>\n <Command.Item\n value=\"replace\"\n className=\"gap-2 px-4\"\n onSelect={() => {\n const { selection } = editor.view.state;\n editor\n .chain()\n .focus()\n .insertContentAt(\n { from: selection.from, to: selection.to },\n completion\n )\n .run();\n }}\n >\n <Check className=\"size-4 text-muted-foreground\" />\n {content.replaceSelection}\n </Command.Item>\n <Command.Item\n value=\"insert\"\n className=\"gap-2 px-4\"\n onSelect={() => {\n const { selection } = editor.view.state;\n editor\n .chain()\n .focus()\n .insertContentAt(selection.to + 1, completion)\n .run();\n }}\n >\n <TextQuote className=\"size-4 text-muted-foreground\" />\n {content.insertBelow}\n </Command.Item>\n </Command.Group>\n <Command.Separator />\n <Command.Group>\n <Command.Item\n value=\"discard\"\n className=\"gap-2 px-4\"\n onSelect={onDiscard}\n >\n <TrashIcon className=\"size-4 text-muted-foreground\" />\n {content.discard}\n </Command.Item>\n </Command.Group>\n </>\n );\n};\n"],"mappings":";;;;;;;;;;;;;AAgBA,MAAa,wBAAwB,EACnC,YACA,gBAC+B;CAC/B,MAAM,EAAE,WAAW,WAAW;CAC9B,MAAM,UAAU,YAAY,kBAAkB;AAE9C,KAAI,CAAC,OAAQ,QAAO;AAEpB,QACE;EACE,qBAAC,QAAQ,OAAT,aACE,qBAAC,QAAQ,MAAT;GACE,OAAM;GACN,WAAU;GACV,gBAAgB;IACd,MAAM,EAAE,cAAc,OAAO,KAAK;AAClC,WACG,OAAO,CACP,OAAO,CACP,gBACC;KAAE,MAAM,UAAU;KAAM,IAAI,UAAU;KAAI,EAC1C,WACD,CACA,KAAK;;aAZZ,CAeE,oBAAC,OAAD,EAAO,WAAU,gCAAiC,GACjD,QAAQ,iBACI;MACf,qBAAC,QAAQ,MAAT;GACE,OAAM;GACN,WAAU;GACV,gBAAgB;IACd,MAAM,EAAE,cAAc,OAAO,KAAK;AAClC,WACG,OAAO,CACP,OAAO,CACP,gBAAgB,UAAU,KAAK,GAAG,WAAW,CAC7C,KAAK;;aATZ,CAYE,oBAAC,WAAD,EAAW,WAAU,gCAAiC,GACrD,QAAQ,YACI;KACD;EAChB,oBAAC,QAAQ,WAAT,EAAqB;EACrB,oBAAC,QAAQ,OAAT,YACE,qBAAC,QAAQ,MAAT;GACE,OAAM;GACN,WAAU;GACV,UAAU;aAHZ,CAKE,oBAAC,WAAD,EAAW,WAAU,gCAAiC,GACrD,QAAQ,QACI;MACD;EACf"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { toast } from "../../Toaster/useToast.mjs";
|
|
4
|
+
import { Loader } from "../../Loader/index.mjs";
|
|
5
|
+
import { Button } from "../../Button/Button.mjs";
|
|
6
|
+
import { Command, CommandRoot } from "../../Command/index.mjs";
|
|
7
|
+
import { MarkdownRenderer as MarkdownRenderer$1 } from "../../MarkDownRender/MarkDownRender.mjs";
|
|
8
|
+
import { useEditor } from "../novel/components/index.mjs";
|
|
9
|
+
import { addAIHighlight } from "../novel/extensions/ai-highlight.mjs";
|
|
10
|
+
import { AICompletionCommands } from "./AICompletionCommands.mjs";
|
|
11
|
+
import { AISelectorCommands } from "./AISelectorCommands.mjs";
|
|
12
|
+
import { useAICompletion } from "./useAICompletion.mjs";
|
|
13
|
+
import { useState } from "react";
|
|
14
|
+
import { ArrowUp, Sparkles } from "lucide-react";
|
|
15
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
16
|
+
import { useIntlayer } from "react-intlayer";
|
|
17
|
+
|
|
18
|
+
//#region src/components/MarkdownEditor/generative/AISelector.tsx
|
|
19
|
+
/**
|
|
20
|
+
* The AI command palette shown inside the bubble menu. Lets the user run a
|
|
21
|
+
* preset transformation or a free-form prompt against the current selection,
|
|
22
|
+
* preview the markdown result, then replace / insert / discard it.
|
|
23
|
+
*/
|
|
24
|
+
const AISelector = ({ onOpenChange, sendButtonVariant = "default", sendButtonColor = "text" }) => {
|
|
25
|
+
const { editor } = useEditor();
|
|
26
|
+
const [inputValue, setInputValue] = useState("");
|
|
27
|
+
const content = useIntlayer("markdown-editor");
|
|
28
|
+
const { completion, complete, isLoading, reset } = useAICompletion((error) => toast({
|
|
29
|
+
variant: "error",
|
|
30
|
+
title: content.aiError.value,
|
|
31
|
+
description: error.message
|
|
32
|
+
}));
|
|
33
|
+
if (!editor) return null;
|
|
34
|
+
const hasCompletion = completion.length > 0;
|
|
35
|
+
const getSelectionMarkdown = () => {
|
|
36
|
+
const slice = editor.state.selection.content();
|
|
37
|
+
return editor.storage.markdown.serializer.serialize(slice.content);
|
|
38
|
+
};
|
|
39
|
+
return /* @__PURE__ */ jsxs(CommandRoot, {
|
|
40
|
+
className: "w-[350px]",
|
|
41
|
+
children: [
|
|
42
|
+
hasCompletion && /* @__PURE__ */ jsx("div", {
|
|
43
|
+
className: "flex max-h-[400px]",
|
|
44
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
45
|
+
className: "relative overflow-y-auto",
|
|
46
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
47
|
+
className: "prose prose-sm dark:prose-invert p-2 px-4",
|
|
48
|
+
children: /* @__PURE__ */ jsx(MarkdownRenderer$1, { children: completion })
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
}),
|
|
52
|
+
isLoading && /* @__PURE__ */ jsxs("div", {
|
|
53
|
+
className: "flex h-12 w-full items-center px-4 font-medium text-sm text-text",
|
|
54
|
+
children: [
|
|
55
|
+
/* @__PURE__ */ jsx(Sparkles, { className: "mr-2 size-4 shrink-0" }),
|
|
56
|
+
content.aiThinking,
|
|
57
|
+
/* @__PURE__ */ jsx("div", {
|
|
58
|
+
className: "mt-1 ml-2",
|
|
59
|
+
children: /* @__PURE__ */ jsx(Loader, {})
|
|
60
|
+
})
|
|
61
|
+
]
|
|
62
|
+
}),
|
|
63
|
+
!isLoading && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("div", {
|
|
64
|
+
className: "flex items-center gap-2 px-3 py-3",
|
|
65
|
+
children: [/* @__PURE__ */ jsx(Command.Input, {
|
|
66
|
+
value: inputValue,
|
|
67
|
+
onValueChange: setInputValue,
|
|
68
|
+
autoFocus: true,
|
|
69
|
+
placeholder: hasCompletion ? content.tellAI.value : content.askAIToEdit.value,
|
|
70
|
+
onFocus: () => addAIHighlight(editor)
|
|
71
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
72
|
+
label: content.send.value,
|
|
73
|
+
size: "icon-sm",
|
|
74
|
+
roundedSize: "full",
|
|
75
|
+
variant: sendButtonVariant,
|
|
76
|
+
color: sendButtonColor,
|
|
77
|
+
className: "size-7 shrink-0",
|
|
78
|
+
Icon: ArrowUp,
|
|
79
|
+
onClick: () => {
|
|
80
|
+
complete(completion || getSelectionMarkdown(), { body: {
|
|
81
|
+
option: "zap",
|
|
82
|
+
command: inputValue
|
|
83
|
+
} }).then(() => setInputValue(""));
|
|
84
|
+
}
|
|
85
|
+
})]
|
|
86
|
+
}), hasCompletion ? /* @__PURE__ */ jsx(AICompletionCommands, {
|
|
87
|
+
completion,
|
|
88
|
+
onDiscard: () => {
|
|
89
|
+
editor.chain().unsetHighlight().focus().run();
|
|
90
|
+
reset();
|
|
91
|
+
onOpenChange(false);
|
|
92
|
+
}
|
|
93
|
+
}) : /* @__PURE__ */ jsx(AISelectorCommands, { onSelect: (value, option) => complete(value, { body: { option } }) })] })
|
|
94
|
+
]
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
//#endregion
|
|
99
|
+
export { AISelector };
|
|
100
|
+
//# sourceMappingURL=AISelector.mjs.map
|
|
@@ -0,0 +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,WAAW;CAC9B,MAAM,CAAC,YAAY,iBAAiB,SAAS,GAAG;CAChD,MAAM,UAAU,YAAY,kBAAkB;CAE9C,MAAM,EAAE,YAAY,UAAU,WAAW,UAAU,iBAAiB,UAClE,MAAM;EACJ,SAAS;EACT,OAAO,QAAQ,QAAQ;EACvB,aAAa,MAAM;EACpB,CAAC,CACH;AAED,KAAI,CAAC,OAAQ,QAAO;CAEpB,MAAM,gBAAgB,WAAW,SAAS;CAE1C,MAAM,6BAA6B;EACjC,MAAM,QAAQ,OAAO,MAAM,UAAU,SAAS;AAC9C,SAAO,OAAO,QAAQ,SAAS,WAAW,UACxC,MAAM,QACP;;AAGH,QACE,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,YAA8B;MAC7C;KACF;IACF;GAGP,aACC,qBAAC,OAAD;IAAK,WAAU;cAAf;KACE,oBAAC,UAAD,EAAU,WAAU,wBAAyB;KAC5C,QAAQ;KACT,oBAAC,OAAD;MAAK,WAAU;gBACb,oBAAC,QAAD,EAAU;MACN;KACF;;GAGP,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,OAAO;KACrC,GACF,oBAAC,QAAD;KACE,OAAO,QAAQ,KAAK;KACpB,MAAK;KACL,aAAY;KACZ,SAAS;KACT,OAAO;KACP,WAAU;KACV,MAAM;KACN,eAAe;AAEb,eADa,cAAc,sBAAsB,EAClC,EACb,MAAM;OAAE,QAAQ;OAAO,SAAS;OAAY,EAC7C,CAAC,CAAC,WAAW,cAAc,GAAG,CAAC;;KAElC,EACE;OACL,gBACC,oBAAC,sBAAD;IACc;IACZ,iBAAiB;AACf,YAAO,OAAO,CAAC,gBAAgB,CAAC,OAAO,CAAC,KAAK;AAC7C,YAAO;AACP,kBAAa,MAAM;;IAErB,IAEF,oBAAC,oBAAD,EACE,WAAW,OAAO,WAChB,SAAS,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,EAEvC,EAEH;GAEO"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Command } from "../../Command/index.mjs";
|
|
4
|
+
import { useEditor } from "../novel/components/index.mjs";
|
|
5
|
+
import { getPrevText } from "../novel/utils/index.mjs";
|
|
6
|
+
import { ArrowDownWideNarrow, CheckCheck, RefreshCcwDot, StepForward, WrapText } from "lucide-react";
|
|
7
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
import { useIntlayer } from "react-intlayer";
|
|
9
|
+
|
|
10
|
+
//#region src/components/MarkdownEditor/generative/AISelectorCommands.tsx
|
|
11
|
+
const options = [
|
|
12
|
+
{
|
|
13
|
+
value: "improve",
|
|
14
|
+
label: "Improve writing",
|
|
15
|
+
icon: RefreshCcwDot
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
value: "fix",
|
|
19
|
+
label: "Fix grammar",
|
|
20
|
+
icon: CheckCheck
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
value: "shorter",
|
|
24
|
+
label: "Make shorter",
|
|
25
|
+
icon: ArrowDownWideNarrow
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
value: "longer",
|
|
29
|
+
label: "Make longer",
|
|
30
|
+
icon: WrapText
|
|
31
|
+
}
|
|
32
|
+
];
|
|
33
|
+
/**
|
|
34
|
+
* The default command list shown in the AI bubble before a completion exists:
|
|
35
|
+
* transforms applied to the current selection plus "continue writing".
|
|
36
|
+
*/
|
|
37
|
+
const AISelectorCommands = ({ onSelect }) => {
|
|
38
|
+
const { editor } = useEditor();
|
|
39
|
+
const content = useIntlayer("markdown-editor");
|
|
40
|
+
if (!editor) return null;
|
|
41
|
+
const getOptionLabel = (value) => {
|
|
42
|
+
switch (value) {
|
|
43
|
+
case "improve": return content.improveWriting.value;
|
|
44
|
+
case "fix": return content.fixGrammar.value;
|
|
45
|
+
case "shorter": return content.makeShorter.value;
|
|
46
|
+
case "longer": return content.makeLonger.value;
|
|
47
|
+
default: return value;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
51
|
+
/* @__PURE__ */ jsx(Command.Group, {
|
|
52
|
+
heading: content.editHeading.value,
|
|
53
|
+
children: options.map((option) => /* @__PURE__ */ jsxs(Command.Item, {
|
|
54
|
+
value: option.value,
|
|
55
|
+
className: "flex gap-2 px-4",
|
|
56
|
+
onSelect: (value) => {
|
|
57
|
+
const slice = editor.state.selection.content();
|
|
58
|
+
onSelect(editor.storage.markdown.serializer.serialize(slice.content), value);
|
|
59
|
+
},
|
|
60
|
+
children: [/* @__PURE__ */ jsx(option.icon, { className: "size-4 text-text" }), getOptionLabel(option.value)]
|
|
61
|
+
}, option.value))
|
|
62
|
+
}),
|
|
63
|
+
/* @__PURE__ */ jsx(Command.Separator, {}),
|
|
64
|
+
/* @__PURE__ */ jsx(Command.Group, {
|
|
65
|
+
heading: content.moreHeading.value,
|
|
66
|
+
children: /* @__PURE__ */ jsxs(Command.Item, {
|
|
67
|
+
value: "continue",
|
|
68
|
+
className: "gap-2 px-4",
|
|
69
|
+
onSelect: () => {
|
|
70
|
+
const pos = editor.state.selection.from;
|
|
71
|
+
onSelect(getPrevText(editor, pos), "continue");
|
|
72
|
+
},
|
|
73
|
+
children: [/* @__PURE__ */ jsx(StepForward, { className: "size-4 text-text" }), content.continueWriting]
|
|
74
|
+
})
|
|
75
|
+
})
|
|
76
|
+
] });
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
//#endregion
|
|
80
|
+
export { AISelectorCommands };
|
|
81
|
+
//# sourceMappingURL=AISelectorCommands.mjs.map
|
|
@@ -0,0 +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;EAAe;CACnE;EAAE,OAAO;EAAO,OAAO;EAAe,MAAM;EAAY;CACxD;EAAE,OAAO;EAAW,OAAO;EAAgB,MAAM;EAAqB;CACtE;EAAE,OAAO;EAAU,OAAO;EAAe,MAAM;EAAU;CAC1D;;;;;AAUD,MAAa,sBAAsB,EAAE,eAAwC;CAC3E,MAAM,EAAE,WAAW,WAAW;CAC9B,MAAM,UAAU,YAAY,kBAAkB;AAE9C,KAAI,CAAC,OAAQ,QAAO;CAEpB,MAAM,kBAAkB,UAAkB;AACxC,UAAQ,OAAR;GACE,KAAK,UACH,QAAO,QAAQ,eAAe;GAChC,KAAK,MACH,QAAO,QAAQ,WAAW;GAC5B,KAAK,UACH,QAAO,QAAQ,YAAY;GAC7B,KAAK,SACH,QAAO,QAAQ,WAAW;GAC5B,QACE,QAAO;;;AAIb,QACE;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,SAAS;AAI9C,cAHa,OAAO,QAAQ,SAAS,WAAW,UAC9C,MAAM,QAEK,EAAE,MAA4B;;cAT/C,CAYE,oBAAC,OAAO,MAAR,EAAa,WAAU,oBAAqB,GAC3C,eAAe,OAAO,MAAM,CAChB;MAbR,OAAO,MAaC,CACf;GACY;EAChB,oBAAC,QAAQ,WAAT,EAAqB;EACrB,oBAAC,QAAQ,OAAT;GAAe,SAAS,QAAQ,YAAY;aAC1C,qBAAC,QAAQ,MAAT;IACE,OAAM;IACN,WAAU;IACV,gBAAgB;KACd,MAAM,MAAM,OAAO,MAAM,UAAU;AAEnC,cADa,YAAY,QAAQ,IACpB,EAAE,WAAW;;cAN9B,CASE,oBAAC,aAAD,EAAa,WAAU,oBAAqB,GAC3C,QAAQ,gBACI;;GACD;EACf"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Button } from "../../Button/Button.mjs";
|
|
4
|
+
import { EditorBubble } from "../novel/components/editor-bubble.mjs";
|
|
5
|
+
import { useEditor } from "../novel/components/index.mjs";
|
|
6
|
+
import { removeAIHighlight } from "../novel/extensions/ai-highlight.mjs";
|
|
7
|
+
import { AISelector } from "./AISelector.mjs";
|
|
8
|
+
import { Fragment, useEffect } from "react";
|
|
9
|
+
import { Sparkles } from "lucide-react";
|
|
10
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
11
|
+
import { useIntlayer } from "react-intlayer";
|
|
12
|
+
|
|
13
|
+
//#region src/components/MarkdownEditor/generative/GenerativeMenuSwitch.tsx
|
|
14
|
+
/**
|
|
15
|
+
* Bubble menu that toggles between the regular formatting toolbar (its
|
|
16
|
+
* `children`) and the AI command palette.
|
|
17
|
+
*/
|
|
18
|
+
const GenerativeMenuSwitch = ({ children, open, onOpenChange, sendButtonVariant, sendButtonColor }) => {
|
|
19
|
+
const { editor } = useEditor();
|
|
20
|
+
const content = useIntlayer("markdown-editor");
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
if (!open && editor) removeAIHighlight(editor);
|
|
23
|
+
}, [open, editor]);
|
|
24
|
+
return /* @__PURE__ */ jsxs(EditorBubble, {
|
|
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",
|
|
27
|
+
children: [open && /* @__PURE__ */ jsx(AISelector, {
|
|
28
|
+
onOpenChange,
|
|
29
|
+
sendButtonVariant,
|
|
30
|
+
sendButtonColor
|
|
31
|
+
}), !open && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Button, {
|
|
32
|
+
label: content.askAI.value,
|
|
33
|
+
variant: "hoverable",
|
|
34
|
+
color: "text",
|
|
35
|
+
size: "sm",
|
|
36
|
+
roundedSize: "none",
|
|
37
|
+
className: "gap-1 text-text",
|
|
38
|
+
Icon: Sparkles,
|
|
39
|
+
onClick: () => onOpenChange(true),
|
|
40
|
+
children: content.askAI
|
|
41
|
+
}), children] })]
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
//#endregion
|
|
46
|
+
export { GenerativeMenuSwitch };
|
|
47
|
+
//# sourceMappingURL=GenerativeMenuSwitch.mjs.map
|
|
@@ -0,0 +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,WAAW;CAC9B,MAAM,UAAU,YAAY,kBAAkB;AAE9C,iBAAgB;AACd,MAAI,CAAC,QAAQ,OAAQ,mBAAkB,OAAO;IAC7C,CAAC,MAAM,OAAO,CAAC;AAElB,QACE,qBAAC,cAAD;EACE,SAAS,EAAE,WAAW,OAAO,iBAAiB,OAAO;EACrD,WAAU;YAFZ,CAIG,QACC,oBAAC,YAAD;GACgB;GACK;GACF;GACjB,GAEH,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,KAAK;aAEhC,QAAQ;GACF,GACR,SACQ,IAEA"}
|