@ecsplico/editor 3.1.0 → 3.2.0
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/CHANGELOG.md +41 -0
- package/dist/adapters/direct-ai.d.ts +36 -0
- package/dist/adapters/direct-ai.js +169 -0
- package/dist/adapters/direct-ai.js.map +1 -0
- package/dist/adapters/memory.js.map +1 -1
- package/dist/code/CodeSurface.vue.d.ts +2 -2
- package/dist/core/block-drag.js +3 -0
- package/dist/core/block-drag.js.map +1 -1
- package/dist/core/block-transfer.d.ts +44 -0
- package/dist/core/block-transfer.js +68 -0
- package/dist/core/block-transfer.js.map +1 -0
- package/dist/core/commands.js +5 -0
- package/dist/core/commands.js.map +1 -1
- package/dist/core/core-feature.d.ts +4 -1
- package/dist/core/core-feature.js +8 -3
- package/dist/core/core-feature.js.map +1 -1
- package/dist/core/editor-ui.d.ts +3 -1
- package/dist/core/editor-ui.js.map +1 -1
- package/dist/core/extension-points.d.ts +17 -1
- package/dist/core/feature.d.ts +6 -1
- package/dist/core/feature.js.map +1 -1
- package/dist/core/index.d.ts +4 -2
- package/dist/core/index.js +3 -2
- package/dist/core/schema/index.d.ts +24 -3
- package/dist/core/schema/index.js +21 -4
- package/dist/core/schema/index.js.map +1 -1
- package/dist/core/surface-session.js +3 -1
- package/dist/core/surface-session.js.map +1 -1
- package/dist/core/toolbar-items.js +1 -1
- package/dist/core/toolbar-items.js.map +1 -1
- package/dist/document/DocumentSurface.vue.d.ts +2 -2
- package/dist/features/ai/agent-op-review.js +1 -1
- package/dist/features/ai/agent-ops.js +2 -2
- package/dist/features/ai/ai-apply.js +1 -1
- package/dist/features/ai/assistant.js +1 -1
- package/dist/features/ai/components/AiSelectionMenu.js +2 -2
- package/dist/features/ai/components/AiSidebar.js +2 -2
- package/dist/features/ai/components/SettingsAiTab.js +1 -1
- package/dist/features/ai/components/SettingsModelProfiles.js +1 -1
- package/dist/features/ai/document-chat-model.js +1 -1
- package/dist/features/ai/document-chat.js +2 -2
- package/dist/features/ai/ghost-autocomplete.js +1 -1
- package/dist/features/ai/index.d.ts +4 -3
- package/dist/features/ai/index.js +6 -5
- package/dist/features/ai/protocol/complete.d.ts +19 -0
- package/dist/features/ai/protocol/complete.js +27 -1
- package/dist/features/ai/protocol/complete.js.map +1 -1
- package/dist/features/ai/protocol/generate.d.ts +95 -0
- package/dist/features/ai/protocol/generate.js +58 -0
- package/dist/features/ai/protocol/generate.js.map +1 -0
- package/dist/features/ai/protocol/index.d.ts +4 -3
- package/dist/features/ai/protocol/index.js +6 -5
- package/dist/features/ai/protocol/tasks.d.ts +3 -1
- package/dist/features/ai/protocol/tasks.js +74 -2
- package/dist/features/ai/protocol/tasks.js.map +1 -1
- package/dist/features/ai/react/AiSelectionMenu.js +2 -2
- package/dist/features/ai/react/AiSidebar.js +2 -2
- package/dist/features/ai/react/SettingsAiTab.js +1 -1
- package/dist/features/ai/react/SettingsModelProfiles.js +1 -1
- package/dist/features/ai/selection-flow.js +1 -1
- package/dist/features/ai/sidebar-flow.js +1 -1
- package/dist/features/block-ui/block-actions.d.ts +19 -3
- package/dist/features/block-ui/block-actions.js +49 -3
- package/dist/features/block-ui/block-actions.js.map +1 -1
- package/dist/features/block-ui/components/BlockActionsOverlay.js +24 -13
- package/dist/features/block-ui/components/BlockActionsOverlay.js.map +1 -1
- package/dist/features/block-ui/components/BlockActionsOverlay.vue.d.ts +12 -5
- package/dist/features/block-ui/components/BlockUi.js +52 -20
- package/dist/features/block-ui/components/BlockUi.js.map +1 -1
- package/dist/features/block-ui/components/BlockUi.vue.d.ts +6 -4
- package/dist/features/block-ui/feature.d.ts +8 -2
- package/dist/features/block-ui/feature.js +7 -2
- package/dist/features/block-ui/feature.js.map +1 -1
- package/dist/features/block-ui/index.d.ts +3 -2
- package/dist/features/block-ui/index.js +2 -2
- package/dist/features/block-ui/react/BlockActionsOverlay.d.ts +14 -7
- package/dist/features/block-ui/react/BlockActionsOverlay.js +25 -13
- package/dist/features/block-ui/react/BlockActionsOverlay.js.map +1 -1
- package/dist/features/block-ui/react/BlockUi.d.ts +6 -4
- package/dist/features/block-ui/react/BlockUi.js +43 -15
- package/dist/features/block-ui/react/BlockUi.js.map +1 -1
- package/dist/features/block-ui/react.d.ts +4 -2
- package/dist/features/block-ui/react.js +3 -3
- package/dist/features/block-ui/react.js.map +1 -1
- package/dist/features/block-ui/vue.d.ts +4 -2
- package/dist/features/block-ui/vue.js +3 -3
- package/dist/features/block-ui/vue.js.map +1 -1
- package/dist/features/composites/baskets.d.ts +27 -0
- package/dist/features/composites/baskets.js +46 -0
- package/dist/features/composites/baskets.js.map +1 -0
- package/dist/features/composites/components/CompositeBasketMenu.js +97 -0
- package/dist/features/composites/components/CompositeBasketMenu.js.map +1 -0
- package/dist/features/composites/components/CompositeChrome.js +79 -23
- package/dist/features/composites/components/CompositeChrome.js.map +1 -1
- package/dist/features/composites/components/CompositeFieldEditor.js +1 -0
- package/dist/features/composites/components/CompositeFieldEditor.js.map +1 -1
- package/dist/features/composites/components/CompositeView.js +17 -2
- package/dist/features/composites/components/CompositeView.js.map +1 -1
- package/dist/features/composites/components/DerivationBadge.js +12 -3
- package/dist/features/composites/components/DerivationBadge.js.map +1 -1
- package/dist/features/composites/components/FocusChip.js +53 -0
- package/dist/features/composites/components/FocusChip.js.map +1 -0
- package/dist/features/composites/components/FocusChip.vue.d.ts +14 -0
- package/dist/features/composites/components/SettingsEditingTab.js +40 -4
- package/dist/features/composites/components/SettingsEditingTab.js.map +1 -1
- package/dist/features/composites/derivation.d.ts +2 -1
- package/dist/features/composites/derivation.js +5 -1
- package/dist/features/composites/derivation.js.map +1 -1
- package/dist/features/composites/derived-chrome.js +13 -3
- package/dist/features/composites/derived-chrome.js.map +1 -1
- package/dist/features/composites/feature.d.ts +7 -1
- package/dist/features/composites/feature.js +96 -23
- package/dist/features/composites/feature.js.map +1 -1
- package/dist/features/composites/focus.d.ts +38 -0
- package/dist/features/composites/focus.js +86 -0
- package/dist/features/composites/focus.js.map +1 -0
- package/dist/features/composites/index.d.ts +6 -3
- package/dist/features/composites/index.js +6 -3
- package/dist/features/composites/meta-keys.d.ts +4 -0
- package/dist/features/composites/meta-keys.js +8 -4
- package/dist/features/composites/meta-keys.js.map +1 -1
- package/dist/features/composites/nodes.d.ts +14 -2
- package/dist/features/composites/nodes.js +61 -5
- package/dist/features/composites/nodes.js.map +1 -1
- package/dist/features/composites/outline.d.ts +4 -0
- package/dist/features/composites/outline.js +3 -0
- package/dist/features/composites/outline.js.map +1 -1
- package/dist/features/composites/react/CompositeBasketMenu.js +88 -0
- package/dist/features/composites/react/CompositeBasketMenu.js.map +1 -0
- package/dist/features/composites/react/CompositeChrome.js +52 -4
- package/dist/features/composites/react/CompositeChrome.js.map +1 -1
- package/dist/features/composites/react/CompositeView.js +12 -2
- package/dist/features/composites/react/CompositeView.js.map +1 -1
- package/dist/features/composites/react/DerivationBadge.js +10 -1
- package/dist/features/composites/react/DerivationBadge.js.map +1 -1
- package/dist/features/composites/react/FocusChip.d.ts +11 -0
- package/dist/features/composites/react/FocusChip.js +50 -0
- package/dist/features/composites/react/FocusChip.js.map +1 -0
- package/dist/features/composites/react/SettingsEditingTab.js +36 -1
- package/dist/features/composites/react/SettingsEditingTab.js.map +1 -1
- package/dist/features/composites/react.d.ts +2 -1
- package/dist/features/composites/react.js +3 -1
- package/dist/features/composites/react.js.map +1 -1
- package/dist/features/composites/schema.d.ts +5 -3
- package/dist/features/composites/schema.js +8 -4
- package/dist/features/composites/schema.js.map +1 -1
- package/dist/features/composites/split.d.ts +62 -0
- package/dist/features/composites/split.js +172 -0
- package/dist/features/composites/split.js.map +1 -0
- package/dist/features/composites/vue.d.ts +4 -3
- package/dist/features/composites/vue.js +7 -5
- package/dist/features/composites/vue.js.map +1 -1
- package/dist/features/media/index.js +1 -1
- package/dist/features/media/react/AudioRecorderModal.js +1 -1
- package/dist/features/media/react.js +1 -1
- package/dist/features/transfer/components/TransferMenu.js +93 -0
- package/dist/features/transfer/components/TransferMenu.js.map +1 -0
- package/dist/features/transfer/components/TransferMenu.vue.d.ts +15 -0
- package/dist/features/transfer/feature.d.ts +76 -0
- package/dist/features/transfer/feature.js +169 -0
- package/dist/features/transfer/feature.js.map +1 -0
- package/dist/features/transfer/index.d.ts +3 -0
- package/dist/features/transfer/index.js +3 -0
- package/dist/features/transfer/react/TransferMenu.d.ts +12 -0
- package/dist/features/transfer/react/TransferMenu.js +71 -0
- package/dist/features/transfer/react/TransferMenu.js.map +1 -0
- package/dist/features/transfer/react.d.ts +10 -0
- package/dist/features/transfer/react.js +11 -0
- package/dist/features/transfer/react.js.map +1 -0
- package/dist/features/transfer/transforms.d.ts +83 -0
- package/dist/features/transfer/transforms.js +137 -0
- package/dist/features/transfer/transforms.js.map +1 -0
- package/dist/features/transfer/vue.d.ts +9 -0
- package/dist/features/transfer/vue.js +11 -0
- package/dist/features/transfer/vue.js.map +1 -0
- package/dist/i18n/de/ai.js +4 -0
- package/dist/i18n/de/ai.js.map +1 -1
- package/dist/i18n/de/composites.js +20 -1
- package/dist/i18n/de/composites.js.map +1 -1
- package/dist/i18n/de/integrations.js +16 -0
- package/dist/i18n/de/integrations.js.map +1 -1
- package/dist/i18n/de/transfer.js +17 -0
- package/dist/i18n/de/transfer.js.map +1 -0
- package/dist/i18n/de/tree.js +6 -0
- package/dist/i18n/de/tree.js.map +1 -1
- package/dist/i18n/de.d.ts +55 -0
- package/dist/i18n/de.js +3 -1
- package/dist/i18n/de.js.map +1 -1
- package/dist/i18n/en/ai.js +4 -0
- package/dist/i18n/en/ai.js.map +1 -1
- package/dist/i18n/en/composites.js +20 -1
- package/dist/i18n/en/composites.js.map +1 -1
- package/dist/i18n/en/integrations.js +16 -0
- package/dist/i18n/en/integrations.js.map +1 -1
- package/dist/i18n/en/transfer.js +17 -0
- package/dist/i18n/en/transfer.js.map +1 -0
- package/dist/i18n/en/tree.js +6 -0
- package/dist/i18n/en/tree.js.map +1 -1
- package/dist/i18n/en.js +3 -1
- package/dist/i18n/en.js.map +1 -1
- package/dist/integrations/composite-ai/composite-fill.js +1 -1
- package/dist/integrations/composite-ai/composite-summary.js +1 -1
- package/dist/integrations/composite-ai/derive.d.ts +21 -0
- package/dist/integrations/composite-ai/derive.js +69 -47
- package/dist/integrations/composite-ai/derive.js.map +1 -1
- package/dist/integrations/composite-ai/index.d.ts +8 -3
- package/dist/integrations/composite-ai/index.js +50 -4
- package/dist/integrations/composite-ai/index.js.map +1 -1
- package/dist/integrations/composite-ai/outline-sanitize.d.ts +48 -0
- package/dist/integrations/composite-ai/outline-sanitize.js +175 -0
- package/dist/integrations/composite-ai/outline-sanitize.js.map +1 -0
- package/dist/integrations/composite-ai/split.d.ts +44 -0
- package/dist/integrations/composite-ai/split.js +187 -0
- package/dist/integrations/composite-ai/split.js.map +1 -0
- package/dist/integrations/composite-ai/transfer-transforms.d.ts +27 -0
- package/dist/integrations/composite-ai/transfer-transforms.js +153 -0
- package/dist/integrations/composite-ai/transfer-transforms.js.map +1 -0
- package/dist/integrations/transcript-ai/protocol.d.ts +13 -0
- package/dist/integrations/transcript-ai/protocol.js +49 -1
- package/dist/integrations/transcript-ai/protocol.js.map +1 -1
- package/dist/integrations/transcript-ai/transcript-cleanup.js +1 -1
- package/dist/markdown/MarkdownSurface.vue.d.ts +6 -6
- package/dist/nuxt/module.d.ts +110 -0
- package/dist/presets/compose.d.ts +24 -8
- package/dist/presets/compose.js +117 -58
- package/dist/presets/compose.js.map +1 -1
- package/dist/react/compose.js +6 -2
- package/dist/react/compose.js.map +1 -1
- package/dist/react/composed-surface.d.ts +9 -0
- package/dist/react/composed-surface.js +4 -1
- package/dist/react/composed-surface.js.map +1 -1
- package/dist/server/ai.d.ts +2 -5
- package/dist/server/ai.js.map +1 -1
- package/dist/server/handlers/ai.d.ts +7 -78
- package/dist/server/handlers/ai.js +30 -94
- package/dist/server/handlers/ai.js.map +1 -1
- package/dist/server/handlers/editor.d.ts +1 -0
- package/dist/server/handlers/index.d.ts +3 -1
- package/dist/server/handlers/index.js +3 -1
- package/dist/server/index.d.ts +2 -1
- package/dist/settings/index.d.ts +8 -0
- package/dist/settings/index.js +16 -4
- package/dist/settings/index.js.map +1 -1
- package/dist/styles/composites.css +32 -0
- package/dist/tree/DocumentTree.js +229 -78
- package/dist/tree/DocumentTree.js.map +1 -1
- package/dist/tree/drop-rules.js +8 -0
- package/dist/tree/drop-rules.js.map +1 -1
- package/dist/tree/react/DocumentTree.js +152 -31
- package/dist/tree/react/DocumentTree.js.map +1 -1
- package/dist/tree/tree-host.js +30 -1
- package/dist/tree/tree-host.js.map +1 -1
- package/dist/tree/tree-items.js +52 -5
- package/dist/tree/tree-items.js.map +1 -1
- package/dist/tree/tree-model.js +113 -10
- package/dist/tree/tree-model.js.map +1 -1
- package/dist/tree/tree-rows.js +20 -7
- package/dist/tree/tree-rows.js.map +1 -1
- package/dist/tree/tree-view-model.d.ts +5 -0
- package/dist/tree/tree-view-model.js +13 -0
- package/dist/tree/tree-view-model.js.map +1 -1
- package/dist/vue/AdvancedEditor.js +6 -0
- package/dist/vue/AdvancedEditor.js.map +1 -1
- package/dist/vue/AdvancedEditor.vue.d.ts +13 -5
- package/dist/vue/SimpleEditor.vue.d.ts +4 -4
- package/dist/vue/components/EditorSurface.vue.d.ts +4 -4
- package/dist/vue/compose.js +6 -2
- package/dist/vue/compose.js.map +1 -1
- package/dist/vue/kit/types.d.ts +2 -0
- package/package.json +21 -1
|
@@ -3,6 +3,7 @@ import { isChromeNode } from "../../../core/schema/node-flags.js";
|
|
|
3
3
|
import { useUiKit } from "../../../vue/kit/use-ui-kit.js";
|
|
4
4
|
import { useEditorSettings } from "../../../vue/settings.js";
|
|
5
5
|
import { firstLineCenterShift, gutterShiftOf, handleLeftOf, hasKindIcon } from "../handle-geometry.js";
|
|
6
|
+
import { actionSurfacesOf, sameBlock, selectedBlockOf } from "../block-actions.js";
|
|
6
7
|
import _sfc_main$1 from "./BlockActionsOverlay.js";
|
|
7
8
|
import _sfc_main$2 from "./BlockHandles.js";
|
|
8
9
|
import { Fragment, Teleport, computed, createBlock, createCommentVNode, createElementBlock, createVNode, defineComponent, nextTick, normalizeClass, onBeforeUnmount, onMounted, openBlock, shallowRef, toRaw, unref, useTemplateRef, withCtx } from "vue";
|
|
@@ -10,10 +11,12 @@ import { DragHandle } from "@tiptap/extension-drag-handle-vue-3";
|
|
|
10
11
|
//#region src/features/block-ui/components/BlockUi.vue
|
|
11
12
|
/**
|
|
12
13
|
* Block UI of an editor: the drag handle on the left of the hovered block (hidden while the editor is read-only) and the
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* block actions (`BlockActionsOverlay`) at the editor's right edge. The actions are those the surface registered for
|
|
15
|
+
* `editor` (features' block actions, then the host's), shown per `BlockUiOptions.actions`: on a hover bar for the block
|
|
16
|
+
* the drag handle targets, on a persistent toolbar for the block holding the selection, both, or neither. When both
|
|
17
|
+
* would show a bar on the same block, only the toolbar does. A block with a kind icon (a composite) gets no handle: its
|
|
18
|
+
* icon drags it. With `blocks.dragHandle: 'always'` the hover handle is replaced by permanent handles on every eligible
|
|
19
|
+
* block (`BlockHandles`). Mounted by the block-ui feature beside the editor.
|
|
17
20
|
*/
|
|
18
21
|
const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
19
22
|
__name: "BlockUi",
|
|
@@ -24,6 +27,16 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
24
27
|
const host = shallowRef(null);
|
|
25
28
|
const hoverTarget = shallowRef(null);
|
|
26
29
|
const actions = computed(() => editorEntryOf(toRaw(props.editor))?.blockActions ?? []);
|
|
30
|
+
const surfaces = computed(() => actionSurfacesOf(toRaw(props.editor)));
|
|
31
|
+
const selectedTarget = computed(() => {
|
|
32
|
+
props.editor.state;
|
|
33
|
+
return surfaces.value.toolbar ? selectedBlockOf(props.editor) : null;
|
|
34
|
+
});
|
|
35
|
+
const hoverBarTarget = computed(() => {
|
|
36
|
+
const hovered = hoverTarget.value;
|
|
37
|
+
const selected = selectedTarget.value;
|
|
38
|
+
return hovered && selected && sameBlock(props.editor, hovered, selected) ? null : hovered;
|
|
39
|
+
});
|
|
27
40
|
let overlayHovered = false;
|
|
28
41
|
let hoverClearTimer;
|
|
29
42
|
onMounted(async () => {
|
|
@@ -141,22 +154,41 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
141
154
|
return openBlock(), createElementBlock(Fragment, null, [host.value ? (openBlock(), createBlock(Teleport, {
|
|
142
155
|
key: 0,
|
|
143
156
|
to: host.value
|
|
144
|
-
}, [
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
157
|
+
}, [
|
|
158
|
+
surfaces.value.hover ? (openBlock(), createBlock(_sfc_main$1, {
|
|
159
|
+
key: 0,
|
|
160
|
+
editor: __props.editor,
|
|
161
|
+
target: hoverBarTarget.value,
|
|
162
|
+
host: host.value,
|
|
163
|
+
actions: actions.value,
|
|
164
|
+
surface: "hover",
|
|
165
|
+
onHover: onOverlayHover
|
|
166
|
+
}, null, 8, [
|
|
167
|
+
"editor",
|
|
168
|
+
"target",
|
|
169
|
+
"host",
|
|
170
|
+
"actions"
|
|
171
|
+
])) : createCommentVNode("v-if", true),
|
|
172
|
+
surfaces.value.toolbar ? (openBlock(), createBlock(_sfc_main$1, {
|
|
173
|
+
key: 1,
|
|
174
|
+
editor: __props.editor,
|
|
175
|
+
target: selectedTarget.value,
|
|
176
|
+
host: host.value,
|
|
177
|
+
actions: actions.value,
|
|
178
|
+
surface: "toolbar",
|
|
179
|
+
persistent: ""
|
|
180
|
+
}, null, 8, [
|
|
181
|
+
"editor",
|
|
182
|
+
"target",
|
|
183
|
+
"host",
|
|
184
|
+
"actions"
|
|
185
|
+
])) : createCommentVNode("v-if", true),
|
|
186
|
+
alwaysHandles.value ? (openBlock(), createBlock(_sfc_main$2, {
|
|
187
|
+
key: 2,
|
|
188
|
+
editor: __props.editor,
|
|
189
|
+
host: host.value
|
|
190
|
+
}, null, 8, ["editor", "host"])) : createCommentVNode("v-if", true)
|
|
191
|
+
], 8, ["to"])) : createCommentVNode("v-if", true), createVNode(unref(DragHandle), {
|
|
160
192
|
ref_key: "handle",
|
|
161
193
|
ref: handle,
|
|
162
194
|
editor: __props.editor,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BlockUi.js","names":["_openBlock","_createElementBlock","_Fragment","_createBlock","_Teleport","_createVNode","BlockActionsOverlay","BlockHandles","_createCommentVNode","_unref","_normalizeClass","_withCtx"],"sources":["../../../../src/features/block-ui/components/BlockUi.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { useUiKit } from '../../../vue/kit/use-ui-kit'\nimport { DragHandle, type DragHandleProps } from '@tiptap/extension-drag-handle-vue-3'\nimport type { Node } from '@tiptap/pm/model'\nimport type { Editor } from '@tiptap/vue-3'\nimport { type ComponentPublicInstance, computed, nextTick, onBeforeUnmount, onMounted, shallowRef, toRaw, useTemplateRef } from 'vue'\nimport { type BlockTarget, editorEntryOf } from '../../../core'\nimport { useEditorSettings } from '../../../vue/settings'\nimport { isChromeNode } from '../../../core/schema'\nimport { firstLineCenterShift, gutterShiftOf, handleLeftOf, hasKindIcon } from '../handle-geometry'\nimport BlockActionsOverlay from './BlockActionsOverlay.vue'\nimport BlockHandles from './BlockHandles.vue'\n\n/**\n * Block UI of an editor: the drag handle on the left of the hovered block (hidden while the editor is read-only) and the\n * hover actions (`BlockActionsOverlay`) at the editor's right edge, both targeting the same block. The actions are those\n * the surface registered for `editor` (features' block actions, then the host's). A block with a kind icon (a composite)\n * gets no handle: its icon drags it. With `blocks.dragHandle: 'always'` the hover handle is replaced by permanent\n * handles on every eligible block (`BlockHandles`). Mounted by the block-ui feature beside the editor.\n */\nconst props = defineProps<{ editor: Editor }>()\n\nconst { Icon: UIcon } = useUiKit()\n\n// Hover actions target the drag handle's block. The plugin drops its target when the pointer leaves the editor (also\n// onto the overlay, which sits outside it) and on keydown; a short grace keeps the overlay while the pointer moves\n// onto it, and typing hides it at once.\nconst host = shallowRef<HTMLElement | null>(null)\nconst hoverTarget = shallowRef<BlockTarget | null>(null)\nconst actions = computed(() => editorEntryOf(toRaw(props.editor))?.blockActions ?? [])\nlet overlayHovered = false\nlet hoverClearTimer: ReturnType<typeof setTimeout> | undefined\n\n// The overlay is positioned inside the element that holds the editor content (`relative`). Tiptap's content component\n// moves the editor DOM into it on the tick after it mounted, so look it up after that one.\nonMounted(async () => {\n await nextTick()\n const dom = props.editor.isDestroyed ? null : props.editor.view.dom\n host.value = dom?.isConnected ? dom.parentElement : null\n dom?.addEventListener('keydown', onEditorKeydown)\n dom?.addEventListener('mousemove', onEditorMousemove)\n})\n\nonBeforeUnmount(() => {\n clearTimeout(hoverClearTimer)\n if (props.editor.isDestroyed) return\n props.editor.view.dom.removeEventListener('keydown', onEditorKeydown)\n props.editor.view.dom.removeEventListener('mousemove', onEditorMousemove)\n})\n\n// Pointer position for the targeting rules below. The plugin evaluates them in a frame callback after the mousemove,\n// so this is the position they see.\nlet pointer: { x: number, y: number } | null = null\nfunction onEditorMousemove(e: MouseEvent) {\n pointer = { x: e.clientX, y: e.clientY }\n}\n\n// Like the drag-handle plugin: a key pressed in the focused editor hides the actions.\nfunction onEditorKeydown() {\n clearTimeout(hoverClearTimer)\n hoverTarget.value = null\n}\n\nfunction scheduleHoverClear() {\n clearTimeout(hoverClearTimer)\n hoverClearTimer = setTimeout(() => {\n if (!overlayHovered) hoverTarget.value = null\n }, 200)\n}\n\nfunction onOverlayHover(hovered: boolean) {\n overlayHovered = hovered\n if (!hovered) scheduleHoverClear()\n}\n\n// The drag-handle plugin hides the handle on dragend but keeps its current target. A dropped\n// nested node is the same node object, so hovering it again would not re-show the handle.\n// The plugin's `hideDragHandle` meta clears the target.\nfunction resetDragHandle() {\n const { editor } = props\n if (editor.isDestroyed) return\n editor.view.dispatch(editor.state.tr.setMeta('hideDragHandle', true).setMeta('addToHistory', false))\n}\n\n// The plugin puts the handle's right edge on the target block's left edge (`gutterShiftOf`: list items move further\n// left). The hover handle is hidden where the block has a handle of its own: the kind icon of a composite, or, with\n// `blocks.dragHandle: 'always'`, the permanent handles. Hiding keeps its layout, which the targeting rules below measure.\nconst { settings } = useEditorSettings()\nconst alwaysHandles = computed(() => settings.value.blocks.dragHandle === 'always')\nconst handle = useTemplateRef<ComponentPublicInstance>('handle')\nconst targetHasKindIcon = shallowRef(false)\nconst hoverHandleHidden = computed(() => alwaysHandles.value || targetHasKindIcon.value)\nlet handleGutterShift = 0\nlet dragTargetDom: HTMLElement | null = null\nlet dragTargetNode: Node | null = null\nfunction trackDragTarget({ node, pos }: { node: Node | null, pos: number }) {\n if (node) {\n clearTimeout(hoverClearTimer)\n hoverTarget.value = { node, pos }\n }\n else {\n scheduleHoverClear()\n }\n const dom = node ? props.editor.view.nodeDOM(pos) : null\n dragTargetDom = dom instanceof HTMLElement ? dom : null\n dragTargetNode = dragTargetDom ? node : null\n handleGutterShift = dragTargetDom && dragTargetNode ? gutterShiftOf(dragTargetNode, dragTargetDom) : 0\n targetHasKindIcon.value = dragTargetDom ? hasKindIcon(dragTargetDom) : false\n}\n\n// Left of a block, ProseMirror resolves the pointer to the position before the block, so the plugin only considers\n// the block's ancestors there: the target's own handle column would hand the handle to its parent before the pointer\n// reaches it. This keeps the target while the pointer is between its handle and its box, over its whole height\n// including the gaps to its neighbours, and in those gaps themselves (which resolve to the parent as well). The rule\n// then excludes every candidate, and the plugin keeps its target when the detection yields none. Inside the target's\n// own box the rule is off, so its descendants are still targeted.\nfunction pointerReachesTarget(): boolean {\n if (!pointer || !dragTargetNode || !dragTargetDom?.isConnected) return false\n const rect = dragTargetDom.getBoundingClientRect()\n const { x, y } = pointer\n if (x < handleLeftOf(dragTargetNode, dragTargetDom, handle.value?.$el.offsetWidth ?? 0) || x >= rect.right) return false\n if (y < (laidOutSibling(dragTargetDom, 'previous')?.bottom ?? rect.top)) return false\n if (y >= (laidOutSibling(dragTargetDom, 'next')?.top ?? rect.bottom)) return false\n return x < rect.left || y < rect.top || y >= rect.bottom\n}\n\n// Nearest sibling with a layout box (a composite's hidden description/tags rows have none).\nfunction laidOutSibling(dom: Element, side: 'previous' | 'next'): DOMRect | null {\n for (let el = dom[`${side}ElementSibling`]; el; el = el[`${side}ElementSibling`]) {\n const rect = el.getBoundingClientRect()\n if (rect.height > 0) return rect\n }\n return null\n}\n\n// The plugin aligns the handle's top with the block's top, which sits above the middle of the\n// block's first text line: `firstLineCenterShift` moves the handle's centre to that line's centre.\nconst dragHandlePosition: DragHandleProps['computePositionConfig'] = {\n middleware: [\n { name: 'gutterShift', fn: ({ x, y }) => ({ x: x - handleGutterShift, y }) },\n {\n name: 'firstLineCenter',\n fn: ({ x, y, elements, rects }) => ({\n x,\n y: dragTargetDom ? y + firstLineCenterShift(dragTargetDom, elements.reference.getBoundingClientRect().top, rects.floating.height) : y,\n }),\n },\n ],\n}\n\n// Targeting among nested blocks. `compositeChrome`: hovering a composite's title, description or tags row targets the\n// composite itself (a deduction of 1000, the base score, excludes a node). `reachTarget`: see `pointerReachesTarget`.\n// `ownGutter`: a nested block is targeted only from its handle column rightwards; further left its ancestors are, so\n// on any row the gutter is partitioned by the handle columns of the blocks nested there. The plugin's edge detection\n// is off: its left strip is this partition, its top strip would hand the handle to the parent on the block's first\n// line, where the handle sits.\nconst dragHandleNested: DragHandleProps['nested'] = {\n edgeDetection: 'none',\n rules: [\n { id: 'compositeChrome', evaluate: ({ node }) => isChromeNode(node) ? 1000 : 0 },\n { id: 'reachTarget', evaluate: () => pointerReachesTarget() ? 1000 : 0 },\n {\n id: 'ownGutter',\n evaluate: ({ node, pos, depth, view }) => {\n if (depth < 2 || !pointer) return 0\n const dom = view.nodeDOM(pos)\n return dom instanceof HTMLElement && pointer.x < handleLeftOf(node, dom, handle.value?.$el.offsetWidth ?? 0) ? 1000 : 0\n },\n },\n ],\n}\n</script>\n\n<template>\n <Teleport v-if=\"host\" :to=\"host\">\n <BlockActionsOverlay\n :editor=\"editor\"\n :target=\"hoverTarget\"\n :host=\"host\"\n :actions=\"actions\"\n @hover=\"onOverlayHover\"\n />\n <BlockHandles v-if=\"alwaysHandles\" :editor=\"editor\" :host=\"host\" />\n </Teleport>\n <DragHandle\n ref=\"handle\"\n :editor=\"editor\"\n :on-element-drag-end=\"resetDragHandle\"\n :on-node-change=\"trackDragTarget\"\n :compute-position-config=\"dragHandlePosition\"\n :nested=\"dragHandleNested\"\n :class=\"{ 'invisible! pointer-events-none!': hoverHandleHidden }\"\n class=\"flex size-6 cursor-grab items-center justify-center rounded text-[var(--sk-muted)] hover:bg-[var(--sk-bg-elevated)] hover:text-[var(--sk-fg-highlighted)] data-[dragging=true]:cursor-grabbing\"\n >\n <UIcon name=\"i-lucide-grip-vertical\" class=\"size-4\" />\n </DragHandle>\n</template>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"BlockUi.js","names":["_openBlock","_createElementBlock","_Fragment","_createBlock","_Teleport","BlockActionsOverlay","_createCommentVNode","BlockHandles","_createVNode","_unref","_normalizeClass","_withCtx"],"sources":["../../../../src/features/block-ui/components/BlockUi.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { useUiKit } from '../../../vue/kit/use-ui-kit'\nimport { DragHandle, type DragHandleProps } from '@tiptap/extension-drag-handle-vue-3'\nimport type { Node } from '@tiptap/pm/model'\nimport type { Editor } from '@tiptap/vue-3'\nimport { type ComponentPublicInstance, computed, nextTick, onBeforeUnmount, onMounted, shallowRef, toRaw, useTemplateRef } from 'vue'\nimport { type BlockTarget, editorEntryOf } from '../../../core'\nimport { useEditorSettings } from '../../../vue/settings'\nimport { isChromeNode } from '../../../core/schema'\nimport { firstLineCenterShift, gutterShiftOf, handleLeftOf, hasKindIcon } from '../handle-geometry'\nimport { actionSurfacesOf, sameBlock, selectedBlockOf } from '../block-actions'\nimport BlockActionsOverlay from './BlockActionsOverlay.vue'\nimport BlockHandles from './BlockHandles.vue'\n\n/**\n * Block UI of an editor: the drag handle on the left of the hovered block (hidden while the editor is read-only) and the\n * block actions (`BlockActionsOverlay`) at the editor's right edge. The actions are those the surface registered for\n * `editor` (features' block actions, then the host's), shown per `BlockUiOptions.actions`: on a hover bar for the block\n * the drag handle targets, on a persistent toolbar for the block holding the selection, both, or neither. When both\n * would show a bar on the same block, only the toolbar does. A block with a kind icon (a composite) gets no handle: its\n * icon drags it. With `blocks.dragHandle: 'always'` the hover handle is replaced by permanent handles on every eligible\n * block (`BlockHandles`). Mounted by the block-ui feature beside the editor.\n */\nconst props = defineProps<{ editor: Editor }>()\n\nconst { Icon: UIcon } = useUiKit()\n\n// Hover actions target the drag handle's block. The plugin drops its target when the pointer leaves the editor (also\n// onto the overlay, which sits outside it) and on keydown; a short grace keeps the overlay while the pointer moves\n// onto it, and typing hides it at once.\nconst host = shallowRef<HTMLElement | null>(null)\nconst hoverTarget = shallowRef<BlockTarget | null>(null)\nconst actions = computed(() => editorEntryOf(toRaw(props.editor))?.blockActions ?? [])\n// Read inside the teleport only: the surface registers its entry after its overlays have mounted.\nconst surfaces = computed(() => actionSurfacesOf(toRaw(props.editor)))\n// The block holding the selection follows every transaction (`editor.state` is reactive).\nconst selectedTarget = computed(() => {\n void props.editor.state\n return surfaces.value.toolbar ? selectedBlockOf(props.editor) : null\n})\n// One bar per block: the toolbar wins where the hover bar would sit on the same block.\nconst hoverBarTarget = computed(() => {\n const hovered = hoverTarget.value\n const selected = selectedTarget.value\n return hovered && selected && sameBlock(props.editor, hovered, selected) ? null : hovered\n})\nlet overlayHovered = false\nlet hoverClearTimer: ReturnType<typeof setTimeout> | undefined\n\n// The overlay is positioned inside the element that holds the editor content (`relative`). Tiptap's content component\n// moves the editor DOM into it on the tick after it mounted, so look it up after that one.\nonMounted(async () => {\n await nextTick()\n const dom = props.editor.isDestroyed ? null : props.editor.view.dom\n host.value = dom?.isConnected ? dom.parentElement : null\n dom?.addEventListener('keydown', onEditorKeydown)\n dom?.addEventListener('mousemove', onEditorMousemove)\n})\n\nonBeforeUnmount(() => {\n clearTimeout(hoverClearTimer)\n if (props.editor.isDestroyed) return\n props.editor.view.dom.removeEventListener('keydown', onEditorKeydown)\n props.editor.view.dom.removeEventListener('mousemove', onEditorMousemove)\n})\n\n// Pointer position for the targeting rules below. The plugin evaluates them in a frame callback after the mousemove,\n// so this is the position they see.\nlet pointer: { x: number, y: number } | null = null\nfunction onEditorMousemove(e: MouseEvent) {\n pointer = { x: e.clientX, y: e.clientY }\n}\n\n// Like the drag-handle plugin: a key pressed in the focused editor hides the actions.\nfunction onEditorKeydown() {\n clearTimeout(hoverClearTimer)\n hoverTarget.value = null\n}\n\nfunction scheduleHoverClear() {\n clearTimeout(hoverClearTimer)\n hoverClearTimer = setTimeout(() => {\n if (!overlayHovered) hoverTarget.value = null\n }, 200)\n}\n\nfunction onOverlayHover(hovered: boolean) {\n overlayHovered = hovered\n if (!hovered) scheduleHoverClear()\n}\n\n// The drag-handle plugin hides the handle on dragend but keeps its current target. A dropped\n// nested node is the same node object, so hovering it again would not re-show the handle.\n// The plugin's `hideDragHandle` meta clears the target.\nfunction resetDragHandle() {\n const { editor } = props\n if (editor.isDestroyed) return\n editor.view.dispatch(editor.state.tr.setMeta('hideDragHandle', true).setMeta('addToHistory', false))\n}\n\n// The plugin puts the handle's right edge on the target block's left edge (`gutterShiftOf`: list items move further\n// left). The hover handle is hidden where the block has a handle of its own: the kind icon of a composite, or, with\n// `blocks.dragHandle: 'always'`, the permanent handles. Hiding keeps its layout, which the targeting rules below measure.\nconst { settings } = useEditorSettings()\nconst alwaysHandles = computed(() => settings.value.blocks.dragHandle === 'always')\nconst handle = useTemplateRef<ComponentPublicInstance>('handle')\nconst targetHasKindIcon = shallowRef(false)\nconst hoverHandleHidden = computed(() => alwaysHandles.value || targetHasKindIcon.value)\nlet handleGutterShift = 0\nlet dragTargetDom: HTMLElement | null = null\nlet dragTargetNode: Node | null = null\nfunction trackDragTarget({ node, pos }: { node: Node | null, pos: number }) {\n if (node) {\n clearTimeout(hoverClearTimer)\n hoverTarget.value = { node, pos }\n }\n else {\n scheduleHoverClear()\n }\n const dom = node ? props.editor.view.nodeDOM(pos) : null\n dragTargetDom = dom instanceof HTMLElement ? dom : null\n dragTargetNode = dragTargetDom ? node : null\n handleGutterShift = dragTargetDom && dragTargetNode ? gutterShiftOf(dragTargetNode, dragTargetDom) : 0\n targetHasKindIcon.value = dragTargetDom ? hasKindIcon(dragTargetDom) : false\n}\n\n// Left of a block, ProseMirror resolves the pointer to the position before the block, so the plugin only considers\n// the block's ancestors there: the target's own handle column would hand the handle to its parent before the pointer\n// reaches it. This keeps the target while the pointer is between its handle and its box, over its whole height\n// including the gaps to its neighbours, and in those gaps themselves (which resolve to the parent as well). The rule\n// then excludes every candidate, and the plugin keeps its target when the detection yields none. Inside the target's\n// own box the rule is off, so its descendants are still targeted.\nfunction pointerReachesTarget(): boolean {\n if (!pointer || !dragTargetNode || !dragTargetDom?.isConnected) return false\n const rect = dragTargetDom.getBoundingClientRect()\n const { x, y } = pointer\n if (x < handleLeftOf(dragTargetNode, dragTargetDom, handle.value?.$el.offsetWidth ?? 0) || x >= rect.right) return false\n if (y < (laidOutSibling(dragTargetDom, 'previous')?.bottom ?? rect.top)) return false\n if (y >= (laidOutSibling(dragTargetDom, 'next')?.top ?? rect.bottom)) return false\n return x < rect.left || y < rect.top || y >= rect.bottom\n}\n\n// Nearest sibling with a layout box (a composite's hidden description/tags rows have none).\nfunction laidOutSibling(dom: Element, side: 'previous' | 'next'): DOMRect | null {\n for (let el = dom[`${side}ElementSibling`]; el; el = el[`${side}ElementSibling`]) {\n const rect = el.getBoundingClientRect()\n if (rect.height > 0) return rect\n }\n return null\n}\n\n// The plugin aligns the handle's top with the block's top, which sits above the middle of the\n// block's first text line: `firstLineCenterShift` moves the handle's centre to that line's centre.\nconst dragHandlePosition: DragHandleProps['computePositionConfig'] = {\n middleware: [\n { name: 'gutterShift', fn: ({ x, y }) => ({ x: x - handleGutterShift, y }) },\n {\n name: 'firstLineCenter',\n fn: ({ x, y, elements, rects }) => ({\n x,\n y: dragTargetDom ? y + firstLineCenterShift(dragTargetDom, elements.reference.getBoundingClientRect().top, rects.floating.height) : y,\n }),\n },\n ],\n}\n\n// Targeting among nested blocks. `compositeChrome`: hovering a composite's title, description or tags row targets the\n// composite itself (a deduction of 1000, the base score, excludes a node). `reachTarget`: see `pointerReachesTarget`.\n// `ownGutter`: a nested block is targeted only from its handle column rightwards; further left its ancestors are, so\n// on any row the gutter is partitioned by the handle columns of the blocks nested there. The plugin's edge detection\n// is off: its left strip is this partition, its top strip would hand the handle to the parent on the block's first\n// line, where the handle sits.\nconst dragHandleNested: DragHandleProps['nested'] = {\n edgeDetection: 'none',\n rules: [\n { id: 'compositeChrome', evaluate: ({ node }) => isChromeNode(node) ? 1000 : 0 },\n { id: 'reachTarget', evaluate: () => pointerReachesTarget() ? 1000 : 0 },\n {\n id: 'ownGutter',\n evaluate: ({ node, pos, depth, view }) => {\n if (depth < 2 || !pointer) return 0\n const dom = view.nodeDOM(pos)\n return dom instanceof HTMLElement && pointer.x < handleLeftOf(node, dom, handle.value?.$el.offsetWidth ?? 0) ? 1000 : 0\n },\n },\n ],\n}\n</script>\n\n<template>\n <Teleport v-if=\"host\" :to=\"host\">\n <BlockActionsOverlay\n v-if=\"surfaces.hover\"\n :editor=\"editor\"\n :target=\"hoverBarTarget\"\n :host=\"host\"\n :actions=\"actions\"\n surface=\"hover\"\n @hover=\"onOverlayHover\"\n />\n <BlockActionsOverlay\n v-if=\"surfaces.toolbar\"\n :editor=\"editor\"\n :target=\"selectedTarget\"\n :host=\"host\"\n :actions=\"actions\"\n surface=\"toolbar\"\n persistent\n />\n <BlockHandles v-if=\"alwaysHandles\" :editor=\"editor\" :host=\"host\" />\n </Teleport>\n <DragHandle\n ref=\"handle\"\n :editor=\"editor\"\n :on-element-drag-end=\"resetDragHandle\"\n :on-node-change=\"trackDragTarget\"\n :compute-position-config=\"dragHandlePosition\"\n :nested=\"dragHandleNested\"\n :class=\"{ 'invisible! pointer-events-none!': hoverHandleHidden }\"\n class=\"flex size-6 cursor-grab items-center justify-center rounded text-[var(--sk-muted)] hover:bg-[var(--sk-bg-elevated)] hover:text-[var(--sk-fg-highlighted)] data-[dragging=true]:cursor-grabbing\"\n >\n <UIcon name=\"i-lucide-grip-vertical\" class=\"size-4\" />\n </DragHandle>\n</template>\n"],"mappings":"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"}
|
|
@@ -2,10 +2,12 @@ import { Editor } from "@tiptap/vue-3";
|
|
|
2
2
|
//#region src/features/block-ui/components/BlockUi.vue.d.ts
|
|
3
3
|
/**
|
|
4
4
|
* Block UI of an editor: the drag handle on the left of the hovered block (hidden while the editor is read-only) and the
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* block actions (`BlockActionsOverlay`) at the editor's right edge. The actions are those the surface registered for
|
|
6
|
+
* `editor` (features' block actions, then the host's), shown per `BlockUiOptions.actions`: on a hover bar for the block
|
|
7
|
+
* the drag handle targets, on a persistent toolbar for the block holding the selection, both, or neither. When both
|
|
8
|
+
* would show a bar on the same block, only the toolbar does. A block with a kind icon (a composite) gets no handle: its
|
|
9
|
+
* icon drags it. With `blocks.dragHandle: 'always'` the hover handle is replaced by permanent handles on every eligible
|
|
10
|
+
* block (`BlockHandles`). Mounted by the block-ui feature beside the editor.
|
|
9
11
|
*/
|
|
10
12
|
type __VLS_Props = {
|
|
11
13
|
editor: Editor;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { BlockActionSurface, BlockUiOptions } from "../../core/extension-points.js";
|
|
1
2
|
import { EditorFeature } from "../../core/feature.js";
|
|
2
3
|
import "../../core/index.js";
|
|
3
4
|
//#region src/features/block-ui/feature.d.ts
|
|
@@ -8,14 +9,19 @@ export interface BlockUiUi<C> {
|
|
|
8
9
|
}
|
|
9
10
|
/**
|
|
10
11
|
* Block-level UI beside the editor content: the drag handle (upstream `@tiptap/extension-drag-handle`) with hover
|
|
11
|
-
* targeting, and the
|
|
12
|
+
* targeting, and the actions of the targeted block (features' and host block actions, `BlockActionsOverlay`), on the
|
|
13
|
+
* hover bar, the toolbar of the block holding the selection, or both (`BlockUiOptions.actions`; hover by default).
|
|
12
14
|
* Also adds block-range selection (`NodeRange`: Mod+drag, Shift+↑/↓). Composite chrome (`editorChrome` nodes) targets its
|
|
13
15
|
* parent block.
|
|
14
16
|
*
|
|
15
17
|
* Opt-in: the simple editor has none of it. Installing a binding also bundles the drag handle's transitive collaboration
|
|
16
18
|
* support code (its upstream package imports `@tiptap/extension-collaboration` and `y-tiptap`), even without a live
|
|
17
19
|
* collaboration session.
|
|
20
|
+
*
|
|
21
|
+
* The overlay is a component type the surface mounts without props, so `options` reach it through `EditorFeature.blockUi`
|
|
22
|
+
* and the editor entry (`editorEntryOf(editor)?.blockUi`).
|
|
18
23
|
*/
|
|
19
|
-
export declare function blockUiFeatureCore<C>(ui: BlockUiUi<C
|
|
24
|
+
export declare function blockUiFeatureCore<C>(ui: BlockUiUi<C>, options?: BlockUiOptions): EditorFeature<C>;
|
|
20
25
|
//#endregion
|
|
26
|
+
export type { BlockActionSurface, BlockUiOptions };
|
|
21
27
|
//# sourceMappingURL=feature.d.ts.map
|
|
@@ -2,19 +2,24 @@ import { NodeRange } from "@tiptap/extension-node-range";
|
|
|
2
2
|
//#region src/features/block-ui/feature.ts
|
|
3
3
|
/**
|
|
4
4
|
* Block-level UI beside the editor content: the drag handle (upstream `@tiptap/extension-drag-handle`) with hover
|
|
5
|
-
* targeting, and the
|
|
5
|
+
* targeting, and the actions of the targeted block (features' and host block actions, `BlockActionsOverlay`), on the
|
|
6
|
+
* hover bar, the toolbar of the block holding the selection, or both (`BlockUiOptions.actions`; hover by default).
|
|
6
7
|
* Also adds block-range selection (`NodeRange`: Mod+drag, Shift+↑/↓). Composite chrome (`editorChrome` nodes) targets its
|
|
7
8
|
* parent block.
|
|
8
9
|
*
|
|
9
10
|
* Opt-in: the simple editor has none of it. Installing a binding also bundles the drag handle's transitive collaboration
|
|
10
11
|
* support code (its upstream package imports `@tiptap/extension-collaboration` and `y-tiptap`), even without a live
|
|
11
12
|
* collaboration session.
|
|
13
|
+
*
|
|
14
|
+
* The overlay is a component type the surface mounts without props, so `options` reach it through `EditorFeature.blockUi`
|
|
15
|
+
* and the editor entry (`editorEntryOf(editor)?.blockUi`).
|
|
12
16
|
*/
|
|
13
|
-
function blockUiFeatureCore(ui) {
|
|
17
|
+
function blockUiFeatureCore(ui, options = {}) {
|
|
14
18
|
return {
|
|
15
19
|
id: "block-ui",
|
|
16
20
|
dimension: "interaction",
|
|
17
21
|
extensions: () => [NodeRange],
|
|
22
|
+
blockUi: options,
|
|
18
23
|
overlays: [ui.overlay]
|
|
19
24
|
};
|
|
20
25
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"feature.js","names":[],"sources":["../../../src/features/block-ui/feature.ts"],"sourcesContent":["import { NodeRange } from '@tiptap/extension-node-range'\nimport type { EditorFeature } from '../../core'\n\n/** What a framework binding supplies to the block-ui feature. */\nexport interface BlockUiUi<C> {\n /** The block UI of an editor (drag handle, permanent handles, hover actions); mounted beside the editor content. */\n overlay: C\n}\n\n/**\n * Block-level UI beside the editor content: the drag handle (upstream `@tiptap/extension-drag-handle`) with hover\n * targeting, and the
|
|
1
|
+
{"version":3,"file":"feature.js","names":[],"sources":["../../../src/features/block-ui/feature.ts"],"sourcesContent":["import { NodeRange } from '@tiptap/extension-node-range'\nimport type { BlockUiOptions, EditorFeature } from '../../core'\n\n// The option types live in `core` (the editor entry carries them to the overlay); this is their public home.\nexport type { BlockActionSurface, BlockUiOptions } from '../../core'\n\n/** What a framework binding supplies to the block-ui feature. */\nexport interface BlockUiUi<C> {\n /** The block UI of an editor (drag handle, permanent handles, hover actions); mounted beside the editor content. */\n overlay: C\n}\n\n/**\n * Block-level UI beside the editor content: the drag handle (upstream `@tiptap/extension-drag-handle`) with hover\n * targeting, and the actions of the targeted block (features' and host block actions, `BlockActionsOverlay`), on the\n * hover bar, the toolbar of the block holding the selection, or both (`BlockUiOptions.actions`; hover by default).\n * Also adds block-range selection (`NodeRange`: Mod+drag, Shift+↑/↓). Composite chrome (`editorChrome` nodes) targets its\n * parent block.\n *\n * Opt-in: the simple editor has none of it. Installing a binding also bundles the drag handle's transitive collaboration\n * support code (its upstream package imports `@tiptap/extension-collaboration` and `y-tiptap`), even without a live\n * collaboration session.\n *\n * The overlay is a component type the surface mounts without props, so `options` reach it through `EditorFeature.blockUi`\n * and the editor entry (`editorEntryOf(editor)?.blockUi`).\n */\nexport function blockUiFeatureCore<C>(ui: BlockUiUi<C>, options: BlockUiOptions = {}): EditorFeature<C> {\n return {\n id: 'block-ui',\n dimension: 'interaction',\n extensions: () => [NodeRange],\n blockUi: options,\n overlays: [ui.overlay],\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;AA0BA,SAAgB,mBAAsB,IAAkB,UAA0B,CAAC,GAAqB;CACtG,OAAO;EACL,IAAI;EACJ,WAAW;EACX,kBAAkB,CAAC,SAAS;EAC5B,SAAS;EACT,UAAU,CAAC,GAAG,OAAO;CACvB;AACF"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { BlockActionSurface, BlockUiOptions } from "../../core/extension-points.js";
|
|
2
|
+
import { BLOCK_CORNER_ATTR, BlockActionEntry, BlockCorner, actionSurfacesOf, blockActionsFor, blockCornerOf, effectiveTarget, sameBlock, selectedBlockOf } from "./block-actions.js";
|
|
2
3
|
import { BlockUiUi, blockUiFeatureCore } from "./feature.js";
|
|
3
|
-
export { BLOCK_CORNER_ATTR, type BlockActionEntry, type BlockCorner, type BlockUiUi, blockActionsFor, blockCornerOf, blockUiFeatureCore, effectiveTarget };
|
|
4
|
+
export { BLOCK_CORNER_ATTR, type BlockActionEntry, type BlockActionSurface, type BlockCorner, type BlockUiOptions, type BlockUiUi, actionSurfacesOf, blockActionsFor, blockCornerOf, blockUiFeatureCore, effectiveTarget, sameBlock, selectedBlockOf };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { blockUiFeatureCore } from "./feature.js";
|
|
2
|
-
import { BLOCK_CORNER_ATTR, blockActionsFor, blockCornerOf, effectiveTarget } from "./block-actions.js";
|
|
3
|
-
export { BLOCK_CORNER_ATTR, blockActionsFor, blockCornerOf, blockUiFeatureCore, effectiveTarget };
|
|
2
|
+
import { BLOCK_CORNER_ATTR, actionSurfacesOf, blockActionsFor, blockCornerOf, effectiveTarget, sameBlock, selectedBlockOf } from "./block-actions.js";
|
|
3
|
+
export { BLOCK_CORNER_ATTR, actionSurfacesOf, blockActionsFor, blockCornerOf, blockUiFeatureCore, effectiveTarget, sameBlock, selectedBlockOf };
|
|
@@ -1,23 +1,30 @@
|
|
|
1
|
-
import { BlockAction, BlockTarget } from "../../../core/extension-points.js";
|
|
1
|
+
import { BlockAction, BlockActionSurface, BlockTarget } from "../../../core/extension-points.js";
|
|
2
2
|
import "../../../core/index.js";
|
|
3
3
|
import { Editor } from "@tiptap/core";
|
|
4
4
|
//#region src/features/block-ui/react/BlockActionsOverlay.d.ts
|
|
5
5
|
/**
|
|
6
|
-
* The
|
|
7
|
-
* targets the same block). Positioned absolutely inside `host`, a `relative` element around the editor; the
|
|
8
|
-
* chrome (title, description, tags) addresses the composite (`effectiveTarget`). The bar is hidden while the
|
|
9
|
-
* read-only. `menu` actions open a dropdown of their items instead of running on click.
|
|
6
|
+
* The actions of a block on one surface, right-aligned at the editor's text column (the drag handle sits on the block's
|
|
7
|
+
* left and targets the same block). Positioned absolutely inside `host`, a `relative` element around the editor; the
|
|
8
|
+
* composite chrome (title, description, tags) addresses the composite (`effectiveTarget`). The bar is hidden while the
|
|
9
|
+
* editor is read-only. `menu` actions open a dropdown of their items instead of running on click.
|
|
10
|
+
*
|
|
11
|
+
* `surface` selects the actions by their `placement`. The `hover` surface follows the pointer (`target` is the hovered
|
|
12
|
+
* block, `onHover` keeps it alive while the pointer is on the bar); the `toolbar` surface is `persistent`: `target` is
|
|
13
|
+
* the block holding the selection, the bar shows as long as it is set, reports no hover and carries the class
|
|
14
|
+
* `block-actions-toolbar` for host styling.
|
|
10
15
|
*
|
|
11
16
|
* A block that shows icons of its own in its top-right corner marks them with `data-block-corner` (`BLOCK_CORNER_ATTR`):
|
|
12
17
|
* the bar then docks immediately left of them, centred on their row, instead of covering them. Without such elements
|
|
13
18
|
* it sits at the editor's right edge, aligned to the block's top.
|
|
14
19
|
*/
|
|
15
|
-
export declare function BlockActionsOverlay({ editor, target, actions, host, onHover }: {
|
|
20
|
+
export declare function BlockActionsOverlay({ editor, target, actions, host, surface, persistent, onHover }: {
|
|
16
21
|
editor: Editor;
|
|
17
22
|
target: BlockTarget | null;
|
|
18
23
|
actions: readonly BlockAction[];
|
|
19
24
|
host: HTMLElement | null;
|
|
20
|
-
|
|
25
|
+
surface: BlockActionSurface;
|
|
26
|
+
persistent?: boolean;
|
|
27
|
+
onHover?: (hovered: boolean) => void;
|
|
21
28
|
}): import("react").JSX.Element | null;
|
|
22
29
|
//#endregion
|
|
23
30
|
//# sourceMappingURL=BlockActionsOverlay.d.ts.map
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { cn } from "../../../react/ui/utils.js";
|
|
1
2
|
import { Icon } from "../../../react/Icon.js";
|
|
2
3
|
import { Button } from "../../../react/ui/button.js";
|
|
3
4
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../../../react/ui/tooltip.js";
|
|
4
|
-
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from "../../../react/ui/dropdown-menu.js";
|
|
5
5
|
import { blockActionsFor, blockCornerOf, effectiveTarget } from "../block-actions.js";
|
|
6
|
+
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from "../../../react/ui/dropdown-menu.js";
|
|
6
7
|
import { useEffect, useLayoutEffect, useReducer, useRef, useState } from "react";
|
|
7
8
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
8
9
|
//#region src/features/block-ui/react/BlockActionsOverlay.tsx
|
|
@@ -11,16 +12,21 @@ const EDGE_GUTTER = 8;
|
|
|
11
12
|
/** Gap between the bar and the block's own corner elements it docks next to. */
|
|
12
13
|
const CORNER_GAP = 4;
|
|
13
14
|
/**
|
|
14
|
-
* The
|
|
15
|
-
* targets the same block). Positioned absolutely inside `host`, a `relative` element around the editor; the
|
|
16
|
-
* chrome (title, description, tags) addresses the composite (`effectiveTarget`). The bar is hidden while the
|
|
17
|
-
* read-only. `menu` actions open a dropdown of their items instead of running on click.
|
|
15
|
+
* The actions of a block on one surface, right-aligned at the editor's text column (the drag handle sits on the block's
|
|
16
|
+
* left and targets the same block). Positioned absolutely inside `host`, a `relative` element around the editor; the
|
|
17
|
+
* composite chrome (title, description, tags) addresses the composite (`effectiveTarget`). The bar is hidden while the
|
|
18
|
+
* editor is read-only. `menu` actions open a dropdown of their items instead of running on click.
|
|
19
|
+
*
|
|
20
|
+
* `surface` selects the actions by their `placement`. The `hover` surface follows the pointer (`target` is the hovered
|
|
21
|
+
* block, `onHover` keeps it alive while the pointer is on the bar); the `toolbar` surface is `persistent`: `target` is
|
|
22
|
+
* the block holding the selection, the bar shows as long as it is set, reports no hover and carries the class
|
|
23
|
+
* `block-actions-toolbar` for host styling.
|
|
18
24
|
*
|
|
19
25
|
* A block that shows icons of its own in its top-right corner marks them with `data-block-corner` (`BLOCK_CORNER_ATTR`):
|
|
20
26
|
* the bar then docks immediately left of them, centred on their row, instead of covering them. Without such elements
|
|
21
27
|
* it sits at the editor's right edge, aligned to the block's top.
|
|
22
28
|
*/
|
|
23
|
-
function BlockActionsOverlay({ editor, target, actions, host, onHover }) {
|
|
29
|
+
function BlockActionsOverlay({ editor, target, actions, host, surface, persistent, onHover }) {
|
|
24
30
|
const [, rerender] = useReducer((count) => count + 1, 0);
|
|
25
31
|
useEffect(() => {
|
|
26
32
|
editor.on("transaction", rerender);
|
|
@@ -39,7 +45,7 @@ function BlockActionsOverlay({ editor, target, actions, host, onHover }) {
|
|
|
39
45
|
pos: target.pos
|
|
40
46
|
});
|
|
41
47
|
})();
|
|
42
|
-
const items = block && editor.isEditable ? blockActionsFor(editor, block, actions) : [];
|
|
48
|
+
const items = block && editor.isEditable ? blockActionsFor(editor, block, actions, surface) : [];
|
|
43
49
|
const [place, setPlace] = useState(null);
|
|
44
50
|
const resizeObserver = useRef(null);
|
|
45
51
|
const watched = useRef([]);
|
|
@@ -94,28 +100,34 @@ function BlockActionsOverlay({ editor, target, actions, host, onHover }) {
|
|
|
94
100
|
const menuOpen = useRef(false);
|
|
95
101
|
const onMenuOpenChange = (open) => {
|
|
96
102
|
menuOpen.current = open;
|
|
97
|
-
onHover(open || !!root.current?.matches(":hover"));
|
|
103
|
+
if (!persistent) onHover?.(open || !!root.current?.matches(":hover"));
|
|
98
104
|
};
|
|
99
105
|
const wasVisible = useRef(false);
|
|
100
106
|
useEffect(() => {
|
|
101
107
|
if (wasVisible.current && !visible) {
|
|
102
108
|
menuOpen.current = false;
|
|
103
|
-
onHover(false);
|
|
109
|
+
if (!persistent) onHover?.(false);
|
|
104
110
|
}
|
|
105
111
|
wasVisible.current = visible;
|
|
106
|
-
}, [
|
|
112
|
+
}, [
|
|
113
|
+
visible,
|
|
114
|
+
persistent,
|
|
115
|
+
onHover
|
|
116
|
+
]);
|
|
107
117
|
if (!visible || !place) return null;
|
|
108
118
|
return /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsx("div", {
|
|
109
119
|
ref: root,
|
|
110
|
-
className: "block-actions absolute z-10 flex gap-0.5 rounded-md bg-background p-0.5",
|
|
120
|
+
className: cn("block-actions absolute z-10 flex gap-0.5 rounded-md bg-background p-0.5", persistent && "block-actions-toolbar"),
|
|
111
121
|
style: {
|
|
112
122
|
top: `${place.top}px`,
|
|
113
123
|
right: `${place.right}px`,
|
|
114
124
|
transform: place.docked ? "translateY(-50%)" : void 0
|
|
115
125
|
},
|
|
116
|
-
onMouseEnter: () =>
|
|
126
|
+
onMouseEnter: () => {
|
|
127
|
+
if (!persistent) onHover?.(true);
|
|
128
|
+
},
|
|
117
129
|
onMouseLeave: () => {
|
|
118
|
-
if (!menuOpen.current) onHover(false);
|
|
130
|
+
if (!persistent && !menuOpen.current) onHover?.(false);
|
|
119
131
|
},
|
|
120
132
|
children: items.map((entry) => {
|
|
121
133
|
const button = /* @__PURE__ */ jsx(Button, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BlockActionsOverlay.js","names":[],"sources":["../../../../src/features/block-ui/react/BlockActionsOverlay.tsx"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport { useEffect, useLayoutEffect, useReducer, useRef, useState } from 'react'\nimport type { BlockAction, BlockTarget, MenuItem } from '../../../core'\nimport { Icon } from '../../../react/Icon'\nimport { Button } from '../../../react/ui/button'\nimport { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from '../../../react/ui/dropdown-menu'\nimport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../../../react/ui/tooltip'\nimport { blockActionsFor, blockCornerOf, effectiveTarget } from '../block-actions'\n\n/** Gap between the bar and the editor's right edge. */\nconst EDGE_GUTTER = 8\n/** Gap between the bar and the block's own corner elements it docks next to. */\nconst CORNER_GAP = 4\n\n/**\n * The hover actions of a block, right-aligned at the editor's text column (the drag handle sits on the block's left and\n * targets the same block). Positioned absolutely inside `host`, a `relative` element around the editor; the composite\n * chrome (title, description, tags) addresses the composite (`effectiveTarget`). The bar is hidden while the editor is\n * read-only. `menu` actions open a dropdown of their items instead of running on click.\n *\n * A block that shows icons of its own in its top-right corner marks them with `data-block-corner` (`BLOCK_CORNER_ATTR`):\n * the bar then docks immediately left of them, centred on their row, instead of covering them. Without such elements\n * it sits at the editor's right edge, aligned to the block's top.\n */\nexport function BlockActionsOverlay({ editor, target, actions, host, onHover }: {\n editor: Editor\n target: BlockTarget | null\n actions: readonly BlockAction[]\n host: HTMLElement | null\n onHover: (hovered: boolean) => void\n}) {\n // The document and `isEditable` change with transactions (`setEditable` dispatches one too): render on each.\n const [, rerender] = useReducer((count: number) => count + 1, 0)\n useEffect(() => {\n editor.on('transaction', rerender)\n return () => { editor.off('transaction', rerender) }\n }, [editor])\n\n // The target in the current document; null once its position holds something else (deleted, replaced).\n const block = ((): BlockTarget | null => {\n if (!target || editor.isDestroyed) return null\n const { doc } = editor.state\n if (target.pos < 0 || target.pos >= doc.content.size) return null\n const node = doc.nodeAt(target.pos)\n if (!node || node.type !== target.node.type) return null\n return effectiveTarget(editor, { node, pos: target.pos })\n })()\n const items = block && editor.isEditable ? blockActionsFor(editor, block, actions) : []\n\n // `docked`: the bar sits next to the block's corner elements, and `top` is then their vertical centre.\n const [place, setPlace] = useState<{ top: number, right: number, docked: boolean } | null>(null)\n const resizeObserver = useRef<ResizeObserver | null>(null)\n const watched = useRef<HTMLElement[]>([])\n // The corner elements can change size (a badge appears) without the document changing: watch the current ones.\n const watchCorner = (elements: HTMLElement[]) => {\n const current = watched.current\n if (elements.length === current.length && elements.every((element, index) => element === current[index])) return\n for (const element of current) resizeObserver.current?.unobserve(element)\n watched.current = elements\n for (const element of elements) resizeObserver.current?.observe(element)\n }\n const measure = () => {\n const dom = block && host && !editor.isDestroyed ? editor.view.nodeDOM(block.pos) : null\n if (!(dom instanceof HTMLElement) || !host) {\n watchCorner([])\n setPlace(null)\n return\n }\n const hostRect = host.getBoundingClientRect()\n const corner = blockCornerOf(dom)\n watchCorner(corner?.elements ?? [])\n const next = corner\n ? { top: corner.centerY - hostRect.top, right: hostRect.right - corner.left + CORNER_GAP, docked: true }\n : {\n top: dom.getBoundingClientRect().top - hostRect.top + 2,\n right: hostRect.right - editor.view.dom.getBoundingClientRect().right + EDGE_GUTTER,\n docked: false,\n }\n setPlace(previous => (previous && previous.top === next.top && previous.right === next.right && previous.docked === next.docked ? previous : next))\n }\n // After every render: the block, the document or the layout may have moved it. Equal positions bail out of the state update.\n useLayoutEffect(measure)\n\n const visible = editor.isEditable && items.length > 0 && place !== null\n\n // The bar follows the editor's right edge when the column resizes without the window doing so.\n const latestMeasure = useRef(measure)\n latestMeasure.current = measure\n useEffect(() => {\n const run = () => latestMeasure.current()\n window.addEventListener('resize', run)\n window.addEventListener('scroll', run, true)\n const observer = new ResizeObserver(run)\n resizeObserver.current = observer\n observer.observe(editor.view.dom)\n for (const element of watched.current) observer.observe(element)\n return () => {\n window.removeEventListener('resize', run)\n window.removeEventListener('scroll', run, true)\n observer.disconnect()\n resizeObserver.current = null\n }\n }, [editor])\n\n // An open action menu is rendered outside the overlay (portal): the overlay counts as hovered until the menu closes.\n const root = useRef<HTMLDivElement>(null)\n const menuOpen = useRef(false)\n const onMenuOpenChange = (open: boolean) => {\n menuOpen.current = open\n onHover(open || !!root.current?.matches(':hover'))\n }\n\n // A vanishing overlay sends no mouseleave: report it, so the host does not keep waiting for one.\n const wasVisible = useRef(false)\n useEffect(() => {\n if (wasVisible.current && !visible) {\n menuOpen.current = false\n onHover(false)\n }\n wasVisible.current = visible\n }, [visible, onHover])\n\n if (!visible || !place) return null\n return (\n <TooltipProvider>\n <div\n ref={root}\n className=\"block-actions absolute z-10 flex gap-0.5 rounded-md bg-background p-0.5\"\n style={{ top: `${place.top}px`, right: `${place.right}px`, transform: place.docked ? 'translateY(-50%)' : undefined }}\n onMouseEnter={() => onHover(true)}\n onMouseLeave={() => { if (!menuOpen.current) onHover(false) }}\n >\n {items.map((entry) => {\n const button = (\n <Button\n variant=\"ghost\"\n size=\"icon-xs\"\n aria-label={entry.action.label}\n onMouseDown={event => event.preventDefault()}\n onClick={entry.menu ? undefined : () => entry.action.run?.(entry.ctx)}\n >\n <Icon name={entry.action.icon} />\n </Button>\n )\n if (!entry.menu) {\n return (\n <Tooltip key={entry.action.id}>\n <TooltipTrigger asChild>{button}</TooltipTrigger>\n <TooltipContent>{entry.action.label}</TooltipContent>\n </Tooltip>\n )\n }\n return (\n <DropdownMenu key={entry.action.id} onOpenChange={onMenuOpenChange}>\n <Tooltip>\n <TooltipTrigger asChild>\n <DropdownMenuTrigger asChild>{button}</DropdownMenuTrigger>\n </TooltipTrigger>\n <TooltipContent>{entry.action.label}</TooltipContent>\n </Tooltip>\n <DropdownMenuContent align=\"end\">\n {entry.menu.map((item, index) => <MenuRow key={index} item={item} />)}\n </DropdownMenuContent>\n </DropdownMenu>\n )\n })}\n </div>\n </TooltipProvider>\n )\n}\n\nfunction MenuRow({ item }: { item: MenuItem }) {\n if (item.type === 'label') return <DropdownMenuLabel>{item.label}</DropdownMenuLabel>\n if (item.type === 'separator') return <DropdownMenuSeparator />\n return (\n <DropdownMenuItem disabled={item.disabled} onSelect={() => item.onSelect?.()}>\n {item.icon && <Icon name={item.icon} />}\n <span>{item.label}</span>\n {item.description && <span className=\"ml-auto text-xs text-muted-foreground\">{item.description}</span>}\n </DropdownMenuItem>\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"BlockActionsOverlay.js","names":[],"sources":["../../../../src/features/block-ui/react/BlockActionsOverlay.tsx"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport { useEffect, useLayoutEffect, useReducer, useRef, useState } from 'react'\nimport type { BlockAction, BlockActionSurface, BlockTarget, MenuItem } from '../../../core'\nimport { Icon } from '../../../react/Icon'\nimport { Button } from '../../../react/ui/button'\nimport { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from '../../../react/ui/dropdown-menu'\nimport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../../../react/ui/tooltip'\nimport { cn } from '../../../react/ui/utils'\nimport { blockActionsFor, blockCornerOf, effectiveTarget } from '../block-actions'\n\n/** Gap between the bar and the editor's right edge. */\nconst EDGE_GUTTER = 8\n/** Gap between the bar and the block's own corner elements it docks next to. */\nconst CORNER_GAP = 4\n\n/**\n * The actions of a block on one surface, right-aligned at the editor's text column (the drag handle sits on the block's\n * left and targets the same block). Positioned absolutely inside `host`, a `relative` element around the editor; the\n * composite chrome (title, description, tags) addresses the composite (`effectiveTarget`). The bar is hidden while the\n * editor is read-only. `menu` actions open a dropdown of their items instead of running on click.\n *\n * `surface` selects the actions by their `placement`. The `hover` surface follows the pointer (`target` is the hovered\n * block, `onHover` keeps it alive while the pointer is on the bar); the `toolbar` surface is `persistent`: `target` is\n * the block holding the selection, the bar shows as long as it is set, reports no hover and carries the class\n * `block-actions-toolbar` for host styling.\n *\n * A block that shows icons of its own in its top-right corner marks them with `data-block-corner` (`BLOCK_CORNER_ATTR`):\n * the bar then docks immediately left of them, centred on their row, instead of covering them. Without such elements\n * it sits at the editor's right edge, aligned to the block's top.\n */\nexport function BlockActionsOverlay({ editor, target, actions, host, surface, persistent, onHover }: {\n editor: Editor\n target: BlockTarget | null\n actions: readonly BlockAction[]\n host: HTMLElement | null\n surface: BlockActionSurface\n persistent?: boolean\n onHover?: (hovered: boolean) => void\n}) {\n // The document and `isEditable` change with transactions (`setEditable` dispatches one too): render on each.\n const [, rerender] = useReducer((count: number) => count + 1, 0)\n useEffect(() => {\n editor.on('transaction', rerender)\n return () => { editor.off('transaction', rerender) }\n }, [editor])\n\n // The target in the current document; null once its position holds something else (deleted, replaced).\n const block = ((): BlockTarget | null => {\n if (!target || editor.isDestroyed) return null\n const { doc } = editor.state\n if (target.pos < 0 || target.pos >= doc.content.size) return null\n const node = doc.nodeAt(target.pos)\n if (!node || node.type !== target.node.type) return null\n return effectiveTarget(editor, { node, pos: target.pos })\n })()\n const items = block && editor.isEditable ? blockActionsFor(editor, block, actions, surface) : []\n\n // `docked`: the bar sits next to the block's corner elements, and `top` is then their vertical centre.\n const [place, setPlace] = useState<{ top: number, right: number, docked: boolean } | null>(null)\n const resizeObserver = useRef<ResizeObserver | null>(null)\n const watched = useRef<HTMLElement[]>([])\n // The corner elements can change size (a badge appears) without the document changing: watch the current ones.\n const watchCorner = (elements: HTMLElement[]) => {\n const current = watched.current\n if (elements.length === current.length && elements.every((element, index) => element === current[index])) return\n for (const element of current) resizeObserver.current?.unobserve(element)\n watched.current = elements\n for (const element of elements) resizeObserver.current?.observe(element)\n }\n const measure = () => {\n const dom = block && host && !editor.isDestroyed ? editor.view.nodeDOM(block.pos) : null\n if (!(dom instanceof HTMLElement) || !host) {\n watchCorner([])\n setPlace(null)\n return\n }\n const hostRect = host.getBoundingClientRect()\n const corner = blockCornerOf(dom)\n watchCorner(corner?.elements ?? [])\n const next = corner\n ? { top: corner.centerY - hostRect.top, right: hostRect.right - corner.left + CORNER_GAP, docked: true }\n : {\n top: dom.getBoundingClientRect().top - hostRect.top + 2,\n right: hostRect.right - editor.view.dom.getBoundingClientRect().right + EDGE_GUTTER,\n docked: false,\n }\n setPlace(previous => (previous && previous.top === next.top && previous.right === next.right && previous.docked === next.docked ? previous : next))\n }\n // After every render: the block, the document or the layout may have moved it. Equal positions bail out of the state update.\n useLayoutEffect(measure)\n\n const visible = editor.isEditable && items.length > 0 && place !== null\n\n // The bar follows the editor's right edge when the column resizes without the window doing so.\n const latestMeasure = useRef(measure)\n latestMeasure.current = measure\n useEffect(() => {\n const run = () => latestMeasure.current()\n window.addEventListener('resize', run)\n window.addEventListener('scroll', run, true)\n const observer = new ResizeObserver(run)\n resizeObserver.current = observer\n observer.observe(editor.view.dom)\n for (const element of watched.current) observer.observe(element)\n return () => {\n window.removeEventListener('resize', run)\n window.removeEventListener('scroll', run, true)\n observer.disconnect()\n resizeObserver.current = null\n }\n }, [editor])\n\n // An open action menu is rendered outside the overlay (portal): the overlay counts as hovered until the menu closes.\n const root = useRef<HTMLDivElement>(null)\n const menuOpen = useRef(false)\n const onMenuOpenChange = (open: boolean) => {\n menuOpen.current = open\n if (!persistent) onHover?.(open || !!root.current?.matches(':hover'))\n }\n\n // A vanishing overlay sends no mouseleave: report it, so the host does not keep waiting for one.\n const wasVisible = useRef(false)\n useEffect(() => {\n if (wasVisible.current && !visible) {\n menuOpen.current = false\n if (!persistent) onHover?.(false)\n }\n wasVisible.current = visible\n }, [visible, persistent, onHover])\n\n if (!visible || !place) return null\n return (\n <TooltipProvider>\n <div\n ref={root}\n className={cn('block-actions absolute z-10 flex gap-0.5 rounded-md bg-background p-0.5', persistent && 'block-actions-toolbar')}\n style={{ top: `${place.top}px`, right: `${place.right}px`, transform: place.docked ? 'translateY(-50%)' : undefined }}\n onMouseEnter={() => { if (!persistent) onHover?.(true) }}\n onMouseLeave={() => { if (!persistent && !menuOpen.current) onHover?.(false) }}\n >\n {items.map((entry) => {\n const button = (\n <Button\n variant=\"ghost\"\n size=\"icon-xs\"\n aria-label={entry.action.label}\n onMouseDown={event => event.preventDefault()}\n onClick={entry.menu ? undefined : () => entry.action.run?.(entry.ctx)}\n >\n <Icon name={entry.action.icon} />\n </Button>\n )\n if (!entry.menu) {\n return (\n <Tooltip key={entry.action.id}>\n <TooltipTrigger asChild>{button}</TooltipTrigger>\n <TooltipContent>{entry.action.label}</TooltipContent>\n </Tooltip>\n )\n }\n return (\n <DropdownMenu key={entry.action.id} onOpenChange={onMenuOpenChange}>\n <Tooltip>\n <TooltipTrigger asChild>\n <DropdownMenuTrigger asChild>{button}</DropdownMenuTrigger>\n </TooltipTrigger>\n <TooltipContent>{entry.action.label}</TooltipContent>\n </Tooltip>\n <DropdownMenuContent align=\"end\">\n {entry.menu.map((item, index) => <MenuRow key={index} item={item} />)}\n </DropdownMenuContent>\n </DropdownMenu>\n )\n })}\n </div>\n </TooltipProvider>\n )\n}\n\nfunction MenuRow({ item }: { item: MenuItem }) {\n if (item.type === 'label') return <DropdownMenuLabel>{item.label}</DropdownMenuLabel>\n if (item.type === 'separator') return <DropdownMenuSeparator />\n return (\n <DropdownMenuItem disabled={item.disabled} onSelect={() => item.onSelect?.()}>\n {item.icon && <Icon name={item.icon} />}\n <span>{item.label}</span>\n {item.description && <span className=\"ml-auto text-xs text-muted-foreground\">{item.description}</span>}\n </DropdownMenuItem>\n )\n}\n"],"mappings":"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"}
|
|
@@ -2,10 +2,12 @@ import { Editor } from "@tiptap/core";
|
|
|
2
2
|
//#region src/features/block-ui/react/BlockUi.d.ts
|
|
3
3
|
/**
|
|
4
4
|
* Block UI of an editor: the drag handle on the left of the hovered block (hidden while the editor is read-only) and the
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* block actions (`BlockActionsOverlay`) at the editor's right edge. The actions are those the surface registered for
|
|
6
|
+
* `editor` (features' block actions, then the host's), shown per `BlockUiOptions.actions`: on a hover bar for the block
|
|
7
|
+
* the drag handle targets, on a persistent toolbar for the block holding the selection, both, or neither. When both
|
|
8
|
+
* would show a bar on the same block, only the toolbar does. A block with a kind icon (a composite) gets no handle: its
|
|
9
|
+
* icon drags it. With `blocks.dragHandle: 'always'` the hover handle is replaced by permanent handles on every eligible
|
|
10
|
+
* block (`BlockHandles`). Mounted by the block-ui feature beside the editor.
|
|
9
11
|
*/
|
|
10
12
|
export declare function BlockUi({ editor }: {
|
|
11
13
|
editor: Editor;
|
|
@@ -4,19 +4,22 @@ import { cn } from "../../../react/ui/utils.js";
|
|
|
4
4
|
import { Icon } from "../../../react/Icon.js";
|
|
5
5
|
import { useEditorSettings } from "../../../react/settings.js";
|
|
6
6
|
import { firstLineCenterShift, gutterShiftOf, handleLeftOf, hasKindIcon } from "../handle-geometry.js";
|
|
7
|
+
import { actionSurfacesOf, sameBlock, selectedBlockOf } from "../block-actions.js";
|
|
7
8
|
import { BlockActionsOverlay } from "./BlockActionsOverlay.js";
|
|
8
9
|
import { BlockHandles } from "./BlockHandles.js";
|
|
9
|
-
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
10
|
+
import { useCallback, useEffect, useMemo, useReducer, useRef, useState } from "react";
|
|
10
11
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
11
12
|
import { DragHandle } from "@tiptap/extension-drag-handle-react";
|
|
12
13
|
import { createPortal } from "react-dom";
|
|
13
14
|
//#region src/features/block-ui/react/BlockUi.tsx
|
|
14
15
|
/**
|
|
15
16
|
* Block UI of an editor: the drag handle on the left of the hovered block (hidden while the editor is read-only) and the
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
17
|
+
* block actions (`BlockActionsOverlay`) at the editor's right edge. The actions are those the surface registered for
|
|
18
|
+
* `editor` (features' block actions, then the host's), shown per `BlockUiOptions.actions`: on a hover bar for the block
|
|
19
|
+
* the drag handle targets, on a persistent toolbar for the block holding the selection, both, or neither. When both
|
|
20
|
+
* would show a bar on the same block, only the toolbar does. A block with a kind icon (a composite) gets no handle: its
|
|
21
|
+
* icon drags it. With `blocks.dragHandle: 'always'` the hover handle is replaced by permanent handles on every eligible
|
|
22
|
+
* block (`BlockHandles`). Mounted by the block-ui feature beside the editor.
|
|
20
23
|
*/
|
|
21
24
|
function BlockUi({ editor }) {
|
|
22
25
|
const [host, setHost] = useState(null);
|
|
@@ -24,6 +27,19 @@ function BlockUi({ editor }) {
|
|
|
24
27
|
const [targetHasKindIcon, setTargetHasKindIcon] = useState(false);
|
|
25
28
|
const { settings } = useEditorSettings();
|
|
26
29
|
const actions = editorEntryOf(editor)?.blockActions ?? [];
|
|
30
|
+
const surfaces = actionSurfacesOf(editor);
|
|
31
|
+
const [, rerender] = useReducer((count) => count + 1, 0);
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
const onTransaction = () => {
|
|
34
|
+
if (actionSurfacesOf(editor).toolbar) rerender();
|
|
35
|
+
};
|
|
36
|
+
editor.on("transaction", onTransaction);
|
|
37
|
+
return () => {
|
|
38
|
+
editor.off("transaction", onTransaction);
|
|
39
|
+
};
|
|
40
|
+
}, [editor]);
|
|
41
|
+
const selectedTarget = surfaces.toolbar ? selectedBlockOf(editor) : null;
|
|
42
|
+
const hoverBarTarget = hoverTarget && selectedTarget && sameBlock(editor, hoverTarget, selectedTarget) ? null : hoverTarget;
|
|
27
43
|
const track = useRef({
|
|
28
44
|
overlayHovered: false,
|
|
29
45
|
clearTimer: void 0,
|
|
@@ -138,16 +154,28 @@ function BlockUi({ editor }) {
|
|
|
138
154
|
]
|
|
139
155
|
};
|
|
140
156
|
}, [track]);
|
|
141
|
-
return /* @__PURE__ */ jsxs(Fragment$1, { children: [host && createPortal(/* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
157
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [host && createPortal(/* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
158
|
+
surfaces.hover && /* @__PURE__ */ jsx(BlockActionsOverlay, {
|
|
159
|
+
editor,
|
|
160
|
+
target: hoverBarTarget,
|
|
161
|
+
host,
|
|
162
|
+
actions,
|
|
163
|
+
surface: "hover",
|
|
164
|
+
onHover: onOverlayHover
|
|
165
|
+
}),
|
|
166
|
+
surfaces.toolbar && /* @__PURE__ */ jsx(BlockActionsOverlay, {
|
|
167
|
+
editor,
|
|
168
|
+
target: selectedTarget,
|
|
169
|
+
host,
|
|
170
|
+
actions,
|
|
171
|
+
surface: "toolbar",
|
|
172
|
+
persistent: true
|
|
173
|
+
}),
|
|
174
|
+
alwaysHandles && /* @__PURE__ */ jsx(BlockHandles, {
|
|
175
|
+
editor,
|
|
176
|
+
host
|
|
177
|
+
})
|
|
178
|
+
] }), host), /* @__PURE__ */ jsx(DragHandle, {
|
|
151
179
|
editor,
|
|
152
180
|
onElementDragEnd: resetDragHandle,
|
|
153
181
|
onNodeChange: trackDragTarget,
|