@djangocfg/ui-tools 2.1.430 → 2.1.432
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/tree/index.cjs +43 -8
- package/dist/tree/index.cjs.map +1 -1
- package/dist/tree/index.d.cts +31 -1
- package/dist/tree/index.d.ts +31 -1
- package/dist/tree/index.mjs +43 -8
- package/dist/tree/index.mjs.map +1 -1
- package/package.json +28 -14
- package/src/common/CodePanelHeader/CodePanelHeader.tsx +121 -0
- package/src/common/CodePanelHeader/index.ts +1 -0
- package/src/common/blocks/BlockError.tsx +46 -0
- package/src/common/blocks/index.ts +34 -0
- package/src/common/blocks/schemas.ts +143 -0
- package/src/common/blocks/types.ts +76 -0
- package/src/common/chips/README.md +67 -0
- package/src/common/chips/UrlChip.tsx +105 -0
- package/src/common/chips/index.ts +20 -0
- package/src/common/link-preview/LinkPreviewCard.tsx +178 -0
- package/src/common/link-preview/index.ts +12 -0
- package/src/common/link-preview/types.ts +44 -0
- package/src/common/tiptap/index.ts +10 -0
- package/src/common/tiptap/useNodeAttrs.ts +128 -0
- package/src/lib/page-snapshot/react/PageSnapshotChip.tsx +1 -1
- package/src/tools/chat/README.md +109 -2
- package/src/tools/chat/composer/Composer.tsx +45 -12
- package/src/tools/chat/composer/ComposerRichTextarea.tsx +16 -4
- package/src/tools/chat/composer/PastedTextDialog.tsx +88 -0
- package/src/tools/chat/composer/index.ts +9 -0
- package/src/tools/chat/composer/textToAttachment.ts +69 -0
- package/src/tools/chat/composer/types.ts +13 -0
- package/src/tools/chat/composer/useComposerAttach.tsx +47 -0
- package/src/tools/chat/constants.ts +9 -2
- package/src/tools/chat/core/extractFirstUrl.ts +45 -0
- package/src/tools/chat/core/reducer.ts +32 -8
- package/src/tools/chat/core/transport/mappers/pydantic-ai.ts +1 -1
- package/src/tools/chat/core/transport/mock.ts +4 -1
- package/src/tools/chat/hooks/useChat.ts +20 -20
- package/src/tools/chat/lazy.tsx +10 -0
- package/src/tools/chat/messages/Attachments.tsx +335 -46
- package/src/tools/chat/messages/BubbleContextMenu.tsx +203 -0
- package/src/tools/chat/messages/MessageBubble.tsx +175 -31
- package/src/tools/chat/messages/MessageList.tsx +107 -21
- package/src/tools/chat/messages/ToolCalls.tsx +28 -13
- package/src/tools/chat/messages/blocks/MessageBlocks.tsx +70 -17
- package/src/tools/chat/messages/blocks/builtin.tsx +6 -3
- package/src/tools/chat/messages/blocks/renderers/AudioBlock.tsx +6 -0
- package/src/tools/chat/messages/blocks/renderers/ChatImageAlbum.tsx +154 -0
- package/src/tools/chat/messages/blocks/renderers/CodeBlock.tsx +94 -6
- package/src/tools/chat/messages/blocks/renderers/DiffBlock.tsx +29 -0
- package/src/tools/chat/messages/blocks/renderers/GalleryBlock.tsx +17 -10
- package/src/tools/chat/messages/blocks/renderers/ImageBlock.tsx +37 -16
- package/src/tools/chat/messages/blocks/renderers/JsonBlock.tsx +11 -6
- package/src/tools/chat/messages/blocks/renderers/LinkBlock.tsx +32 -0
- package/src/tools/chat/messages/blocks/renderers/MapBlock.tsx +181 -19
- package/src/tools/chat/messages/blocks/renderers/VideoBlock.tsx +2 -5
- package/src/tools/chat/messages/index.ts +13 -0
- package/src/tools/chat/public.ts +5 -1
- package/src/tools/chat/shell/ChatRoot.tsx +29 -0
- package/src/tools/chat/styles/bubbleTokens.ts +18 -0
- package/src/tools/chat/styles/index.ts +1 -0
- package/src/tools/chat/types/attachment.ts +38 -3
- package/src/tools/chat/types/block.ts +56 -9
- package/src/tools/chat/types/blockSchemas.ts +197 -0
- package/src/tools/chat/types/config.ts +33 -0
- package/src/tools/chat/types/events.ts +14 -7
- package/src/tools/chat/types/index.ts +4 -2
- package/src/tools/chat/types/message.ts +7 -0
- package/src/tools/data/JsonTree/JsonViewer.tsx +7 -7
- package/src/tools/data/Tree/README.md +30 -0
- package/src/tools/data/Tree/components/TreeRow.tsx +11 -2
- package/src/tools/data/Tree/context/menu/render.tsx +56 -27
- package/src/tools/data/Tree/data/appearance.ts +65 -3
- package/src/tools/data/Tree/data/index.ts +2 -0
- package/src/tools/data/Tree/index.tsx +2 -0
- package/src/tools/dev/Map/README.md +30 -345
- package/src/tools/dev/Map/cards/index.ts +6 -0
- package/src/tools/dev/Map/cards/types.ts +67 -0
- package/src/tools/dev/Map/components/CompassChip.tsx +67 -0
- package/src/tools/dev/Map/components/DraggableMarkers.tsx +79 -0
- package/src/tools/dev/Map/components/GeocoderControl.tsx +148 -63
- package/src/tools/dev/Map/components/GeocoderInput.tsx +181 -0
- package/src/tools/dev/Map/components/MapContainer/FullscreenDialog.tsx +140 -0
- package/src/tools/dev/Map/components/MapContainer/GeolocateHandle.tsx +70 -0
- package/src/tools/dev/Map/components/MapContainer/MapInner.tsx +326 -0
- package/src/tools/dev/Map/components/MapContainer/MapToolbar.tsx +286 -0
- package/src/tools/dev/Map/components/MapContainer/RotateHint.tsx +25 -0
- package/src/tools/dev/Map/components/MapContainer/ScrollLockOverlay.tsx +52 -0
- package/src/tools/dev/Map/components/MapContainer/constants.ts +15 -0
- package/src/tools/dev/Map/components/MapContainer/hooks/useAutoReset.ts +51 -0
- package/src/tools/dev/Map/components/MapContainer/hooks/useBasemapState.ts +48 -0
- package/src/tools/dev/Map/components/MapContainer/hooks/useExternalMapsUrl.ts +29 -0
- package/src/tools/dev/Map/components/MapContainer/index.tsx +29 -0
- package/src/tools/dev/Map/components/MapContainer/types.ts +198 -0
- package/src/tools/dev/Map/components/MapControls.tsx +27 -0
- package/src/tools/dev/Map/components/MapMarkers.tsx +94 -0
- package/src/tools/dev/Map/components/MarkerCard.tsx +102 -0
- package/src/tools/dev/Map/components/MeasureControl.tsx +219 -0
- package/src/tools/dev/Map/components/index.ts +35 -1
- package/src/tools/dev/Map/geocode/index.ts +2 -0
- package/src/tools/dev/Map/geocode/photon.ts +265 -0
- package/src/tools/dev/Map/hooks/index.ts +37 -0
- package/src/tools/dev/Map/hooks/useCompass.ts +44 -0
- package/src/tools/dev/Map/hooks/useDraggableMarkers.ts +112 -0
- package/src/tools/dev/Map/hooks/useGeocoder.ts +155 -0
- package/src/tools/dev/Map/hooks/useGeolocation.ts +174 -0
- package/src/tools/dev/Map/hooks/useMapControl.ts +5 -1
- package/src/tools/dev/Map/hooks/useMapImages.ts +112 -0
- package/src/tools/dev/Map/hooks/useMapScrollProtection.ts +155 -0
- package/src/tools/dev/Map/hooks/useMapTouchMode.ts +21 -0
- package/src/tools/dev/Map/hooks/useMarkerCard.ts +39 -0
- package/src/tools/dev/Map/hooks/useMarkers.ts +9 -3
- package/src/tools/dev/Map/hooks/useMeasure.ts +169 -0
- package/src/tools/dev/Map/hooks/useTerrain.ts +123 -0
- package/src/tools/dev/Map/index.ts +59 -2
- package/src/tools/dev/Map/layers/index.ts +1 -0
- package/src/tools/dev/Map/layers/symbol.ts +93 -0
- package/src/tools/dev/Map/lazy.tsx +120 -0
- package/src/tools/dev/Map/protocols/pmtiles.ts +73 -0
- package/src/tools/dev/Map/styles/index.ts +62 -0
- package/src/tools/dev/Map/styles/terrain.ts +29 -0
- package/src/tools/dev/Map/types.ts +31 -3
- package/src/tools/dev/api/OpenapiViewer/components/DocsLayout/EndpointDoc/Header/MetaActions.tsx +1 -1
- package/src/tools/dev/api/OpenapiViewer/components/DocsLayout/EndpointDoc/Responses/StatusTag.tsx +5 -12
- package/src/tools/dev/api/OpenapiViewer/components/DocsLayout/EndpointDoc/Section/SectionHeader.tsx +1 -1
- package/src/tools/dev/api/OpenapiViewer/components/DocsLayout/SchemaCopyMenu.tsx +1 -1
- package/src/tools/dev/api/OpenapiViewer/components/shared/SendButton.tsx +1 -1
- package/src/tools/dev/api/OpenapiViewer/components/shared/http-colors.ts +61 -0
- package/src/tools/dev/api/OpenapiViewer/components/shared/ui.tsx +4 -21
- package/src/tools/dev/code/MarkdownMessage/MarkdownMessage.tsx +150 -58
- package/src/tools/dev/code/MarkdownMessage/README.md +38 -0
- package/src/tools/dev/code/MarkdownMessage/builtinRules.tsx +92 -0
- package/src/tools/dev/code/MarkdownMessage/index.ts +3 -0
- package/src/tools/dev/code/MarkdownMessage/streamingBlocks.ts +164 -0
- package/src/tools/dev/code/MarkdownMessage/types.ts +20 -0
- package/src/tools/dev/code/PrettyCode/PrettyCode.client.tsx +27 -30
- package/src/tools/dev/code/PrettyCode/index.tsx +4 -1
- package/src/tools/dev/code/PrettyCode/lazy.tsx +3 -1
- package/src/tools/forms/MarkdownEditor/MarkdownEditor.tsx +168 -3
- package/src/tools/forms/MarkdownEditor/README.md +84 -0
- package/src/tools/forms/MarkdownEditor/chip/ChipNode.ts +454 -0
- package/src/tools/forms/MarkdownEditor/chip/syncChips.ts +80 -0
- package/src/tools/forms/MarkdownEditor/filePath/FilePathChip.tsx +68 -0
- package/src/tools/forms/MarkdownEditor/filePath/__tests__/detect.test.ts +161 -0
- package/src/tools/forms/MarkdownEditor/filePath/detect.ts +289 -0
- package/src/tools/forms/MarkdownEditor/filePath/index.ts +15 -0
- package/src/tools/forms/MarkdownEditor/index.ts +19 -0
- package/src/tools/forms/MarkdownEditor/lazy.tsx +10 -0
- package/src/tools/forms/MarkdownEditor/styles.css +134 -2
- package/src/tools/forms/MarkdownEditor/url/__tests__/detect.test.ts +121 -0
- package/src/tools/forms/MarkdownEditor/url/detect.ts +227 -0
- package/src/tools/forms/NotionEditor/BookmarkNode.ts +158 -0
- package/src/tools/forms/NotionEditor/BookmarkView.tsx +45 -0
- package/src/tools/forms/NotionEditor/MapEditLayer.tsx +199 -0
- package/src/tools/forms/NotionEditor/MapNode.ts +218 -0
- package/src/tools/forms/NotionEditor/MapView.tsx +103 -0
- package/src/tools/forms/NotionEditor/NotionEditor.tsx +7 -2
- package/src/tools/forms/NotionEditor/README.md +109 -188
- package/src/tools/forms/NotionEditor/extensions.ts +15 -0
- package/src/tools/forms/NotionEditor/slashItems.ts +19 -0
- package/src/tools/forms/NotionEditor/styles.css +25 -0
- package/src/tools/forms/NotionEditor/types.ts +9 -0
- package/src/tools/input/SpeechRecognition/core/engine/external.ts +5 -0
- package/src/tools/input/SpeechRecognition/types.ts +10 -0
- package/src/tools/media/Gallery/components/lightbox/GalleryLightbox.tsx +39 -22
- package/src/tools/media/ImageViewer/README.md +9 -0
- package/src/tools/media/ImageViewer/components/ImageViewer.tsx +165 -79
- package/src/tools/media/ImageViewer/components/LightboxChrome.tsx +137 -0
- package/src/tools/media/ImageViewer/components/MiddleEllipsis.tsx +46 -0
- package/src/tools/media/ImageViewer/components/index.ts +2 -0
- package/src/tools/media/ImageViewer/hooks/index.ts +3 -0
- package/src/tools/media/ImageViewer/hooks/useIdleChrome.ts +73 -0
- package/src/tools/media/ImageViewer/types.ts +10 -0
- package/src/tools/media/ImageViewer/utils/constants.ts +28 -0
- package/src/tools/media/ImageViewer/utils/index.ts +4 -0
- package/src/tools/media/VideoPlayer/VideoPlayer.tsx +34 -40
- package/src/tools/media/VideoPlayer/canvas/youtube-canvas.tsx +13 -13
- package/src/tools/media/VideoPlayer/parts/center-play.tsx +26 -0
- package/src/tools/media/VideoPlayer/parts/index.ts +2 -0
- package/src/tools/media/VideoPlayer/styles/video-player.css +102 -53
- package/src/tools/visual/charts/SmoothLine/README.md +97 -0
- package/src/tools/visual/charts/SmoothLine/SmoothLine.tsx +177 -0
- package/src/tools/visual/charts/SmoothLine/index.ts +21 -0
- package/src/tools/visual/charts/SmoothLine/lazy.tsx +23 -0
- package/src/tools/visual/charts/SmoothLine/smoothPath.ts +209 -0
- package/src/tools/visual/charts/SmoothLine/types.ts +98 -0
- package/src/tools/visual/charts/SmoothLine/useSmoothSeries.ts +429 -0
- package/src/tools/visual/indicators/Fps/types.ts +1 -1
- package/src/tools/chat/messages/blocks/renderers/LottieBlock.tsx +0 -11
- package/src/tools/dev/Map/components/MapContainer.tsx +0 -198
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
Notion-style WYSIWYG markdown editor built on **TipTap v3**.
|
|
4
4
|
|
|
5
5
|
Lazy-loaded sibling of [`MarkdownEditor`](../MarkdownEditor/) — same
|
|
6
|
-
markdown round-trip, but with a heavier extension stack
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
markdown round-trip, but with a heavier extension stack, the floating
|
|
7
|
+
menus (slash / bubble / drag handle) users expect from a "document"
|
|
8
|
+
editor, **and serializable block nodes** (a link-preview *bookmark*
|
|
9
|
+
card and an interactive *map* block) that round-trip through markdown.
|
|
9
10
|
|
|
10
11
|
```tsx
|
|
11
12
|
import { NotionEditor } from '@djangocfg/ui-tools/notion-editor';
|
|
@@ -16,84 +17,96 @@ import { NotionEditor } from '@djangocfg/ui-tools/notion-editor';
|
|
|
16
17
|
onSave={(md) => writeFile(md)}
|
|
17
18
|
autoFocus
|
|
18
19
|
placeholder="Type '/' for commands…"
|
|
20
|
+
resolveLinkPreview={unfurl} // optional — powers bookmark cards
|
|
19
21
|
/>
|
|
20
22
|
```
|
|
21
23
|
|
|
24
|
+
> **Bundle.** The full stack (lowlight common pack + tables + drag
|
|
25
|
+
> handle, plus the lazy MapLibre chunk when a map block mounts) lands
|
|
26
|
+
> around ~350 KB minified. `@djangocfg/ui-tools/notion-editor` is the
|
|
27
|
+
> **lazy** entry (`lazy.tsx`) — pages that never render the editor pay
|
|
28
|
+
> nothing. All of it is `'use client'`.
|
|
29
|
+
|
|
22
30
|
## When to use this vs `MarkdownEditor`
|
|
23
31
|
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
-
|
|
89
|
-
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
32
|
+
| Use case | Pick |
|
|
33
|
+
| --------------------------------------- | ---------------- |
|
|
34
|
+
| Chat composer, single-paragraph input | `MarkdownEditor` |
|
|
35
|
+
| Note / doc surface, multi-paragraph | `NotionEditor` |
|
|
36
|
+
| `.md` file viewer with full editing | `NotionEditor` |
|
|
37
|
+
| Slim mention dropdown, no tables/tasks | `MarkdownEditor` |
|
|
38
|
+
| Slash menu, drag handle, tables, hljs | `NotionEditor` |
|
|
39
|
+
| Bookmark cards / embedded map blocks | `NotionEditor` |
|
|
40
|
+
|
|
41
|
+
The two coexist intentionally — `NotionEditor` is heavier; don't mount
|
|
42
|
+
it inside a chat composer just to get features the user will never use.
|
|
43
|
+
|
|
44
|
+
## Props
|
|
45
|
+
|
|
46
|
+
`NotionEditorProps` (see `types.ts`):
|
|
47
|
+
|
|
48
|
+
| Prop | Type | Default | Notes |
|
|
49
|
+
| -------------------- | -------------------------- | -------------------- | ----- |
|
|
50
|
+
| `value` | `string` | — | Markdown source (required). |
|
|
51
|
+
| `onChange` | `(md: string) => void` | — | Fired on every keystroke with freshly-serialised markdown (required). |
|
|
52
|
+
| `placeholder` | `string` | `"Type '/' for commands…"` | Ghost text on an empty paragraph. |
|
|
53
|
+
| `disabled` | `boolean` | `false` | Read-only renderer (also makes map blocks static). |
|
|
54
|
+
| `autoFocus` | `boolean` | `false` | Focus on mount. Pair with `key={path}` upstream for fresh focus per file. |
|
|
55
|
+
| `onSave` | `(md: string) => void` | — | Bound to Cmd/Ctrl+S, scoped to the editor DOM. |
|
|
56
|
+
| `className` | `string` | `''` | Extra class on the outer wrapper. |
|
|
57
|
+
| `minHeight` | `number` | `320` | Min height of the editor surface (px). |
|
|
58
|
+
| `resolveLinkPreview` | `ResolveLinkPreview` | `undefined` | **Host URL-unfurl resolver** threaded into the bookmark node. `undefined` → cards render a hostname/favicon fallback. See [nodes.md](./@docs/nodes.md#bookmark-block). |
|
|
59
|
+
|
|
60
|
+
`resolveLinkPreview` is captured on first build (memoised on
|
|
61
|
+
`[placeholder, resolveLinkPreview]`) — pass a **stable reference** (a
|
|
62
|
+
host method), not a new closure per render.
|
|
63
|
+
|
|
64
|
+
### Imperative API
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
const ref = useRef<NotionEditorHandle>(null);
|
|
68
|
+
|
|
69
|
+
ref.current?.focus(); // focus the editor surface
|
|
70
|
+
ref.current?.moveCursorToEnd(); // focus + caret at end
|
|
71
|
+
ref.current?.getEditor(); // raw TipTap Editor (escape hatch)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`NotionEditorHandle` is structurally compatible with `ComposerHandle`
|
|
75
|
+
from `@djangocfg/ui-tools/composer-registry` — the chat suite can
|
|
76
|
+
register a NotionEditor as a composer if you ever want to swap them in.
|
|
77
|
+
|
|
78
|
+
## Features at a glance
|
|
79
|
+
|
|
80
|
+
- **Slash menu (`/`)** — 12 commands: Text, H1-H3, Bullet/Numbered/Todo
|
|
81
|
+
lists, Quote, Code block, Divider, Table, and **Map**. Filtering by
|
|
82
|
+
title + aliases; each item shows its markdown shorthand as a hint.
|
|
83
|
+
See [slash.md](./@docs/slash.md).
|
|
84
|
+
- **Block nodes** — a **bookmark** (link-preview) card from pasting a
|
|
85
|
+
bare URL on an empty line, and an interactive **map** block from
|
|
86
|
+
`/map`. Both round-trip through markdown. See [nodes.md](./@docs/nodes.md).
|
|
87
|
+
- **Interactive map editing** — drag a pin, click-to-add, remove,
|
|
88
|
+
switch basemap; every edit writes back into the document via
|
|
89
|
+
`useNodeAttrs`. See [interactivity.md](./@docs/interactivity.md).
|
|
90
|
+
- **Safe block rendering** — each block parses its attrs through a Zod
|
|
91
|
+
schema; a malformed payload renders `<BlockError>` instead of
|
|
92
|
+
crashing the editor. See [interactivity.md](./@docs/interactivity.md#safe-rendering).
|
|
93
|
+
- **Bubble menu** — floating selection toolbar (Bold, Italic, Underline,
|
|
94
|
+
Strike, Code, Highlight, Link) with shortcut tooltips. Auto-hides in
|
|
95
|
+
code blocks and on empty selections.
|
|
96
|
+
- **Cmd+K link prompt**, **drag handle**, **smart Cmd+A**,
|
|
97
|
+
**heading-aware placeholder**, **forced-dark code blocks**,
|
|
98
|
+
**task list** (ui-core `<Checkbox>`), **Cmd+S save hook**,
|
|
99
|
+
**markdown input rules**. See [editor.md](./@docs/editor.md).
|
|
100
|
+
|
|
101
|
+
## Docs
|
|
102
|
+
|
|
103
|
+
| Doc | Covers |
|
|
104
|
+
|---|---|
|
|
105
|
+
| [nodes.md](./@docs/nodes.md) | The **bookmark** + **map** block nodes — triggers, payload shapes, the `resolveLinkPreview` injection, and markdown serialisation / round-trip. |
|
|
106
|
+
| [interactivity.md](./@docs/interactivity.md) | The `useNodeAttrs` write-back seam (how map edits persist) and the Zod-validator / `<BlockError>` safety net. |
|
|
107
|
+
| [slash.md](./@docs/slash.md) | The `/` slash menu — the command list, filtering, and how to add a command (incl. `/map`). |
|
|
108
|
+
| [editor.md](./@docs/editor.md) | Base editor config — the TipTap extension stack, keymap, task items, drag handle, link dialog, markdown round-trip table, and known limitations. |
|
|
109
|
+
| [stories.md](./@docs/stories.md) | The Storybook stories list. |
|
|
97
110
|
|
|
98
111
|
## Files
|
|
99
112
|
|
|
@@ -101,8 +114,10 @@ node spec when serialising.
|
|
|
101
114
|
| ----------------------------- | ----------------------------------------------------------- |
|
|
102
115
|
| `lazy.tsx` | Subpath entry — `React.lazy` + `forwardRef` for the handle |
|
|
103
116
|
| `NotionEditor.tsx` | Main component + `BubbleSelectionToolbar` |
|
|
104
|
-
| `extensions.ts`
|
|
117
|
+
| `extensions.ts` | Assembled TipTap stack (factory `notionExtensions()`) |
|
|
105
118
|
| `types.ts` | `NotionEditorProps`, `NotionEditorHandle` |
|
|
119
|
+
| `BookmarkNode.ts` · `BookmarkView.tsx` | Bookmark (link-preview) block node + NodeView |
|
|
120
|
+
| `MapNode.ts` · `MapView.tsx` · `MapEditLayer.tsx` | Map block node, NodeView, editable layer |
|
|
106
121
|
| `SlashExtension.ts` | TipTap extension wrapping `@tiptap/suggestion` |
|
|
107
122
|
| `createSlashSuggestion.ts` | Suggestion config — floating-ui popup mount/update/teardown |
|
|
108
123
|
| `SlashList.tsx` | Popover content (listbox + keyboard nav) |
|
|
@@ -112,126 +127,32 @@ node spec when serialising.
|
|
|
112
127
|
| `LinkDialog.tsx` | Cmd+K link prompt (ui-core Dialog) |
|
|
113
128
|
| `styles.css` | Typography + slash/bubble menu + drag handle + lowlight |
|
|
114
129
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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).
|
|
130
|
+
Shared seams used by the block nodes live under `src/common/` (outside
|
|
131
|
+
this folder): `common/link-preview` (the `LinkPreviewCard` +
|
|
132
|
+
`ResolveLinkPreview` contract), `common/blocks` (payload types, Zod
|
|
133
|
+
schemas, `<BlockError>`), and `common/tiptap` (the `useNodeAttrs`
|
|
134
|
+
write-back hook).
|
|
129
135
|
|
|
130
136
|
## Saving — dirty / pristine handling
|
|
131
137
|
|
|
132
|
-
The editor
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
Two gotchas the host needs to know about:
|
|
138
|
+
The editor calls `onChange` on every keystroke with the freshly
|
|
139
|
+
serialised markdown; the host owns state and detects "dirty" however it
|
|
140
|
+
likes. Two gotchas:
|
|
137
141
|
|
|
138
|
-
1. **TipTap re-serialises markdown on mount** (trailing newline,
|
|
139
|
-
|
|
140
|
-
`
|
|
141
|
-
|
|
142
|
-
the dirty indicator lights up on every file open. **Fix in the
|
|
143
|
-
host**: normalise both sides before comparing.
|
|
142
|
+
1. **TipTap re-serialises markdown on mount** (trailing newline, CRLF →
|
|
143
|
+
LF). The first `onChange` after `setContent(value)` is byte-different
|
|
144
|
+
from `value` even though nobody typed. Normalise both sides before a
|
|
145
|
+
dirty comparison:
|
|
144
146
|
|
|
145
147
|
```ts
|
|
146
148
|
const norm = (s: string) => s.replace(/\r\n/g, '\n').trimEnd();
|
|
147
149
|
const isDirty = norm(draft) !== norm(saved);
|
|
148
150
|
```
|
|
149
151
|
|
|
150
|
-
2. **Cmd+S only fires when focus is inside the editor
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
host as a fallback — both paths call the same save fn.
|
|
152
|
+
2. **Cmd+S only fires when focus is inside the editor.** Add a
|
|
153
|
+
window-level `useHotkey('mod+s', save)` in the host as a fallback —
|
|
154
|
+
both paths call the same save fn.
|
|
154
155
|
|
|
155
156
|
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.
|
|
157
|
+
canonical wiring. Use `key={path}` on the wrapper if you remount per
|
|
158
|
+
file — that's what makes `autoFocus` fire on each file open.
|
|
@@ -17,6 +17,9 @@ import { SlashExtension } from './SlashExtension';
|
|
|
17
17
|
import { createSlashSuggestion } from './createSlashSuggestion';
|
|
18
18
|
import { TaskItemView } from './TaskItemView';
|
|
19
19
|
import { CustomKeymap } from './CustomKeymap';
|
|
20
|
+
import { BookmarkNode } from './BookmarkNode';
|
|
21
|
+
import { MapNode } from './MapNode';
|
|
22
|
+
import type { ResolveLinkPreview } from '../../../common/link-preview';
|
|
20
23
|
|
|
21
24
|
// Lowlight bundle: "common" languages cover ts/js/json/py/go/rust/etc.
|
|
22
25
|
// The full language pack pulls in ~300KB more — opt-in case by case.
|
|
@@ -24,6 +27,13 @@ const lowlight = createLowlight(common);
|
|
|
24
27
|
|
|
25
28
|
export interface BuildExtensionsOptions {
|
|
26
29
|
placeholder: string;
|
|
30
|
+
/**
|
|
31
|
+
* Host resolver for the `bookmark` (link-preview) node. Given a URL it
|
|
32
|
+
* returns unfurl metadata. Threaded into `BookmarkNode.options` and read by
|
|
33
|
+
* its NodeView. Default `undefined` → the card renders its own
|
|
34
|
+
* hostname/favicon fallback (same seam as chat's `config.linkPreview`).
|
|
35
|
+
*/
|
|
36
|
+
resolveLinkPreview?: ResolveLinkPreview;
|
|
27
37
|
}
|
|
28
38
|
|
|
29
39
|
/**
|
|
@@ -97,6 +107,11 @@ export function notionExtensions(opts: BuildExtensionsOptions): AnyExtension[] {
|
|
|
97
107
|
// ships — typo and all. Spelling it correctly is silently ignored.
|
|
98
108
|
scrollTreshold: 100,
|
|
99
109
|
}),
|
|
110
|
+
// Bookmark (link-preview) block — paste a bare URL on an empty line and
|
|
111
|
+
// it becomes a card. The host resolver is threaded through options.
|
|
112
|
+
BookmarkNode.configure({ resolveLinkPreview: opts.resolveLinkPreview }),
|
|
113
|
+
// Map block — inserted via the `/map` slash command (see slashItems).
|
|
114
|
+
MapNode,
|
|
100
115
|
SlashExtension.configure({
|
|
101
116
|
suggestion: createSlashSuggestion(),
|
|
102
117
|
}),
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
Heading3,
|
|
7
7
|
List,
|
|
8
8
|
ListOrdered,
|
|
9
|
+
Map as MapIcon,
|
|
9
10
|
Minus,
|
|
10
11
|
Quote,
|
|
11
12
|
Table as TableIcon,
|
|
@@ -13,6 +14,7 @@ import {
|
|
|
13
14
|
type LucideIcon,
|
|
14
15
|
} from 'lucide-react';
|
|
15
16
|
import type { Editor, Range } from '@tiptap/react';
|
|
17
|
+
import { DEFAULT_MAP_PAYLOAD } from './MapNode';
|
|
16
18
|
|
|
17
19
|
export interface SlashItem {
|
|
18
20
|
title: string;
|
|
@@ -146,6 +148,23 @@ export const slashItems: SlashItem[] = [
|
|
|
146
148
|
.run();
|
|
147
149
|
},
|
|
148
150
|
},
|
|
151
|
+
{
|
|
152
|
+
title: 'Map',
|
|
153
|
+
description: 'Interactive map block.',
|
|
154
|
+
icon: MapIcon,
|
|
155
|
+
aliases: ['map', 'location', 'geo', 'pin'],
|
|
156
|
+
command: ({ editor, range }) => {
|
|
157
|
+
// Drop the typed `/map`, then insert a map block seeded with a sensible
|
|
158
|
+
// default payload so the user sees a live map immediately. Attrs are
|
|
159
|
+
// plain JSON and round-trip through markdown (see MapNode).
|
|
160
|
+
editor
|
|
161
|
+
.chain()
|
|
162
|
+
.focus()
|
|
163
|
+
.deleteRange(range)
|
|
164
|
+
.setMapBlock({ ...DEFAULT_MAP_PAYLOAD })
|
|
165
|
+
.run();
|
|
166
|
+
},
|
|
167
|
+
},
|
|
149
168
|
];
|
|
150
169
|
|
|
151
170
|
/** Case-insensitive filter over title + aliases. */
|
|
@@ -58,6 +58,15 @@
|
|
|
58
58
|
text-underline-offset: 2px;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
/* …but block-card NodeViews (bookmark/map) own their styling — the card is
|
|
62
|
+
itself an <a>, and its inner title/description must NOT inherit the editor
|
|
63
|
+
link underline (it read as if the whole card text were a link). */
|
|
64
|
+
.notion-editor .tiptap .notion-bookmark a,
|
|
65
|
+
.notion-editor .tiptap a.notion-bookmark {
|
|
66
|
+
color: inherit;
|
|
67
|
+
text-decoration: none;
|
|
68
|
+
}
|
|
69
|
+
|
|
61
70
|
/* Headings */
|
|
62
71
|
.notion-editor .tiptap h1 {
|
|
63
72
|
font-size: 2em;
|
|
@@ -167,6 +176,22 @@
|
|
|
167
176
|
text-decoration: line-through;
|
|
168
177
|
}
|
|
169
178
|
|
|
179
|
+
/* ── Bookmark (link-preview) + Map block nodes ────────────────────────────
|
|
180
|
+
* Block-level NodeViews rendering shared cards (LinkPreviewCard / the map).
|
|
181
|
+
* The card / map owns its own frame (radius, border, shadow); here we only
|
|
182
|
+
* own the block's vertical rhythm so it breathes like a paragraph and the
|
|
183
|
+
* ProseMirror-selectednode ring reads cleanly on the atom. */
|
|
184
|
+
.notion-editor .tiptap .notion-bookmark,
|
|
185
|
+
.notion-editor .tiptap .notion-map {
|
|
186
|
+
margin: 0.75em 0;
|
|
187
|
+
}
|
|
188
|
+
.notion-editor .tiptap .notion-bookmark.ProseMirror-selectednode,
|
|
189
|
+
.notion-editor .tiptap .notion-map.ProseMirror-selectednode {
|
|
190
|
+
outline: 2px solid var(--ring);
|
|
191
|
+
outline-offset: 2px;
|
|
192
|
+
border-radius: var(--radius, 0.5rem);
|
|
193
|
+
}
|
|
194
|
+
|
|
170
195
|
/* Blockquote */
|
|
171
196
|
.notion-editor .tiptap blockquote {
|
|
172
197
|
border-left: 3px solid var(--color-border, var(--border));
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Editor } from '@tiptap/react';
|
|
2
|
+
import type { ResolveLinkPreview } from '../../../common/link-preview';
|
|
2
3
|
|
|
3
4
|
export interface NotionEditorProps {
|
|
4
5
|
value: string;
|
|
@@ -17,6 +18,14 @@ export interface NotionEditorProps {
|
|
|
17
18
|
className?: string;
|
|
18
19
|
/** Minimum height of the editor surface in px. Default 320. */
|
|
19
20
|
minHeight?: number;
|
|
21
|
+
/**
|
|
22
|
+
* Host resolver for the `bookmark` (link-preview) block. When a user pastes
|
|
23
|
+
* a bare URL on an empty line it becomes a bookmark card; this resolver
|
|
24
|
+
* unfurls the URL into title/description/image (a Wails/Go method, a
|
|
25
|
+
* Next.js API route). Default `undefined` → the card renders its own
|
|
26
|
+
* hostname/favicon fallback. Same seam as chat's `config.linkPreview`.
|
|
27
|
+
*/
|
|
28
|
+
resolveLinkPreview?: ResolveLinkPreview;
|
|
20
29
|
}
|
|
21
30
|
|
|
22
31
|
/** Imperative handle exposed via `ref`. Compatible with `ComposerHandle`
|
|
@@ -182,6 +182,11 @@ export function createExternalEngine(
|
|
|
182
182
|
return {
|
|
183
183
|
id: opts.id ?? 'external',
|
|
184
184
|
isSupported: opts.supported ?? true,
|
|
185
|
+
// Capture happens outside the browser (native / sidecar), so this
|
|
186
|
+
// engine must never be gated on `navigator.mediaDevices.getUserMedia`
|
|
187
|
+
// — that API is absent in many host shells (Wails WKWebView) and in
|
|
188
|
+
// headless / non-secure contexts, yet the engine works fine there.
|
|
189
|
+
usesMicrophone: false,
|
|
185
190
|
on(event, cb): Unsub {
|
|
186
191
|
return bus.on(event, cb);
|
|
187
192
|
},
|
|
@@ -82,6 +82,16 @@ export type Unsub = () => void;
|
|
|
82
82
|
export interface RecognitionEngine {
|
|
83
83
|
readonly id: string;
|
|
84
84
|
readonly isSupported: boolean;
|
|
85
|
+
/**
|
|
86
|
+
* Whether this engine captures audio through the browser microphone
|
|
87
|
+
* (`navigator.mediaDevices.getUserMedia`). `true` (default when
|
|
88
|
+
* omitted) for the browser-native engines — Web Speech, HTTP and
|
|
89
|
+
* WebSocket all open a `getUserMedia` stream. `false` for engines
|
|
90
|
+
* that own capture outside the browser (`createExternalEngine` —
|
|
91
|
+
* Wails / Tauri / native whisper sidecar), so consumers must NOT gate
|
|
92
|
+
* them on `getUserMedia` being present.
|
|
93
|
+
*/
|
|
94
|
+
readonly usesMicrophone?: boolean;
|
|
85
95
|
start(opts: EngineStartOptions): Promise<void>;
|
|
86
96
|
stop(): Promise<void>;
|
|
87
97
|
abort(): void;
|