@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.
Files changed (335) hide show
  1. package/dist/audio-player/index.cjs +1 -2
  2. package/dist/audio-player/index.cjs.map +1 -1
  3. package/dist/audio-player/index.d.cts +3 -11
  4. package/dist/audio-player/index.d.ts +3 -11
  5. package/dist/audio-player/index.mjs +1 -2
  6. package/dist/audio-player/index.mjs.map +1 -1
  7. package/dist/file-icon/index.cjs +3 -3
  8. package/dist/file-icon/index.cjs.map +1 -1
  9. package/dist/file-icon/index.mjs +3 -3
  10. package/dist/file-icon/index.mjs.map +1 -1
  11. package/dist/tree/index.cjs +0 -3
  12. package/dist/tree/index.cjs.map +1 -1
  13. package/dist/tree/index.mjs +0 -3
  14. package/dist/tree/index.mjs.map +1 -1
  15. package/package.json +117 -36
  16. package/src/common/FloatingToolbar/actions/CopyAction.tsx +31 -0
  17. package/src/{components → common}/FloatingToolbar/actions/DownloadAction.tsx +15 -10
  18. package/src/common/FloatingToolbar/actions/ExpandAction.tsx +33 -0
  19. package/src/common/FloatingToolbar/actions/FullscreenAction.tsx +38 -0
  20. package/src/{components → common}/FloatingToolbar/index.tsx +39 -0
  21. package/src/lib/http.ts +64 -0
  22. package/src/tools/chat/index.ts +1 -1
  23. package/src/tools/chat/launcher/ChatFAB.tsx +66 -74
  24. package/src/tools/chat/launcher/header/ChatHeaderActionButton.tsx +2 -3
  25. package/src/tools/chat/lazy.tsx +1 -1
  26. package/src/tools/chat/messages/MessageBubble.tsx +1 -1
  27. package/src/tools/chat/messages/blocks/builtin.tsx +1 -1
  28. package/src/tools/chat/messages/blocks/renderers/CodeBlock.tsx +2 -2
  29. package/src/tools/chat/messages/blocks/renderers/JsonBlock.tsx +12 -1
  30. package/src/tools/data/DataGrid/lazy.tsx +1 -1
  31. package/src/tools/data/DataTable/lazy.tsx +1 -1
  32. package/src/tools/data/JsonTree/JsonViewer.tsx +720 -0
  33. package/src/tools/data/JsonTree/README.md +126 -73
  34. package/src/tools/data/JsonTree/index.tsx +3 -95
  35. package/src/tools/data/JsonTree/lazy.tsx +10 -50
  36. package/src/tools/data/JsonTree/types.ts +82 -63
  37. package/src/tools/data/Kanban/lazy.tsx +1 -1
  38. package/src/tools/data/Listbox/lazy.tsx +1 -1
  39. package/src/tools/data/Masonry/lazy.tsx +1 -1
  40. package/src/tools/data/Timeline/lazy.tsx +1 -1
  41. package/src/tools/data/Tree/components/TreeRow.tsx +0 -11
  42. package/src/tools/data/Tree/lazy.tsx +1 -1
  43. package/src/tools/dev/Map/lazy.tsx +1 -1
  44. package/src/tools/dev/Mermaid/Mermaid.client.tsx +2 -2
  45. package/src/tools/dev/Mermaid/lazy.tsx +1 -1
  46. package/src/tools/dev/api/ApiRefTable/ApiRefTable.tsx +65 -0
  47. package/src/tools/dev/api/ApiRefTable/README.md +31 -0
  48. package/src/tools/dev/api/ApiRefTable/components/Row.tsx +96 -0
  49. package/src/tools/dev/api/ApiRefTable/components/TypeDisplay.tsx +44 -0
  50. package/src/tools/dev/api/ApiRefTable/index.ts +6 -0
  51. package/src/tools/dev/api/ApiRefTable/lazy.tsx +21 -0
  52. package/src/tools/dev/api/ApiRefTable/types.ts +82 -0
  53. package/src/tools/dev/api/ApiRefTable/utils.ts +42 -0
  54. package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/ApiIntroSection.tsx +1 -1
  55. package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/CodeSamples/index.tsx +1 -1
  56. package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/Header/index.tsx +1 -1
  57. package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/Responses/ResponseBody.tsx +7 -21
  58. package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/RequestPanel.tsx +1 -1
  59. package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/ResponsePanel/PrettyView.tsx +13 -19
  60. package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/ResponsePanel/types.ts +1 -1
  61. package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/lazy.tsx +1 -1
  62. package/src/tools/dev/api/RequestViewer/README.md +33 -0
  63. package/src/tools/dev/api/RequestViewer/RequestViewer.tsx +121 -0
  64. package/src/tools/dev/api/RequestViewer/components/BodyTab.tsx +44 -0
  65. package/src/tools/dev/api/RequestViewer/components/EmptyState.tsx +13 -0
  66. package/src/tools/dev/api/RequestViewer/components/HeadersTab.tsx +78 -0
  67. package/src/tools/dev/api/RequestViewer/components/TimingTab.tsx +113 -0
  68. package/src/tools/dev/api/RequestViewer/components/utils.ts +31 -0
  69. package/src/tools/dev/api/RequestViewer/index.ts +16 -0
  70. package/src/tools/dev/api/RequestViewer/lazy.tsx +30 -0
  71. package/src/tools/dev/api/RequestViewer/types.ts +81 -0
  72. package/src/tools/dev/code/DiffViewer/DiffViewer.tsx +144 -0
  73. package/src/tools/dev/code/DiffViewer/README.md +33 -0
  74. package/src/tools/dev/code/DiffViewer/components/CopyButton.tsx +49 -0
  75. package/src/tools/dev/code/DiffViewer/components/DiffLineContent.tsx +48 -0
  76. package/src/tools/dev/code/DiffViewer/components/SplitView.tsx +220 -0
  77. package/src/tools/dev/code/DiffViewer/components/UnifiedView.tsx +154 -0
  78. package/src/tools/dev/code/DiffViewer/hooks/useDiff.ts +47 -0
  79. package/src/tools/dev/code/DiffViewer/hooks/useHighlighter.ts +54 -0
  80. package/src/tools/dev/code/DiffViewer/index.ts +22 -0
  81. package/src/tools/dev/code/DiffViewer/lazy.tsx +22 -0
  82. package/src/tools/dev/code/DiffViewer/types.ts +109 -0
  83. package/src/tools/dev/code/DiffViewer/utils/computeDiff.ts +159 -0
  84. package/src/tools/dev/{MarkdownMessage → code/MarkdownMessage}/CollapseToggle.tsx +1 -1
  85. package/src/tools/dev/{MarkdownMessage → code/MarkdownMessage}/MarkdownMessage.tsx +1 -1
  86. package/src/tools/dev/{MarkdownMessage → code/MarkdownMessage}/components.tsx +2 -2
  87. package/src/tools/dev/{PrettyCode → code/PrettyCode}/PrettyCode.client.tsx +2 -2
  88. package/src/tools/dev/{PrettyCode → code/PrettyCode}/lazy.tsx +1 -1
  89. package/src/tools/dev/ops/EnvTable/EnvTable.tsx +228 -0
  90. package/src/tools/dev/ops/EnvTable/README.md +29 -0
  91. package/src/tools/dev/ops/EnvTable/hooks/useEnvMask.ts +121 -0
  92. package/src/tools/dev/ops/EnvTable/index.ts +12 -0
  93. package/src/tools/dev/ops/EnvTable/lazy.tsx +21 -0
  94. package/src/tools/dev/ops/EnvTable/types.ts +76 -0
  95. package/src/tools/dev/ops/LogViewer/LogViewer.tsx +194 -0
  96. package/src/tools/dev/ops/LogViewer/README.md +30 -0
  97. package/src/tools/dev/ops/LogViewer/components/LogRow.tsx +151 -0
  98. package/src/tools/dev/ops/LogViewer/components/Toolbar.tsx +199 -0
  99. package/src/tools/dev/ops/LogViewer/hooks/useAutoScroll.ts +68 -0
  100. package/src/tools/dev/ops/LogViewer/hooks/useLogFilter.ts +58 -0
  101. package/src/tools/dev/ops/LogViewer/index.ts +18 -0
  102. package/src/tools/dev/ops/LogViewer/lazy.tsx +25 -0
  103. package/src/tools/dev/ops/LogViewer/types.ts +142 -0
  104. package/src/tools/dev/ops/LogViewer/utils/ansi.ts +231 -0
  105. package/src/tools/forms/CodeEditor/components/Editor.tsx +19 -0
  106. package/src/tools/forms/CodeEditor/hooks/useEditorTheme.ts +13 -73
  107. package/src/tools/forms/CodeEditor/lazy.tsx +1 -1
  108. package/src/tools/forms/CodeEditor/types/index.ts +7 -0
  109. package/src/tools/forms/FileUpload/lazy.tsx +1 -1
  110. package/src/tools/forms/JsonEditor/JsonEditor.tsx +115 -0
  111. package/src/tools/forms/JsonEditor/index.ts +1 -0
  112. package/src/tools/forms/JsonEditor/lazy.tsx +24 -0
  113. package/src/tools/forms/JsonForm/index.ts +1 -1
  114. package/src/tools/forms/JsonForm/lazy.tsx +1 -1
  115. package/src/tools/forms/MarkdownEditor/MarkdownEditor.tsx +40 -0
  116. package/src/tools/forms/MarkdownEditor/lazy.tsx +1 -1
  117. package/src/tools/forms/MarkdownEditor/styles.css +174 -21
  118. package/src/tools/forms/NotionEditor/CustomKeymap.ts +48 -0
  119. package/src/tools/forms/NotionEditor/LinkDialog.tsx +133 -0
  120. package/src/tools/forms/NotionEditor/NotionEditor.tsx +304 -0
  121. package/src/tools/forms/NotionEditor/README.md +237 -0
  122. package/src/tools/forms/NotionEditor/SlashExtension.ts +32 -0
  123. package/src/tools/forms/NotionEditor/SlashList.tsx +136 -0
  124. package/src/tools/forms/NotionEditor/TaskItemView.tsx +41 -0
  125. package/src/tools/forms/NotionEditor/createSlashSuggestion.ts +121 -0
  126. package/src/tools/forms/NotionEditor/extensions.ts +105 -0
  127. package/src/tools/forms/NotionEditor/index.ts +1 -0
  128. package/src/tools/forms/NotionEditor/lazy.tsx +44 -0
  129. package/src/tools/forms/NotionEditor/slashItems.ts +159 -0
  130. package/src/tools/forms/NotionEditor/styles.css +478 -0
  131. package/src/tools/forms/NotionEditor/types.ts +28 -0
  132. package/src/tools/index.ts +153 -13
  133. package/src/tools/input/Combobox/lazy.tsx +1 -1
  134. package/src/tools/input/CronScheduler/components/CronPreview.README.md +28 -0
  135. package/src/tools/input/CronScheduler/components/CronPreview.tsx +136 -0
  136. package/src/tools/input/CronScheduler/components/index.ts +3 -0
  137. package/src/tools/input/CronScheduler/index.tsx +5 -1
  138. package/src/tools/input/CronScheduler/lazy.tsx +5 -1
  139. package/src/tools/input/CronScheduler/utils/cron-next-runs.ts +122 -0
  140. package/src/tools/input/CronScheduler/utils/index.ts +1 -0
  141. package/src/tools/input/Scroller/lazy.tsx +1 -1
  142. package/src/tools/input/Sortable/lazy.tsx +1 -1
  143. package/src/tools/input/SpeechRecognition/lazy.tsx +1 -1
  144. package/src/tools/input/SpeechRecognition/widgets/VoiceComposerSlot.tsx +41 -36
  145. package/src/tools/media/AudioPlayer/PlayerShell.tsx +3 -11
  146. package/src/tools/media/AudioPlayer/types.ts +4 -11
  147. package/src/tools/media/ImageViewer/components/ImageToolbar.tsx +58 -47
  148. package/src/tools/media/ImageViewer/components/ImageViewer.tsx +35 -19
  149. package/src/tools/media/ImageViewer/lazy.tsx +1 -1
  150. package/src/tools/media/ImageViewer/types.ts +4 -0
  151. package/src/tools/media/LottiePlayer/lazy.tsx +1 -1
  152. package/src/tools/media/VideoPlayer/VideoPlayer.tsx +47 -1
  153. package/src/tools/media/VideoPlayer/parts/fullscreen.tsx +21 -4
  154. package/src/tools/media/VideoPlayer/parts/pip.tsx +21 -4
  155. package/src/tools/media/VideoPlayer/parts/play-button.tsx +21 -4
  156. package/src/tools/media/VideoPlayer/parts/playback-rate.tsx +19 -3
  157. package/src/tools/media/VideoPlayer/parts/volume.tsx +237 -18
  158. package/src/tools/media/VideoPlayer/styles/video-player.css +87 -7
  159. package/src/tools/media/VideoPlayer/types.ts +4 -0
  160. package/src/tools/overlay/ResponsiveDialog/lazy.tsx +1 -1
  161. package/src/tools/overlay/ScrollSpy/lazy.tsx +1 -1
  162. package/src/tools/overlay/SelectionToolbar/lazy.tsx +1 -1
  163. package/src/tools/overlay/Tour/lazy.tsx +1 -1
  164. package/src/tools/visual/Marquee/lazy.tsx +1 -1
  165. package/src/tools/visual/QRCode/lazy.tsx +1 -1
  166. package/src/tools/visual/charts/ActivityGraph/ActivityGraph.tsx +195 -0
  167. package/src/tools/visual/charts/ActivityGraph/README.md +28 -0
  168. package/src/tools/visual/charts/ActivityGraph/index.ts +8 -0
  169. package/src/tools/visual/charts/ActivityGraph/lazy.tsx +21 -0
  170. package/src/tools/visual/charts/ActivityGraph/types.ts +59 -0
  171. package/src/tools/visual/charts/ActivityGraph/utils.ts +88 -0
  172. package/src/tools/visual/charts/CommitGraph/CommitGraph.tsx +80 -0
  173. package/src/tools/visual/charts/CommitGraph/README.md +28 -0
  174. package/src/tools/visual/charts/CommitGraph/components/CommitDetail.tsx +107 -0
  175. package/src/tools/visual/charts/CommitGraph/components/CommitRow.tsx +122 -0
  176. package/src/tools/visual/charts/CommitGraph/components/Rails.tsx +171 -0
  177. package/src/tools/visual/charts/CommitGraph/hooks/useGraphLayout.ts +169 -0
  178. package/src/tools/visual/charts/CommitGraph/hooks/useLaneColors.ts +45 -0
  179. package/src/tools/visual/charts/CommitGraph/index.ts +14 -0
  180. package/src/tools/visual/charts/CommitGraph/lazy.tsx +25 -0
  181. package/src/tools/visual/charts/CommitGraph/types.ts +85 -0
  182. package/src/tools/visual/charts/CommitGraph/utils.ts +53 -0
  183. package/src/tools/visual/{Gauge → charts/Gauge}/lazy.tsx +1 -1
  184. package/src/tools/visual/charts/Sparkline/README.md +29 -0
  185. package/src/tools/visual/charts/Sparkline/Sparkline.tsx +217 -0
  186. package/src/tools/visual/charts/Sparkline/index.ts +9 -0
  187. package/src/tools/visual/charts/Sparkline/lazy.tsx +26 -0
  188. package/src/tools/visual/charts/Sparkline/types.ts +58 -0
  189. package/src/tools/visual/design/ColorPalette/ColorPalette.tsx +129 -0
  190. package/src/tools/visual/design/ColorPalette/README.md +34 -0
  191. package/src/tools/visual/design/ColorPalette/components/Swatch.tsx +102 -0
  192. package/src/tools/visual/design/ColorPalette/hooks/useCopyToClipboard.ts +41 -0
  193. package/src/tools/visual/design/ColorPalette/index.ts +12 -0
  194. package/src/tools/visual/design/ColorPalette/lazy.tsx +21 -0
  195. package/src/tools/visual/design/ColorPalette/types.ts +63 -0
  196. package/src/tools/visual/design/ColorPalette/utils.ts +83 -0
  197. package/src/tools/visual/{ColorPicker → design/ColorPicker}/lazy.tsx +1 -1
  198. package/src/tools/visual/{FileIcon → design/FileIcon}/treeAdapter.tsx +1 -1
  199. package/src/tools/visual/{Fps → indicators/Fps}/lazy.tsx +1 -1
  200. package/src/tools/visual/{Rating → indicators/Rating}/lazy.tsx +1 -1
  201. package/src/tools/visual/indicators/StatusIndicator/README.md +28 -0
  202. package/src/tools/visual/indicators/StatusIndicator/StatusIndicator.tsx +83 -0
  203. package/src/tools/visual/indicators/StatusIndicator/index.ts +14 -0
  204. package/src/tools/visual/indicators/StatusIndicator/lazy.tsx +21 -0
  205. package/src/tools/visual/indicators/StatusIndicator/types.ts +133 -0
  206. package/src/components/FloatingToolbar/actions/CopyAction.tsx +0 -22
  207. package/src/components/FloatingToolbar/actions/ExpandAction.tsx +0 -25
  208. package/src/components/FloatingToolbar/actions/FullscreenAction.tsx +0 -30
  209. package/src/tools/data/JsonTree/components/JsonContent.tsx +0 -197
  210. package/src/tools/data/JsonTree/hooks/useJsonExpand.ts +0 -50
  211. /package/src/{components → common}/FloatingToolbar/FloatingToolbar.css +0 -0
  212. /package/src/{components → common}/FloatingToolbar/actions/index.ts +0 -0
  213. /package/src/{components → common}/FloatingToolbar/hooks/useElementCorner.ts +0 -0
  214. /package/src/{components → common}/FloatingToolbar/hooks/useScrollIsolation.ts +0 -0
  215. /package/src/{components → common}/index.ts +0 -0
  216. /package/src/{components → common}/lazy-wrapper.tsx +0 -0
  217. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/README.md +0 -0
  218. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/DocsView.tsx +0 -0
  219. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/CodeSamples/LanguageTabs.tsx +0 -0
  220. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/CodeSamples/useCodeSnippet.ts +0 -0
  221. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/Header/MetaActions.tsx +0 -0
  222. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/Header/MethodBadge.tsx +0 -0
  223. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/Header/PathDisplay.tsx +0 -0
  224. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/Parameters/ParamGroup.tsx +0 -0
  225. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/Parameters/ParamRow.tsx +0 -0
  226. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/Parameters/index.tsx +0 -0
  227. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/RequestBody/index.tsx +0 -0
  228. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/Responses/ResponseRow.tsx +0 -0
  229. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/Responses/StatusTag.tsx +0 -0
  230. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/Responses/index.tsx +0 -0
  231. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/SchemaFields/FieldRow.tsx +0 -0
  232. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/SchemaFields/buildTree.ts +0 -0
  233. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/SchemaFields/index.tsx +0 -0
  234. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/SchemaFields/types.ts +0 -0
  235. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/Section/SectionHeader.tsx +0 -0
  236. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/Section/defaults.ts +0 -0
  237. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/Section/index.tsx +0 -0
  238. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/context.tsx +0 -0
  239. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/hooks/useSectionHash.ts +0 -0
  240. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/index.tsx +0 -0
  241. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/store/index.ts +0 -0
  242. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/store/selectors.ts +0 -0
  243. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/EndpointDoc/types.ts +0 -0
  244. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/SchemaCopyMenu.tsx +0 -0
  245. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/Sidebar/BrandHeader.tsx +0 -0
  246. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/Sidebar/CategoryBlock.tsx +0 -0
  247. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/Sidebar/EndpointRow.tsx +0 -0
  248. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/Sidebar/SchemaSection.tsx +0 -0
  249. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/Sidebar/SearchInput.tsx +0 -0
  250. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/Sidebar/SidebarBody.tsx +0 -0
  251. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/Sidebar/Toolbar.tsx +0 -0
  252. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/Sidebar/buildVM.ts +0 -0
  253. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/Sidebar/index.tsx +0 -0
  254. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/Sidebar/types.ts +0 -0
  255. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/Sidebar/useDebouncedValue.ts +0 -0
  256. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/SlideInPlayground.tsx +0 -0
  257. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/TryItSheet.tsx +0 -0
  258. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/anchor.ts +0 -0
  259. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/grouping.ts +0 -0
  260. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/index.tsx +0 -0
  261. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/DocsLayout/sidebarLabel.ts +0 -0
  262. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/index.ts +0 -0
  263. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/BodyFormEditor.tsx +0 -0
  264. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/EndpointDraftSync.tsx +0 -0
  265. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/EndpointResetButton.tsx +0 -0
  266. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/ResponsePanel/PreviewView.tsx +0 -0
  267. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/ResponsePanel/RawView.tsx +0 -0
  268. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/ResponsePanel/StatusBar.tsx +0 -0
  269. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/ResponsePanel/ViewTabs.tsx +0 -0
  270. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/ResponsePanel/detectContent.ts +0 -0
  271. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/ResponsePanel/index.tsx +0 -0
  272. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/ResponsePanel/useResponseView.ts +0 -0
  273. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/SendButton.tsx +0 -0
  274. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/components/shared/ui.tsx +0 -0
  275. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/constants.ts +0 -0
  276. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/context/PlaygroundContext.tsx +0 -0
  277. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/hooks/index.ts +0 -0
  278. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/hooks/useDocsUrlSync.ts +0 -0
  279. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/hooks/useEndpointDraft.ts +0 -0
  280. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/hooks/useMobile.ts +0 -0
  281. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/hooks/useOpenApiSchema.ts +0 -0
  282. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/index.tsx +0 -0
  283. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/types.ts +0 -0
  284. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/utils/apiKeyManager.ts +0 -0
  285. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/utils/codeSamples.ts +0 -0
  286. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/utils/formatters.ts +0 -0
  287. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/utils/index.ts +0 -0
  288. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/utils/operationToHar.ts +0 -0
  289. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/utils/sampler.ts +0 -0
  290. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/utils/schemaExport.ts +0 -0
  291. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/utils/scrollParent.ts +0 -0
  292. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/utils/url.ts +0 -0
  293. /package/src/tools/dev/{OpenapiViewer → api/OpenapiViewer}/utils/versionManager.ts +0 -0
  294. /package/src/tools/dev/{MarkdownMessage → code/MarkdownMessage}/ActionRow.tsx +0 -0
  295. /package/src/tools/dev/{MarkdownMessage → code/MarkdownMessage}/ChatMessageRow.tsx +0 -0
  296. /package/src/tools/dev/{MarkdownMessage → code/MarkdownMessage}/CodeBlock.tsx +0 -0
  297. /package/src/tools/dev/{MarkdownMessage → code/MarkdownMessage}/README.md +0 -0
  298. /package/src/tools/dev/{MarkdownMessage → code/MarkdownMessage}/index.ts +0 -0
  299. /package/src/tools/dev/{MarkdownMessage → code/MarkdownMessage}/linkRules.ts +0 -0
  300. /package/src/tools/dev/{MarkdownMessage → code/MarkdownMessage}/plainText.ts +0 -0
  301. /package/src/tools/dev/{MarkdownMessage → code/MarkdownMessage}/sanitize.ts +0 -0
  302. /package/src/tools/dev/{MarkdownMessage → code/MarkdownMessage}/types.ts +0 -0
  303. /package/src/tools/dev/{PrettyCode → code/PrettyCode}/README.md +0 -0
  304. /package/src/tools/dev/{PrettyCode → code/PrettyCode}/index.tsx +0 -0
  305. /package/src/tools/dev/{PrettyCode → code/PrettyCode}/registerPrismLanguages.ts +0 -0
  306. /package/src/tools/visual/{Gauge → charts/Gauge}/Gauge.tsx +0 -0
  307. /package/src/tools/visual/{Gauge → charts/Gauge}/index.ts +0 -0
  308. /package/src/tools/visual/{Gauge → charts/Gauge}/types.ts +0 -0
  309. /package/src/tools/visual/{ColorPicker → design/ColorPicker}/ColorPicker.tsx +0 -0
  310. /package/src/tools/visual/{ColorPicker → design/ColorPicker}/context/ColorPickerContext.tsx +0 -0
  311. /package/src/tools/visual/{ColorPicker → design/ColorPicker}/context/ColorPickerStore.tsx +0 -0
  312. /package/src/tools/visual/{ColorPicker → design/ColorPicker}/context/index.ts +0 -0
  313. /package/src/tools/visual/{ColorPicker → design/ColorPicker}/index.ts +0 -0
  314. /package/src/tools/visual/{ColorPicker → design/ColorPicker}/lib/color-utils.ts +0 -0
  315. /package/src/tools/visual/{ColorPicker → design/ColorPicker}/parts/ColorPickerAlphaSlider.tsx +0 -0
  316. /package/src/tools/visual/{ColorPicker → design/ColorPicker}/parts/ColorPickerArea.tsx +0 -0
  317. /package/src/tools/visual/{ColorPicker → design/ColorPicker}/parts/ColorPickerEyeDropper.tsx +0 -0
  318. /package/src/tools/visual/{ColorPicker → design/ColorPicker}/parts/ColorPickerFormatSelect.tsx +0 -0
  319. /package/src/tools/visual/{ColorPicker → design/ColorPicker}/parts/ColorPickerHueSlider.tsx +0 -0
  320. /package/src/tools/visual/{ColorPicker → design/ColorPicker}/parts/ColorPickerInput.tsx +0 -0
  321. /package/src/tools/visual/{ColorPicker → design/ColorPicker}/parts/ColorPickerSwatch.tsx +0 -0
  322. /package/src/tools/visual/{ColorPicker → design/ColorPicker}/parts/index.ts +0 -0
  323. /package/src/tools/visual/{ColorPicker → design/ColorPicker}/types.ts +0 -0
  324. /package/src/tools/visual/{FileIcon → design/FileIcon}/FileIcon.tsx +0 -0
  325. /package/src/tools/visual/{FileIcon → design/FileIcon}/get-file-icon.ts +0 -0
  326. /package/src/tools/visual/{FileIcon → design/FileIcon}/icons/icon-data.ts +0 -0
  327. /package/src/tools/visual/{FileIcon → design/FileIcon}/index.ts +0 -0
  328. /package/src/tools/visual/{FileIcon → design/FileIcon}/loader.ts +0 -0
  329. /package/src/tools/visual/{FileIcon → design/FileIcon}/specialFolders.ts +0 -0
  330. /package/src/tools/visual/{Fps → indicators/Fps}/Fps.tsx +0 -0
  331. /package/src/tools/visual/{Fps → indicators/Fps}/index.ts +0 -0
  332. /package/src/tools/visual/{Fps → indicators/Fps}/types.ts +0 -0
  333. /package/src/tools/visual/{Rating → indicators/Rating}/Rating.tsx +0 -0
  334. /package/src/tools/visual/{Rating → indicators/Rating}/index.ts +0 -0
  335. /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
+ });