@djangocfg/ui-tools 2.1.417 → 2.1.419
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/audio-player/index.cjs +1 -2
- package/dist/audio-player/index.cjs.map +1 -1
- package/dist/audio-player/index.d.cts +3 -11
- package/dist/audio-player/index.d.ts +3 -11
- package/dist/audio-player/index.mjs +1 -2
- package/dist/audio-player/index.mjs.map +1 -1
- package/dist/file-icon/index.cjs +3 -3
- package/dist/file-icon/index.cjs.map +1 -1
- package/dist/file-icon/index.mjs +3 -3
- package/dist/file-icon/index.mjs.map +1 -1
- package/dist/tree/index.cjs +0 -3
- package/dist/tree/index.cjs.map +1 -1
- package/dist/tree/index.mjs +0 -3
- package/dist/tree/index.mjs.map +1 -1
- package/package.json +117 -36
- package/src/common/FloatingToolbar/actions/CopyAction.tsx +31 -0
- package/src/{components → common}/FloatingToolbar/actions/DownloadAction.tsx +15 -10
- package/src/common/FloatingToolbar/actions/ExpandAction.tsx +33 -0
- package/src/common/FloatingToolbar/actions/FullscreenAction.tsx +38 -0
- package/src/{components → common}/FloatingToolbar/index.tsx +39 -0
- package/src/lib/http.ts +64 -0
- package/src/tools/chat/index.ts +1 -1
- package/src/tools/chat/launcher/ChatFAB.tsx +66 -74
- package/src/tools/chat/launcher/header/ChatHeaderActionButton.tsx +2 -3
- package/src/tools/chat/lazy.tsx +1 -1
- package/src/tools/chat/messages/MessageBubble.tsx +1 -1
- package/src/tools/chat/messages/blocks/builtin.tsx +1 -1
- package/src/tools/chat/messages/blocks/renderers/CodeBlock.tsx +2 -2
- package/src/tools/chat/messages/blocks/renderers/JsonBlock.tsx +12 -1
- package/src/tools/data/DataGrid/lazy.tsx +1 -1
- package/src/tools/data/DataTable/lazy.tsx +1 -1
- package/src/tools/data/JsonTree/JsonViewer.tsx +720 -0
- package/src/tools/data/JsonTree/README.md +126 -73
- package/src/tools/data/JsonTree/index.tsx +3 -95
- package/src/tools/data/JsonTree/lazy.tsx +10 -50
- package/src/tools/data/JsonTree/types.ts +82 -63
- package/src/tools/data/Kanban/lazy.tsx +1 -1
- package/src/tools/data/Listbox/lazy.tsx +1 -1
- package/src/tools/data/Masonry/lazy.tsx +1 -1
- package/src/tools/data/Timeline/lazy.tsx +1 -1
- package/src/tools/data/Tree/components/TreeRow.tsx +0 -11
- package/src/tools/data/Tree/lazy.tsx +1 -1
- package/src/tools/dev/Map/lazy.tsx +1 -1
- package/src/tools/dev/Mermaid/Mermaid.client.tsx +2 -2
- package/src/tools/dev/Mermaid/lazy.tsx +1 -1
- package/src/tools/dev/api/ApiRefTable/ApiRefTable.tsx +65 -0
- package/src/tools/dev/api/ApiRefTable/README.md +31 -0
- package/src/tools/dev/api/ApiRefTable/components/Row.tsx +96 -0
- package/src/tools/dev/api/ApiRefTable/components/TypeDisplay.tsx +44 -0
- package/src/tools/dev/api/ApiRefTable/index.ts +6 -0
- package/src/tools/dev/api/ApiRefTable/lazy.tsx +21 -0
- package/src/tools/dev/api/ApiRefTable/types.ts +82 -0
- package/src/tools/dev/api/ApiRefTable/utils.ts +42 -0
- package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/ApiIntroSection.tsx +1 -1
- package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/CodeSamples/index.tsx +1 -1
- package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/Header/index.tsx +1 -1
- package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/Responses/ResponseBody.tsx +7 -21
- package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/RequestPanel.tsx +1 -1
- package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/ResponsePanel/PrettyView.tsx +13 -19
- package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/ResponsePanel/types.ts +1 -1
- package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/lazy.tsx +1 -1
- package/src/tools/dev/api/RequestViewer/README.md +33 -0
- package/src/tools/dev/api/RequestViewer/RequestViewer.tsx +121 -0
- package/src/tools/dev/api/RequestViewer/components/BodyTab.tsx +44 -0
- package/src/tools/dev/api/RequestViewer/components/EmptyState.tsx +13 -0
- package/src/tools/dev/api/RequestViewer/components/HeadersTab.tsx +78 -0
- package/src/tools/dev/api/RequestViewer/components/TimingTab.tsx +113 -0
- package/src/tools/dev/api/RequestViewer/components/utils.ts +31 -0
- package/src/tools/dev/api/RequestViewer/index.ts +16 -0
- package/src/tools/dev/api/RequestViewer/lazy.tsx +30 -0
- package/src/tools/dev/api/RequestViewer/types.ts +81 -0
- package/src/tools/dev/code/DiffViewer/DiffViewer.tsx +144 -0
- package/src/tools/dev/code/DiffViewer/README.md +33 -0
- package/src/tools/dev/code/DiffViewer/components/CopyButton.tsx +49 -0
- package/src/tools/dev/code/DiffViewer/components/DiffLineContent.tsx +48 -0
- package/src/tools/dev/code/DiffViewer/components/SplitView.tsx +220 -0
- package/src/tools/dev/code/DiffViewer/components/UnifiedView.tsx +154 -0
- package/src/tools/dev/code/DiffViewer/hooks/useDiff.ts +47 -0
- package/src/tools/dev/code/DiffViewer/hooks/useHighlighter.ts +54 -0
- package/src/tools/dev/code/DiffViewer/index.ts +22 -0
- package/src/tools/dev/code/DiffViewer/lazy.tsx +22 -0
- package/src/tools/dev/code/DiffViewer/types.ts +109 -0
- package/src/tools/dev/code/DiffViewer/utils/computeDiff.ts +159 -0
- package/src/tools/dev/{MarkdownMessage → code/MarkdownMessage}/CollapseToggle.tsx +1 -1
- package/src/tools/dev/{MarkdownMessage → code/MarkdownMessage}/MarkdownMessage.tsx +1 -1
- package/src/tools/dev/{MarkdownMessage → code/MarkdownMessage}/components.tsx +2 -2
- package/src/tools/dev/{PrettyCode → code/PrettyCode}/PrettyCode.client.tsx +2 -2
- package/src/tools/dev/{PrettyCode → code/PrettyCode}/lazy.tsx +1 -1
- package/src/tools/dev/ops/EnvTable/EnvTable.tsx +228 -0
- package/src/tools/dev/ops/EnvTable/README.md +29 -0
- package/src/tools/dev/ops/EnvTable/hooks/useEnvMask.ts +121 -0
- package/src/tools/dev/ops/EnvTable/index.ts +12 -0
- package/src/tools/dev/ops/EnvTable/lazy.tsx +21 -0
- package/src/tools/dev/ops/EnvTable/types.ts +76 -0
- package/src/tools/dev/ops/LogViewer/LogViewer.tsx +194 -0
- package/src/tools/dev/ops/LogViewer/README.md +30 -0
- package/src/tools/dev/ops/LogViewer/components/LogRow.tsx +151 -0
- package/src/tools/dev/ops/LogViewer/components/Toolbar.tsx +199 -0
- package/src/tools/dev/ops/LogViewer/hooks/useAutoScroll.ts +68 -0
- package/src/tools/dev/ops/LogViewer/hooks/useLogFilter.ts +58 -0
- package/src/tools/dev/ops/LogViewer/index.ts +18 -0
- package/src/tools/dev/ops/LogViewer/lazy.tsx +25 -0
- package/src/tools/dev/ops/LogViewer/types.ts +142 -0
- package/src/tools/dev/ops/LogViewer/utils/ansi.ts +231 -0
- package/src/tools/forms/CodeEditor/components/Editor.tsx +19 -0
- package/src/tools/forms/CodeEditor/hooks/useEditorTheme.ts +13 -73
- package/src/tools/forms/CodeEditor/lazy.tsx +1 -1
- package/src/tools/forms/CodeEditor/types/index.ts +7 -0
- package/src/tools/forms/FileUpload/lazy.tsx +1 -1
- package/src/tools/forms/JsonEditor/JsonEditor.tsx +115 -0
- package/src/tools/forms/JsonEditor/index.ts +1 -0
- package/src/tools/forms/JsonEditor/lazy.tsx +24 -0
- package/src/tools/forms/JsonForm/index.ts +1 -1
- package/src/tools/forms/JsonForm/lazy.tsx +1 -1
- package/src/tools/forms/MarkdownEditor/MarkdownEditor.tsx +40 -0
- package/src/tools/forms/MarkdownEditor/lazy.tsx +1 -1
- package/src/tools/forms/MarkdownEditor/styles.css +174 -21
- package/src/tools/forms/NotionEditor/CustomKeymap.ts +48 -0
- package/src/tools/forms/NotionEditor/LinkDialog.tsx +133 -0
- package/src/tools/forms/NotionEditor/NotionEditor.tsx +304 -0
- package/src/tools/forms/NotionEditor/README.md +237 -0
- package/src/tools/forms/NotionEditor/SlashExtension.ts +32 -0
- package/src/tools/forms/NotionEditor/SlashList.tsx +136 -0
- package/src/tools/forms/NotionEditor/TaskItemView.tsx +41 -0
- package/src/tools/forms/NotionEditor/createSlashSuggestion.ts +121 -0
- package/src/tools/forms/NotionEditor/extensions.ts +105 -0
- package/src/tools/forms/NotionEditor/index.ts +1 -0
- package/src/tools/forms/NotionEditor/lazy.tsx +44 -0
- package/src/tools/forms/NotionEditor/slashItems.ts +159 -0
- package/src/tools/forms/NotionEditor/styles.css +478 -0
- package/src/tools/forms/NotionEditor/types.ts +28 -0
- package/src/tools/index.ts +153 -13
- package/src/tools/input/Combobox/lazy.tsx +1 -1
- package/src/tools/input/CronScheduler/components/CronPreview.README.md +28 -0
- package/src/tools/input/CronScheduler/components/CronPreview.tsx +136 -0
- package/src/tools/input/CronScheduler/components/index.ts +3 -0
- package/src/tools/input/CronScheduler/index.tsx +5 -1
- package/src/tools/input/CronScheduler/lazy.tsx +5 -1
- package/src/tools/input/CronScheduler/utils/cron-next-runs.ts +122 -0
- package/src/tools/input/CronScheduler/utils/index.ts +1 -0
- package/src/tools/input/Scroller/lazy.tsx +1 -1
- package/src/tools/input/Sortable/lazy.tsx +1 -1
- package/src/tools/input/SpeechRecognition/lazy.tsx +1 -1
- package/src/tools/input/SpeechRecognition/widgets/VoiceComposerSlot.tsx +41 -36
- package/src/tools/media/AudioPlayer/PlayerShell.tsx +3 -11
- package/src/tools/media/AudioPlayer/types.ts +4 -11
- package/src/tools/media/ImageViewer/components/ImageToolbar.tsx +58 -47
- package/src/tools/media/ImageViewer/components/ImageViewer.tsx +35 -19
- package/src/tools/media/ImageViewer/lazy.tsx +1 -1
- package/src/tools/media/ImageViewer/types.ts +4 -0
- package/src/tools/media/LottiePlayer/lazy.tsx +1 -1
- package/src/tools/media/VideoPlayer/VideoPlayer.tsx +47 -1
- package/src/tools/media/VideoPlayer/parts/fullscreen.tsx +21 -4
- package/src/tools/media/VideoPlayer/parts/pip.tsx +21 -4
- package/src/tools/media/VideoPlayer/parts/play-button.tsx +21 -4
- package/src/tools/media/VideoPlayer/parts/playback-rate.tsx +19 -3
- package/src/tools/media/VideoPlayer/parts/volume.tsx +237 -18
- package/src/tools/media/VideoPlayer/styles/video-player.css +87 -7
- package/src/tools/media/VideoPlayer/types.ts +4 -0
- package/src/tools/overlay/ResponsiveDialog/lazy.tsx +1 -1
- package/src/tools/overlay/ScrollSpy/lazy.tsx +1 -1
- package/src/tools/overlay/SelectionToolbar/lazy.tsx +1 -1
- package/src/tools/overlay/Tour/lazy.tsx +1 -1
- package/src/tools/visual/Marquee/lazy.tsx +1 -1
- package/src/tools/visual/QRCode/lazy.tsx +1 -1
- package/src/tools/visual/charts/ActivityGraph/ActivityGraph.tsx +195 -0
- package/src/tools/visual/charts/ActivityGraph/README.md +28 -0
- package/src/tools/visual/charts/ActivityGraph/index.ts +8 -0
- package/src/tools/visual/charts/ActivityGraph/lazy.tsx +21 -0
- package/src/tools/visual/charts/ActivityGraph/types.ts +59 -0
- package/src/tools/visual/charts/ActivityGraph/utils.ts +88 -0
- package/src/tools/visual/charts/CommitGraph/CommitGraph.tsx +80 -0
- package/src/tools/visual/charts/CommitGraph/README.md +28 -0
- package/src/tools/visual/charts/CommitGraph/components/CommitDetail.tsx +107 -0
- package/src/tools/visual/charts/CommitGraph/components/CommitRow.tsx +122 -0
- package/src/tools/visual/charts/CommitGraph/components/Rails.tsx +171 -0
- package/src/tools/visual/charts/CommitGraph/hooks/useGraphLayout.ts +169 -0
- package/src/tools/visual/charts/CommitGraph/hooks/useLaneColors.ts +45 -0
- package/src/tools/visual/charts/CommitGraph/index.ts +14 -0
- package/src/tools/visual/charts/CommitGraph/lazy.tsx +25 -0
- package/src/tools/visual/charts/CommitGraph/types.ts +85 -0
- package/src/tools/visual/charts/CommitGraph/utils.ts +53 -0
- package/src/tools/visual/{Gauge → charts/Gauge}/lazy.tsx +1 -1
- package/src/tools/visual/charts/Sparkline/README.md +29 -0
- package/src/tools/visual/charts/Sparkline/Sparkline.tsx +217 -0
- package/src/tools/visual/charts/Sparkline/index.ts +9 -0
- package/src/tools/visual/charts/Sparkline/lazy.tsx +26 -0
- package/src/tools/visual/charts/Sparkline/types.ts +58 -0
- package/src/tools/visual/design/ColorPalette/ColorPalette.tsx +129 -0
- package/src/tools/visual/design/ColorPalette/README.md +34 -0
- package/src/tools/visual/design/ColorPalette/components/Swatch.tsx +102 -0
- package/src/tools/visual/design/ColorPalette/hooks/useCopyToClipboard.ts +41 -0
- package/src/tools/visual/design/ColorPalette/index.ts +12 -0
- package/src/tools/visual/design/ColorPalette/lazy.tsx +21 -0
- package/src/tools/visual/design/ColorPalette/types.ts +63 -0
- package/src/tools/visual/design/ColorPalette/utils.ts +83 -0
- package/src/tools/visual/{ColorPicker → design/ColorPicker}/lazy.tsx +1 -1
- package/src/tools/visual/{FileIcon → design/FileIcon}/treeAdapter.tsx +1 -1
- package/src/tools/visual/{Fps → indicators/Fps}/lazy.tsx +1 -1
- package/src/tools/visual/{Rating → indicators/Rating}/lazy.tsx +1 -1
- package/src/tools/visual/indicators/StatusIndicator/README.md +28 -0
- package/src/tools/visual/indicators/StatusIndicator/StatusIndicator.tsx +83 -0
- package/src/tools/visual/indicators/StatusIndicator/index.ts +14 -0
- package/src/tools/visual/indicators/StatusIndicator/lazy.tsx +21 -0
- package/src/tools/visual/indicators/StatusIndicator/types.ts +133 -0
- package/src/components/FloatingToolbar/actions/CopyAction.tsx +0 -22
- package/src/components/FloatingToolbar/actions/ExpandAction.tsx +0 -25
- package/src/components/FloatingToolbar/actions/FullscreenAction.tsx +0 -30
- package/src/tools/data/JsonTree/components/JsonContent.tsx +0 -197
- package/src/tools/data/JsonTree/hooks/useJsonExpand.ts +0 -50
- /package/src/{components → common}/FloatingToolbar/FloatingToolbar.css +0 -0
- /package/src/{components → common}/FloatingToolbar/actions/index.ts +0 -0
- /package/src/{components → common}/FloatingToolbar/hooks/useElementCorner.ts +0 -0
- /package/src/{components → common}/FloatingToolbar/hooks/useScrollIsolation.ts +0 -0
- /package/src/{components → common}/index.ts +0 -0
- /package/src/{components → common}/lazy-wrapper.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/README.md +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/DocsView.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/CodeSamples/LanguageTabs.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/CodeSamples/useCodeSnippet.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/Header/MetaActions.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/Header/MethodBadge.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/Header/PathDisplay.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/Parameters/ParamGroup.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/Parameters/ParamRow.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/Parameters/index.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/RequestBody/index.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/Responses/ResponseRow.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/Responses/StatusTag.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/Responses/index.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/SchemaFields/FieldRow.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/SchemaFields/buildTree.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/SchemaFields/index.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/SchemaFields/types.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/Section/SectionHeader.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/Section/defaults.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/Section/index.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/context.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/hooks/useSectionHash.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/index.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/store/index.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/store/selectors.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/types.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/SchemaCopyMenu.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/Sidebar/BrandHeader.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/Sidebar/CategoryBlock.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/Sidebar/EndpointRow.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/Sidebar/SchemaSection.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/Sidebar/SearchInput.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/Sidebar/SidebarBody.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/Sidebar/Toolbar.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/Sidebar/buildVM.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/Sidebar/index.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/Sidebar/types.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/Sidebar/useDebouncedValue.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/SlideInPlayground.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/TryItSheet.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/anchor.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/grouping.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/index.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/sidebarLabel.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/index.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/BodyFormEditor.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/EndpointDraftSync.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/EndpointResetButton.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/ResponsePanel/PreviewView.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/ResponsePanel/RawView.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/ResponsePanel/StatusBar.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/ResponsePanel/ViewTabs.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/ResponsePanel/detectContent.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/ResponsePanel/index.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/ResponsePanel/useResponseView.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/SendButton.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/ui.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/constants.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/context/PlaygroundContext.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/hooks/index.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/hooks/useDocsUrlSync.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/hooks/useEndpointDraft.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/hooks/useMobile.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/hooks/useOpenApiSchema.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/index.tsx +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/types.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/utils/apiKeyManager.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/utils/codeSamples.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/utils/formatters.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/utils/index.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/utils/operationToHar.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/utils/sampler.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/utils/schemaExport.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/utils/scrollParent.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/utils/url.ts +0 -0
- /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/utils/versionManager.ts +0 -0
- /package/src/tools/dev/{MarkdownMessage → code/MarkdownMessage}/ActionRow.tsx +0 -0
- /package/src/tools/dev/{MarkdownMessage → code/MarkdownMessage}/ChatMessageRow.tsx +0 -0
- /package/src/tools/dev/{MarkdownMessage → code/MarkdownMessage}/CodeBlock.tsx +0 -0
- /package/src/tools/dev/{MarkdownMessage → code/MarkdownMessage}/README.md +0 -0
- /package/src/tools/dev/{MarkdownMessage → code/MarkdownMessage}/index.ts +0 -0
- /package/src/tools/dev/{MarkdownMessage → code/MarkdownMessage}/linkRules.ts +0 -0
- /package/src/tools/dev/{MarkdownMessage → code/MarkdownMessage}/plainText.ts +0 -0
- /package/src/tools/dev/{MarkdownMessage → code/MarkdownMessage}/sanitize.ts +0 -0
- /package/src/tools/dev/{MarkdownMessage → code/MarkdownMessage}/types.ts +0 -0
- /package/src/tools/dev/{PrettyCode → code/PrettyCode}/README.md +0 -0
- /package/src/tools/dev/{PrettyCode → code/PrettyCode}/index.tsx +0 -0
- /package/src/tools/dev/{PrettyCode → code/PrettyCode}/registerPrismLanguages.ts +0 -0
- /package/src/tools/visual/{Gauge → charts/Gauge}/Gauge.tsx +0 -0
- /package/src/tools/visual/{Gauge → charts/Gauge}/index.ts +0 -0
- /package/src/tools/visual/{Gauge → charts/Gauge}/types.ts +0 -0
- /package/src/tools/visual/{ColorPicker → design/ColorPicker}/ColorPicker.tsx +0 -0
- /package/src/tools/visual/{ColorPicker → design/ColorPicker}/context/ColorPickerContext.tsx +0 -0
- /package/src/tools/visual/{ColorPicker → design/ColorPicker}/context/ColorPickerStore.tsx +0 -0
- /package/src/tools/visual/{ColorPicker → design/ColorPicker}/context/index.ts +0 -0
- /package/src/tools/visual/{ColorPicker → design/ColorPicker}/index.ts +0 -0
- /package/src/tools/visual/{ColorPicker → design/ColorPicker}/lib/color-utils.ts +0 -0
- /package/src/tools/visual/{ColorPicker → design/ColorPicker}/parts/ColorPickerAlphaSlider.tsx +0 -0
- /package/src/tools/visual/{ColorPicker → design/ColorPicker}/parts/ColorPickerArea.tsx +0 -0
- /package/src/tools/visual/{ColorPicker → design/ColorPicker}/parts/ColorPickerEyeDropper.tsx +0 -0
- /package/src/tools/visual/{ColorPicker → design/ColorPicker}/parts/ColorPickerFormatSelect.tsx +0 -0
- /package/src/tools/visual/{ColorPicker → design/ColorPicker}/parts/ColorPickerHueSlider.tsx +0 -0
- /package/src/tools/visual/{ColorPicker → design/ColorPicker}/parts/ColorPickerInput.tsx +0 -0
- /package/src/tools/visual/{ColorPicker → design/ColorPicker}/parts/ColorPickerSwatch.tsx +0 -0
- /package/src/tools/visual/{ColorPicker → design/ColorPicker}/parts/index.ts +0 -0
- /package/src/tools/visual/{ColorPicker → design/ColorPicker}/types.ts +0 -0
- /package/src/tools/visual/{FileIcon → design/FileIcon}/FileIcon.tsx +0 -0
- /package/src/tools/visual/{FileIcon → design/FileIcon}/get-file-icon.ts +0 -0
- /package/src/tools/visual/{FileIcon → design/FileIcon}/icons/icon-data.ts +0 -0
- /package/src/tools/visual/{FileIcon → design/FileIcon}/index.ts +0 -0
- /package/src/tools/visual/{FileIcon → design/FileIcon}/loader.ts +0 -0
- /package/src/tools/visual/{FileIcon → design/FileIcon}/specialFolders.ts +0 -0
- /package/src/tools/visual/{Fps → indicators/Fps}/Fps.tsx +0 -0
- /package/src/tools/visual/{Fps → indicators/Fps}/index.ts +0 -0
- /package/src/tools/visual/{Fps → indicators/Fps}/types.ts +0 -0
- /package/src/tools/visual/{Rating → indicators/Rating}/Rating.tsx +0 -0
- /package/src/tools/visual/{Rating → indicators/Rating}/index.ts +0 -0
- /package/src/tools/visual/{Rating → indicators/Rating}/types.ts +0 -0
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* NotionEditor — Notion-style WYSIWYG built on TipTap v3.
|
|
5
|
+
*
|
|
6
|
+
* Composition (vs `MarkdownEditor`):
|
|
7
|
+
* - StarterKit (with H1-H4) + Placeholder + Markdown serialiser
|
|
8
|
+
* - TaskList + TaskItem (GFM `- [ ]`)
|
|
9
|
+
* - Table (+ Row/Header/Cell)
|
|
10
|
+
* - CodeBlockLowlight (syntax highlight via lowlight's `common` pack)
|
|
11
|
+
* - Highlight (`==text==`)
|
|
12
|
+
* - GlobalDragHandle (Notion-style block grabbers)
|
|
13
|
+
* - SlashExtension (own `/` menu — see SlashList.tsx)
|
|
14
|
+
* - BubbleMenu (floating selection toolbar)
|
|
15
|
+
*
|
|
16
|
+
* Why a separate component instead of a variant on MarkdownEditor:
|
|
17
|
+
* - Different baseline (no mentions, no slash-as-chip extension that
|
|
18
|
+
* the chat composer depends on).
|
|
19
|
+
* - ~30KB more deps (tables, lowlight, drag-handle) — would punish
|
|
20
|
+
* every chat composer mount if added to the shared editor.
|
|
21
|
+
* - Lazy chunk boundary stays clean — Skills / chat composer keep
|
|
22
|
+
* their slim TipTap; document-preview pulls the heavy stack.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { useEditor, EditorContent, type Editor } from '@tiptap/react';
|
|
26
|
+
import { BubbleMenu } from '@tiptap/react/menus';
|
|
27
|
+
import { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
|
|
28
|
+
import {
|
|
29
|
+
Bold,
|
|
30
|
+
Italic,
|
|
31
|
+
Strikethrough,
|
|
32
|
+
Code as CodeIcon,
|
|
33
|
+
Highlighter,
|
|
34
|
+
Link as LinkIcon,
|
|
35
|
+
Underline as UnderlineIcon,
|
|
36
|
+
type LucideIcon,
|
|
37
|
+
} from 'lucide-react';
|
|
38
|
+
import { Kbd, Tooltip, TooltipContent, TooltipTrigger } from '@djangocfg/ui-core';
|
|
39
|
+
import { useHotkey } from '@djangocfg/ui-core/hooks';
|
|
40
|
+
import { notionExtensions } from './extensions';
|
|
41
|
+
import { LinkDialog } from './LinkDialog';
|
|
42
|
+
import type { NotionEditorHandle, NotionEditorProps } from './types';
|
|
43
|
+
import './styles.css';
|
|
44
|
+
|
|
45
|
+
// Same markdown helper as MarkdownEditor — TipTap v3 augments Editor with
|
|
46
|
+
// `getMarkdown()` when @tiptap/markdown is registered.
|
|
47
|
+
interface MarkdownManager {
|
|
48
|
+
serialize: (json: Record<string, unknown>) => string;
|
|
49
|
+
}
|
|
50
|
+
function getMarkdown(editor: Editor): string {
|
|
51
|
+
const withMd = editor as Editor & { getMarkdown?: () => string };
|
|
52
|
+
if (typeof withMd.getMarkdown === 'function') return withMd.getMarkdown();
|
|
53
|
+
const storage = editor.storage.markdown as { manager?: MarkdownManager } | undefined;
|
|
54
|
+
if (!storage?.manager) return editor.getText();
|
|
55
|
+
return storage.manager.serialize(editor.getJSON());
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export const NotionEditor = forwardRef<NotionEditorHandle, NotionEditorProps>(
|
|
59
|
+
function NotionEditor(
|
|
60
|
+
{
|
|
61
|
+
value,
|
|
62
|
+
onChange,
|
|
63
|
+
placeholder = "Type '/' for commands…",
|
|
64
|
+
disabled = false,
|
|
65
|
+
autoFocus = false,
|
|
66
|
+
onSave,
|
|
67
|
+
className = '',
|
|
68
|
+
minHeight = 320,
|
|
69
|
+
},
|
|
70
|
+
ref,
|
|
71
|
+
) {
|
|
72
|
+
const isExternalUpdate = useRef(false);
|
|
73
|
+
const [linkDialogOpen, setLinkDialogOpen] = useState(false);
|
|
74
|
+
|
|
75
|
+
// Build extensions once. Placeholder is captured by closure on first
|
|
76
|
+
// render — same constraint as MarkdownEditor; mentions / slash items
|
|
77
|
+
// don't change at runtime here.
|
|
78
|
+
const extensions = useMemo(() => notionExtensions({ placeholder }), [placeholder]);
|
|
79
|
+
|
|
80
|
+
const editor = useEditor({
|
|
81
|
+
immediatelyRender: false,
|
|
82
|
+
editable: !disabled,
|
|
83
|
+
extensions,
|
|
84
|
+
content: value,
|
|
85
|
+
contentType: 'markdown',
|
|
86
|
+
onUpdate: ({ editor }) => {
|
|
87
|
+
if (isExternalUpdate.current) return;
|
|
88
|
+
onChange(getMarkdown(editor));
|
|
89
|
+
},
|
|
90
|
+
editorProps: {
|
|
91
|
+
attributes: {
|
|
92
|
+
class: 'notion-editor-content focus:outline-none',
|
|
93
|
+
style: `min-height: ${minHeight}px`,
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Sync external value → editor without looping back into onChange.
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
if (!editor) return;
|
|
101
|
+
const current = getMarkdown(editor);
|
|
102
|
+
if (current === value) return;
|
|
103
|
+
isExternalUpdate.current = true;
|
|
104
|
+
editor.commands.setContent(value, {
|
|
105
|
+
contentType: 'markdown',
|
|
106
|
+
emitUpdate: false,
|
|
107
|
+
});
|
|
108
|
+
isExternalUpdate.current = false;
|
|
109
|
+
}, [value, editor]);
|
|
110
|
+
|
|
111
|
+
useEffect(() => {
|
|
112
|
+
editor?.setEditable(!disabled);
|
|
113
|
+
}, [editor, disabled]);
|
|
114
|
+
|
|
115
|
+
// Declarative autoFocus.
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
if (!autoFocus || !editor) return;
|
|
118
|
+
editor.commands.focus('end');
|
|
119
|
+
}, [autoFocus, editor]);
|
|
120
|
+
|
|
121
|
+
// Cmd/Ctrl+S → save, scoped to the editor DOM via guard.
|
|
122
|
+
const onSaveRef = useRef(onSave);
|
|
123
|
+
onSaveRef.current = onSave;
|
|
124
|
+
useHotkey(
|
|
125
|
+
'mod+s',
|
|
126
|
+
() => {
|
|
127
|
+
const h = onSaveRef.current;
|
|
128
|
+
if (!h || !editor) return;
|
|
129
|
+
const dom = editor.view.dom;
|
|
130
|
+
const active = document.activeElement;
|
|
131
|
+
if (!active || !dom.contains(active)) return;
|
|
132
|
+
h(getMarkdown(editor));
|
|
133
|
+
},
|
|
134
|
+
{ enabled: !!editor && !!onSave },
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
// Cmd/Ctrl+K → link prompt. Same focus-scope guard so we don't
|
|
138
|
+
// collide with global command palettes higher up the tree.
|
|
139
|
+
useHotkey(
|
|
140
|
+
'mod+k',
|
|
141
|
+
() => {
|
|
142
|
+
if (!editor) return;
|
|
143
|
+
const dom = editor.view.dom;
|
|
144
|
+
const active = document.activeElement;
|
|
145
|
+
if (!active || !dom.contains(active)) return;
|
|
146
|
+
setLinkDialogOpen(true);
|
|
147
|
+
},
|
|
148
|
+
{ enabled: !!editor },
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
useImperativeHandle(
|
|
152
|
+
ref,
|
|
153
|
+
(): NotionEditorHandle => ({
|
|
154
|
+
focus: () => {
|
|
155
|
+
editor?.commands.focus();
|
|
156
|
+
},
|
|
157
|
+
moveCursorToEnd: () => {
|
|
158
|
+
editor?.commands.focus('end');
|
|
159
|
+
},
|
|
160
|
+
getEditor: () => editor ?? null,
|
|
161
|
+
}),
|
|
162
|
+
[editor],
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
return (
|
|
166
|
+
<div className={`notion-editor ${disabled ? 'opacity-60' : ''} ${className}`.trim()}>
|
|
167
|
+
{editor ? (
|
|
168
|
+
<>
|
|
169
|
+
<BubbleSelectionToolbar
|
|
170
|
+
editor={editor}
|
|
171
|
+
onOpenLink={() => setLinkDialogOpen(true)}
|
|
172
|
+
/>
|
|
173
|
+
<LinkDialog
|
|
174
|
+
editor={editor}
|
|
175
|
+
open={linkDialogOpen}
|
|
176
|
+
onOpenChange={setLinkDialogOpen}
|
|
177
|
+
/>
|
|
178
|
+
</>
|
|
179
|
+
) : null}
|
|
180
|
+
<EditorContent editor={editor} />
|
|
181
|
+
</div>
|
|
182
|
+
);
|
|
183
|
+
},
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
interface BubbleItem {
|
|
187
|
+
icon: LucideIcon;
|
|
188
|
+
title: string;
|
|
189
|
+
/** Keyboard shortcut to display in the tooltip. macOS-style (⌘ B). */
|
|
190
|
+
shortcut: readonly string[];
|
|
191
|
+
isActive: () => boolean;
|
|
192
|
+
run: () => void;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Floating selection toolbar. `@tiptap/extension-bubble-menu` anchors
|
|
197
|
+
* itself to the current selection; it auto-hides when the selection
|
|
198
|
+
* collapses, so we only need to declare the buttons. Tooltips show the
|
|
199
|
+
* keyboard chord — Tiptap installs the shortcuts itself (StarterKit).
|
|
200
|
+
*/
|
|
201
|
+
function BubbleSelectionToolbar({
|
|
202
|
+
editor,
|
|
203
|
+
onOpenLink,
|
|
204
|
+
}: {
|
|
205
|
+
editor: Editor;
|
|
206
|
+
onOpenLink: () => void;
|
|
207
|
+
}) {
|
|
208
|
+
const items: BubbleItem[] = [
|
|
209
|
+
{
|
|
210
|
+
icon: Bold,
|
|
211
|
+
title: 'Bold',
|
|
212
|
+
shortcut: ['⌘', 'B'],
|
|
213
|
+
isActive: () => editor.isActive('bold'),
|
|
214
|
+
run: () => editor.chain().focus().toggleBold().run(),
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
icon: Italic,
|
|
218
|
+
title: 'Italic',
|
|
219
|
+
shortcut: ['⌘', 'I'],
|
|
220
|
+
isActive: () => editor.isActive('italic'),
|
|
221
|
+
run: () => editor.chain().focus().toggleItalic().run(),
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
icon: UnderlineIcon,
|
|
225
|
+
title: 'Underline',
|
|
226
|
+
shortcut: ['⌘', 'U'],
|
|
227
|
+
isActive: () => editor.isActive('underline'),
|
|
228
|
+
run: () => editor.chain().focus().toggleUnderline().run(),
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
icon: Strikethrough,
|
|
232
|
+
title: 'Strike',
|
|
233
|
+
shortcut: ['⌘', '⇧', 'X'],
|
|
234
|
+
isActive: () => editor.isActive('strike'),
|
|
235
|
+
run: () => editor.chain().focus().toggleStrike().run(),
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
icon: CodeIcon,
|
|
239
|
+
title: 'Inline code',
|
|
240
|
+
shortcut: ['⌘', 'E'],
|
|
241
|
+
isActive: () => editor.isActive('code'),
|
|
242
|
+
run: () => editor.chain().focus().toggleCode().run(),
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
icon: Highlighter,
|
|
246
|
+
title: 'Highlight',
|
|
247
|
+
shortcut: ['⌘', '⇧', 'H'],
|
|
248
|
+
isActive: () => editor.isActive('highlight'),
|
|
249
|
+
run: () => editor.chain().focus().toggleHighlight().run(),
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
icon: LinkIcon,
|
|
253
|
+
title: 'Link',
|
|
254
|
+
shortcut: ['⌘', 'K'],
|
|
255
|
+
isActive: () => editor.isActive('link'),
|
|
256
|
+
run: onOpenLink,
|
|
257
|
+
},
|
|
258
|
+
];
|
|
259
|
+
|
|
260
|
+
return (
|
|
261
|
+
<BubbleMenu
|
|
262
|
+
editor={editor}
|
|
263
|
+
className="notion-bubble-menu"
|
|
264
|
+
// Hide inside code blocks (Bold/Italic don't apply there) and when
|
|
265
|
+
// the selection is empty/whitespace-only. Default behaviour shows
|
|
266
|
+
// the menu on any non-empty range, which is too eager.
|
|
267
|
+
shouldShow={({ editor, from, to }) => {
|
|
268
|
+
if (from === to) return false;
|
|
269
|
+
if (editor.isActive('codeBlock')) return false;
|
|
270
|
+
const text = editor.state.doc.textBetween(from, to, ' ').trim();
|
|
271
|
+
return text.length > 0;
|
|
272
|
+
}}
|
|
273
|
+
>
|
|
274
|
+
{items.map((item) => {
|
|
275
|
+
const Icon = item.icon;
|
|
276
|
+
const active = item.isActive();
|
|
277
|
+
return (
|
|
278
|
+
<Tooltip key={item.title}>
|
|
279
|
+
<TooltipTrigger asChild>
|
|
280
|
+
<button
|
|
281
|
+
type="button"
|
|
282
|
+
aria-label={item.title}
|
|
283
|
+
aria-pressed={active}
|
|
284
|
+
onMouseDown={(e) => e.preventDefault()}
|
|
285
|
+
onClick={item.run}
|
|
286
|
+
className={`notion-bubble-btn${active ? ' notion-bubble-btn--active' : ''}`}
|
|
287
|
+
>
|
|
288
|
+
<Icon style={{ width: 14, height: 14 }} aria-hidden />
|
|
289
|
+
</button>
|
|
290
|
+
</TooltipTrigger>
|
|
291
|
+
<TooltipContent side="top" sideOffset={6} className="flex items-center gap-1.5">
|
|
292
|
+
<span>{item.title}</span>
|
|
293
|
+
<span className="flex items-center gap-0.5">
|
|
294
|
+
{item.shortcut.map((k) => (
|
|
295
|
+
<Kbd key={k}>{k}</Kbd>
|
|
296
|
+
))}
|
|
297
|
+
</span>
|
|
298
|
+
</TooltipContent>
|
|
299
|
+
</Tooltip>
|
|
300
|
+
);
|
|
301
|
+
})}
|
|
302
|
+
</BubbleMenu>
|
|
303
|
+
);
|
|
304
|
+
}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# NotionEditor
|
|
2
|
+
|
|
3
|
+
Notion-style WYSIWYG markdown editor built on **TipTap v3**.
|
|
4
|
+
|
|
5
|
+
Lazy-loaded sibling of [`MarkdownEditor`](../MarkdownEditor/) — same
|
|
6
|
+
markdown round-trip, but with a heavier extension stack and the
|
|
7
|
+
floating menus (slash / bubble / drag handle) that users expect from a
|
|
8
|
+
"document" editor.
|
|
9
|
+
|
|
10
|
+
```tsx
|
|
11
|
+
import { NotionEditor } from '@djangocfg/ui-tools/notion-editor';
|
|
12
|
+
|
|
13
|
+
<NotionEditor
|
|
14
|
+
value={markdown}
|
|
15
|
+
onChange={setMarkdown}
|
|
16
|
+
onSave={(md) => writeFile(md)}
|
|
17
|
+
autoFocus
|
|
18
|
+
placeholder="Type '/' for commands…"
|
|
19
|
+
/>
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## When to use this vs `MarkdownEditor`
|
|
23
|
+
|
|
24
|
+
| Use case | Pick |
|
|
25
|
+
| --------------------------------------- | ------------------- |
|
|
26
|
+
| Chat composer, single-paragraph input | `MarkdownEditor` |
|
|
27
|
+
| Note / doc surface, multi-paragraph | `NotionEditor` |
|
|
28
|
+
| `.md` file viewer with full editing | `NotionEditor` |
|
|
29
|
+
| Slim mention dropdown, no tables/tasks | `MarkdownEditor` |
|
|
30
|
+
| Slash menu, drag handle, tables, hljs | `NotionEditor` |
|
|
31
|
+
|
|
32
|
+
The two coexist intentionally — `NotionEditor` is ~350 KB minified
|
|
33
|
+
(lowlight common pack + tables + drag handle); `MarkdownEditor` is
|
|
34
|
+
~200 KB. Don't mount NotionEditor inside a chat composer just to get
|
|
35
|
+
features the user will never use.
|
|
36
|
+
|
|
37
|
+
## Features
|
|
38
|
+
|
|
39
|
+
- **Slash menu (`/`)** — 11 commands: Text, H1-H3, Bullet/Numbered/Todo
|
|
40
|
+
lists, Quote, Code block, Divider, Table. Each item shows its
|
|
41
|
+
markdown shorthand (`#`, `- [ ]`, etc.) as a kbd hint. Filtering by
|
|
42
|
+
title + aliases. `Esc` / click-outside close.
|
|
43
|
+
- **Bubble menu** — floating selection toolbar with Bold, Italic,
|
|
44
|
+
Underline, Strike, Code, Highlight, Link. Each button has a Tooltip
|
|
45
|
+
with the keyboard shortcut. Auto-hides inside code blocks and on
|
|
46
|
+
empty selections.
|
|
47
|
+
- **Cmd+K link prompt** — opens a Radix Dialog with URL input and
|
|
48
|
+
Save/Remove/Cancel. Snapshots and restores selection across the
|
|
49
|
+
dialog mount. Rejects unsafe schemes (`javascript:`, `data:`).
|
|
50
|
+
- **Drag handle** — Notion-style ⠿ grip appears on the left of every
|
|
51
|
+
block on hover (powered by `tiptap-extension-global-drag-handle`).
|
|
52
|
+
The icon is rendered as a `mask-image` of `lucide`'s `GripVertical`
|
|
53
|
+
so it matches the rest of the app at the pixel level.
|
|
54
|
+
- **Smart Cmd+A** — first press selects the current text block, second
|
|
55
|
+
press selects the whole document (Notion convention).
|
|
56
|
+
- **Heading-aware placeholder** — empty H1/H2/H3 show "Heading 1/2/3";
|
|
57
|
+
empty paragraphs show the supplied placeholder.
|
|
58
|
+
- **Forced-dark code blocks** — code blocks render on a fixed One Dark
|
|
59
|
+
Pro surface regardless of host theme. Avoids the washed-out
|
|
60
|
+
"light syntax on light page" look every editor hits.
|
|
61
|
+
- **Task list with ui-core `<Checkbox>`** — `taskItem` uses a React
|
|
62
|
+
`NodeView` so the checkbox is the same component used everywhere
|
|
63
|
+
else in the app (theme-aware, focus ring, etc.). Aligned to the
|
|
64
|
+
text cap-height via `align-items: baseline`.
|
|
65
|
+
- **Cmd+S save hook** — `onSave?: (md: string) => void` bound to
|
|
66
|
+
Cmd/Ctrl+S via `useHotkey`, scoped to the editor DOM so it doesn't
|
|
67
|
+
collide with global palettes higher up.
|
|
68
|
+
- **autoFocus** — declarative prop. Pair with `key={path}` upstream
|
|
69
|
+
when the parent wants a fresh focus per file change.
|
|
70
|
+
- **Markdown shortcuts** — StarterKit's input rules cover `#`, `>`,
|
|
71
|
+
`-`, `1.`, `` ``` ``, `---` → matching block on type.
|
|
72
|
+
|
|
73
|
+
## Extension stack
|
|
74
|
+
|
|
75
|
+
Built once per editor instance (see `extensions.ts`). Order matters
|
|
76
|
+
for markdown serialisation — `Markdown` is registered before
|
|
77
|
+
`Highlight`/`TaskList` so the latter use the markdown extension's
|
|
78
|
+
node spec when serialising.
|
|
79
|
+
|
|
80
|
+
- `StarterKit` — paragraph, headings 1-4, bold/italic/code/strike,
|
|
81
|
+
blockquote, ordered+unordered list, link, underline, horizontal
|
|
82
|
+
rule, history. CodeBlock is **disabled** here — replaced by
|
|
83
|
+
`CodeBlockLowlight` for syntax highlighting.
|
|
84
|
+
- `Placeholder` — heading-aware ghost text.
|
|
85
|
+
- `Markdown` — bidirectional serialisation via `@tiptap/markdown`.
|
|
86
|
+
Handles tables, task lists (`- [ ]`), highlight (`==text==`).
|
|
87
|
+
- `Highlight` — `==text==` mark.
|
|
88
|
+
- `TaskList` + `TaskItem.extend({ addNodeView: TaskItemView })`.
|
|
89
|
+
- `Table` + `TableRow` + `TableHeader` + `TableCell`.
|
|
90
|
+
- `CodeBlockLowlight` — uses `lowlight`'s `common` language pack
|
|
91
|
+
(~25 languages). Add more by importing them and registering on the
|
|
92
|
+
shared `lowlight` instance.
|
|
93
|
+
- `GlobalDragHandle` — block grabber. **One handle per page** — see
|
|
94
|
+
the "Known limitations" section below.
|
|
95
|
+
- `SlashExtension` — wraps `@tiptap/suggestion` for the `/` menu.
|
|
96
|
+
- `CustomKeymap` — smart Cmd+A.
|
|
97
|
+
|
|
98
|
+
## Files
|
|
99
|
+
|
|
100
|
+
| File | Purpose |
|
|
101
|
+
| ----------------------------- | ----------------------------------------------------------- |
|
|
102
|
+
| `lazy.tsx` | Subpath entry — `React.lazy` + `forwardRef` for the handle |
|
|
103
|
+
| `NotionEditor.tsx` | Main component + `BubbleSelectionToolbar` |
|
|
104
|
+
| `extensions.ts` | Assembled TipTap stack (factory `notionExtensions()`) |
|
|
105
|
+
| `types.ts` | `NotionEditorProps`, `NotionEditorHandle` |
|
|
106
|
+
| `SlashExtension.ts` | TipTap extension wrapping `@tiptap/suggestion` |
|
|
107
|
+
| `createSlashSuggestion.ts` | Suggestion config — floating-ui popup mount/update/teardown |
|
|
108
|
+
| `SlashList.tsx` | Popover content (listbox + keyboard nav) |
|
|
109
|
+
| `slashItems.ts` | Command list + `filterSlashItems` helper |
|
|
110
|
+
| `CustomKeymap.ts` | Smart Cmd+A extension |
|
|
111
|
+
| `TaskItemView.tsx` | React NodeView for `taskItem` (mounts ui-core Checkbox) |
|
|
112
|
+
| `LinkDialog.tsx` | Cmd+K link prompt (ui-core Dialog) |
|
|
113
|
+
| `styles.css` | Typography + slash/bubble menu + drag handle + lowlight |
|
|
114
|
+
|
|
115
|
+
## Imperative API
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
const ref = useRef<NotionEditorHandle>(null);
|
|
119
|
+
|
|
120
|
+
ref.current?.focus(); // focus the editor surface
|
|
121
|
+
ref.current?.moveCursorToEnd(); // focus + caret at end
|
|
122
|
+
ref.current?.getEditor(); // raw TipTap Editor (escape hatch)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
`NotionEditorHandle` is structurally compatible with `ComposerHandle`
|
|
126
|
+
from `@djangocfg/ui-tools/composer-registry` — the chat suite can
|
|
127
|
+
register a NotionEditor as a composer if you ever want to swap them in
|
|
128
|
+
the chat input (unusual, but allowed).
|
|
129
|
+
|
|
130
|
+
## Saving — dirty / pristine handling
|
|
131
|
+
|
|
132
|
+
The editor is **uncontrolled-ish**: it calls `onChange` on every
|
|
133
|
+
keystroke with the freshly-serialised markdown. The host owns state
|
|
134
|
+
and detects "dirty" however it likes.
|
|
135
|
+
|
|
136
|
+
Two gotchas the host needs to know about:
|
|
137
|
+
|
|
138
|
+
1. **TipTap re-serialises markdown on mount** (trailing newline,
|
|
139
|
+
CRLF → LF normalisation). The first `onChange` after
|
|
140
|
+
`setContent(value)` is byte-different from `value` even though
|
|
141
|
+
nobody typed. If you compare `draft !== savedContent` literally,
|
|
142
|
+
the dirty indicator lights up on every file open. **Fix in the
|
|
143
|
+
host**: normalise both sides before comparing.
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
const norm = (s: string) => s.replace(/\r\n/g, '\n').trimEnd();
|
|
147
|
+
const isDirty = norm(draft) !== norm(saved);
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
2. **Cmd+S only fires when focus is inside the editor**. If the user
|
|
151
|
+
clicked outside (header, sidebar) and presses Cmd+S, this hook
|
|
152
|
+
doesn't fire. Add a window-level `useHotkey('mod+s', save)` in the
|
|
153
|
+
host as a fallback — both paths call the same save fn.
|
|
154
|
+
|
|
155
|
+
See `cmdop/.../document-preview/viewers/text-viewer.tsx` for the
|
|
156
|
+
canonical wiring.
|
|
157
|
+
|
|
158
|
+
## Markdown round-trip
|
|
159
|
+
|
|
160
|
+
| Input | Round-trips? | Notes |
|
|
161
|
+
| ---------------- | ------------ | ---------------------------------- |
|
|
162
|
+
| Headings | ✓ | H1-H4 only (StarterKit config) |
|
|
163
|
+
| Bold/italic/etc. | ✓ | |
|
|
164
|
+
| Inline code | ✓ | |
|
|
165
|
+
| Code blocks | ✓ | Language fence preserved by hljs |
|
|
166
|
+
| Bullet/ordered | ✓ | Nested too |
|
|
167
|
+
| Task lists | ✓ | GFM `- [x]` / `- [ ]` |
|
|
168
|
+
| Blockquote | ✓ | |
|
|
169
|
+
| Horizontal rule | ✓ | `---` |
|
|
170
|
+
| Tables | ✓ | GFM tables — alignment via `:--:` |
|
|
171
|
+
| Links | ✓ | autolinks too |
|
|
172
|
+
| Highlight | ✓ | `==text==` extension syntax |
|
|
173
|
+
| Images | mostly | Round-trip URLs, no upload yet |
|
|
174
|
+
| HTML passthrough | ✗ | Stripped on parse |
|
|
175
|
+
| Footnotes | ✗ | Not in StarterKit |
|
|
176
|
+
|
|
177
|
+
## Known limitations
|
|
178
|
+
|
|
179
|
+
- **One editor per page (drag handle).**
|
|
180
|
+
`tiptap-extension-global-drag-handle` mints a single DOM grabber
|
|
181
|
+
attached to `document.body`. Mounting two `NotionEditor` instances
|
|
182
|
+
on the same page (e.g. a side-by-side diff) makes them fight over
|
|
183
|
+
the handle. If you need multiple editors, render only one with
|
|
184
|
+
drag-handle support.
|
|
185
|
+
|
|
186
|
+
- **Slash popup is portal'd to `document.body`.**
|
|
187
|
+
If the parent unmounts the editor mid-suggestion, the popup gets
|
|
188
|
+
cleaned up by ProseMirror's `onExit` — but a hostile race (route
|
|
189
|
+
change while menu is open) can leave an orphan div. Rare; visible
|
|
190
|
+
only via the inspector.
|
|
191
|
+
|
|
192
|
+
- **No image upload pipeline (yet).**
|
|
193
|
+
`Image` extension is not registered. Pasting an image from the
|
|
194
|
+
clipboard inserts the data-URL into the document (browser default)
|
|
195
|
+
but there's no upload integration. Add `@tiptap/extension-image` +
|
|
196
|
+
a custom paste handler when you have a target storage. See Novel's
|
|
197
|
+
`plugins/upload-images.tsx` for the canonical pattern.
|
|
198
|
+
|
|
199
|
+
- **Bubble menu uses `@tiptap/react/menus`'s `BubbleMenu`** (Tippy
|
|
200
|
+
under the hood). Slash menu uses `floating-ui` directly. Two
|
|
201
|
+
positioning libraries for one editor — kept this way because
|
|
202
|
+
`BubbleMenu` is opinionated about anchor invalidation and Tippy
|
|
203
|
+
handles it correctly; reimplementing on floating-ui would save
|
|
204
|
+
~5 KB but cost a week of edge cases.
|
|
205
|
+
|
|
206
|
+
## Storybook
|
|
207
|
+
|
|
208
|
+
Stories live at `apps/storybook/stories/ui-tools/markdown/NotionEditor.stories.tsx`:
|
|
209
|
+
|
|
210
|
+
- Default (empty editor with placeholder)
|
|
211
|
+
- With sample content (every supported block + serialised output panel)
|
|
212
|
+
- Auto-focus on mount
|
|
213
|
+
- Read-only
|
|
214
|
+
- Compact (240px min-height)
|
|
215
|
+
|
|
216
|
+
Each story renders a right-side panel showing the current serialised
|
|
217
|
+
markdown so you can watch the round-trip live as you type.
|
|
218
|
+
|
|
219
|
+
## Wiring inside a host
|
|
220
|
+
|
|
221
|
+
The canonical host is `cmdop`'s document-preview text viewer:
|
|
222
|
+
|
|
223
|
+
```tsx
|
|
224
|
+
// .md → NotionEditor only (it's already WYSIWYG, no Preview/Source split)
|
|
225
|
+
<div className="mx-auto w-full max-w-[820px] px-6 py-6">
|
|
226
|
+
<NotionEditor
|
|
227
|
+
value={draft}
|
|
228
|
+
onChange={handleChange}
|
|
229
|
+
onSave={save}
|
|
230
|
+
autoFocus
|
|
231
|
+
minHeight={420}
|
|
232
|
+
/>
|
|
233
|
+
</div>
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Use `key={path}` on the wrapper if you remount per file — that's what
|
|
237
|
+
makes `autoFocus` fire on each file open.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Extension } from '@tiptap/core';
|
|
2
|
+
import Suggestion, { type SuggestionOptions } from '@tiptap/suggestion';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Tiptap extension that wires `@tiptap/suggestion` for the slash menu.
|
|
6
|
+
* The actual suggestion config (items / render / floating-ui popup) is
|
|
7
|
+
* passed in via options — keeps this file UI-agnostic and lets the
|
|
8
|
+
* popover live in `createSlashSuggestion.ts`.
|
|
9
|
+
*/
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
11
|
+
export interface SlashExtensionOptions {
|
|
12
|
+
suggestion: Omit<SuggestionOptions<any>, 'editor'>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const SlashExtension = Extension.create<SlashExtensionOptions>({
|
|
16
|
+
name: 'notion-slash-command',
|
|
17
|
+
|
|
18
|
+
addOptions() {
|
|
19
|
+
return {
|
|
20
|
+
suggestion: { char: '/' } as Omit<SuggestionOptions<unknown>, 'editor'>,
|
|
21
|
+
};
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
addProseMirrorPlugins() {
|
|
25
|
+
return [
|
|
26
|
+
Suggestion({
|
|
27
|
+
editor: this.editor,
|
|
28
|
+
...this.options.suggestion,
|
|
29
|
+
}),
|
|
30
|
+
];
|
|
31
|
+
},
|
|
32
|
+
});
|