@atlaskit/editor-plugin-synced-block 9.0.1 → 9.0.3
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 +13 -0
- package/dist/cjs/nodeviews/syncedBlock.js +126 -1
- package/dist/cjs/ui/SyncBlockRendererWrapper.js +26 -3
- package/dist/es2019/nodeviews/syncedBlock.js +126 -1
- package/dist/es2019/ui/SyncBlockRendererWrapper.js +24 -2
- package/dist/esm/nodeviews/syncedBlock.js +128 -2
- package/dist/esm/ui/SyncBlockRendererWrapper.js +24 -2
- package/dist/types/nodeviews/syncedBlock.d.ts +28 -0
- package/dist/types-ts4.5/nodeviews/syncedBlock.d.ts +28 -0
- package/package.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# @atlaskit/editor-plugin-synced-block
|
|
2
2
|
|
|
3
|
+
## 9.0.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies
|
|
8
|
+
|
|
9
|
+
## 9.0.2
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- [`ed89ab85318b9`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/ed89ab85318b9) -
|
|
14
|
+
[ux] Allow text selection and copying within reference synced blocks in the editor
|
|
15
|
+
|
|
3
16
|
## 9.0.1
|
|
4
17
|
|
|
5
18
|
### Patch Changes
|
|
@@ -17,6 +17,8 @@ var _analytics = require("@atlaskit/editor-common/analytics");
|
|
|
17
17
|
var _errorBoundary = require("@atlaskit/editor-common/error-boundary");
|
|
18
18
|
var _reactNodeView = _interopRequireDefault(require("@atlaskit/editor-common/react-node-view"));
|
|
19
19
|
var _syncBlock = require("@atlaskit/editor-common/sync-block");
|
|
20
|
+
var _state = require("@atlaskit/editor-prosemirror/state");
|
|
21
|
+
var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
|
|
20
22
|
var _expValEqualsNoExposure = require("@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure");
|
|
21
23
|
var _editorCommands = require("../editor-commands");
|
|
22
24
|
var _SyncBlockRendererWrapper = require("../ui/SyncBlockRendererWrapper");
|
|
@@ -24,6 +26,11 @@ var _SyncBlockSSRReactContextsProvider = require("../ui/SyncBlockSSRReactContext
|
|
|
24
26
|
function _callSuper(t, o, e) { return o = (0, _getPrototypeOf2.default)(o), (0, _possibleConstructorReturn2.default)(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], (0, _getPrototypeOf2.default)(t).constructor) : o.apply(t, e)); }
|
|
25
27
|
function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); }
|
|
26
28
|
function _superPropGet(t, o, e, r) { var p = (0, _get2.default)((0, _getPrototypeOf2.default)(1 & r ? t.prototype : t), o, e); return 2 & r && "function" == typeof p ? function (t) { return p.apply(e, t); } : p; }
|
|
29
|
+
// Event types that should be intercepted (returned as handled) when they
|
|
30
|
+
// originate inside the sync block content area, so ProseMirror does not
|
|
31
|
+
// convert them into node-level selections or drag operations and the browser
|
|
32
|
+
// can perform native text selection/cut instead.
|
|
33
|
+
var STOPPED_EVENT_TYPES = ['mousedown', 'mousemove', 'mouseup', 'click', 'dblclick', 'selectstart'];
|
|
27
34
|
var SyncBlock = exports.SyncBlock = /*#__PURE__*/function (_ReactNodeView) {
|
|
28
35
|
function SyncBlock(props) {
|
|
29
36
|
var _this;
|
|
@@ -54,12 +61,124 @@ var SyncBlock = exports.SyncBlock = /*#__PURE__*/function (_ReactNodeView) {
|
|
|
54
61
|
(0, _inherits2.default)(SyncBlock, _ReactNodeView);
|
|
55
62
|
return (0, _createClass2.default)(SyncBlock, [{
|
|
56
63
|
key: "createDomRef",
|
|
57
|
-
value:
|
|
64
|
+
value:
|
|
65
|
+
// Stored reference so the listener can be removed in destroy() to
|
|
66
|
+
// avoid a memory leak on every nodeview destruction.
|
|
67
|
+
|
|
68
|
+
function createDomRef() {
|
|
58
69
|
// eslint-disable-next-line @atlaskit/platform/no-direct-document-usage -- NodeView DOM must be created against active runtime document
|
|
59
70
|
var domRef = document.createElement('div');
|
|
60
71
|
domRef.classList.add(_syncBlock.SyncBlockSharedCssClassName.prefix);
|
|
72
|
+
if ((0, _platformFeatureFlags.fg)('platform_synced_block_patch_14')) {
|
|
73
|
+
// Prevent native browser drag on the contentEditable="false" wrapper.
|
|
74
|
+
// Without this, clicking in empty space (outside the contentEditable
|
|
75
|
+
// renderer but inside the domRef) initiates a native element drag.
|
|
76
|
+
domRef.draggable = false;
|
|
77
|
+
this.dragStartHandler = function (e) {
|
|
78
|
+
e.preventDefault();
|
|
79
|
+
};
|
|
80
|
+
// eslint-disable-next-line @atlaskit/design-system/no-direct-use-of-web-platform-drag-and-drop, @repo/internal/dom-events/no-unsafe-event-listeners
|
|
81
|
+
domRef.addEventListener('dragstart', this.dragStartHandler);
|
|
82
|
+
}
|
|
61
83
|
return domRef;
|
|
62
84
|
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Allow mouse and selection events inside the renderer content to pass
|
|
88
|
+
* through to the browser so that users can select and copy text within a
|
|
89
|
+
* reference sync block.
|
|
90
|
+
*
|
|
91
|
+
* Events that originate inside the sync block content area (but not the label)
|
|
92
|
+
* are stopped so ProseMirror does not intercept them for node-level selection.
|
|
93
|
+
* This includes the full click-drag cycle (mousedown, mousemove, mouseup),
|
|
94
|
+
* click, dblclick, selectstart and cut. The `cut` event is stopped because
|
|
95
|
+
* mousedown explicitly sets a NodeSelection on the sync block — without
|
|
96
|
+
* stopping `cut`, a subsequent Ctrl+X would cause ProseMirror to delete the
|
|
97
|
+
* entire sync block node instead of cutting the user's text selection.
|
|
98
|
+
*
|
|
99
|
+
* Copy events are conditionally stopped: when the user has an active native
|
|
100
|
+
* text selection inside the renderer, `copy` is stopped so the browser handles
|
|
101
|
+
* it natively (copying the selected text). When there is no text selection
|
|
102
|
+
* (just a PM NodeSelection), `copy` is NOT stopped so ProseMirror's copy
|
|
103
|
+
* handler runs and sets the "sync-block-copied" flag for the reference paste flow.
|
|
104
|
+
*
|
|
105
|
+
* Events on the SyncBlockLabel are left for ProseMirror to handle, preserving
|
|
106
|
+
* label click interactions and the floating toolbar.
|
|
107
|
+
*
|
|
108
|
+
* The renderer wrapper sets contentEditable="true" to create a re-editable
|
|
109
|
+
* island inside ProseMirror's contentEditable="false" nodeview, enabling
|
|
110
|
+
* native text selection and preventing browser drag behaviour.
|
|
111
|
+
*/
|
|
112
|
+
}, {
|
|
113
|
+
key: "stopEvent",
|
|
114
|
+
value: function stopEvent(event) {
|
|
115
|
+
if (!(0, _platformFeatureFlags.fg)('platform_synced_block_patch_14')) {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
var target = event.target;
|
|
119
|
+
if (!(target instanceof Element)) {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Stop events inside the sync block content area, but not on the
|
|
124
|
+
// SyncBlockLabel (to preserve label click interactions).
|
|
125
|
+
if (target.closest(".".concat(_syncBlock.SyncBlockSharedCssClassName.prefix)) && !target.closest(".".concat(_syncBlock.SyncBlockLabelSharedCssClassName.labelClassName))) {
|
|
126
|
+
var eventType = event.type;
|
|
127
|
+
|
|
128
|
+
// Stop `copy` only when there is an active native text selection
|
|
129
|
+
// inside the renderer. This lets the browser handle text copy
|
|
130
|
+
// natively. When there is no text selection (just a PM
|
|
131
|
+
// NodeSelection), we let PM handle the copy event so the
|
|
132
|
+
// "sync-block-copied" flag is set for the reference paste flow.
|
|
133
|
+
if (eventType === 'copy') {
|
|
134
|
+
var selection = window.getSelection();
|
|
135
|
+
return !!(selection && selection.toString().length > 0);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// For `cut`: when text is selected inside the renderer, stop the
|
|
139
|
+
// event and prevent default to avoid the browser removing text from
|
|
140
|
+
// the read-only content. When no text is selected (NodeSelection),
|
|
141
|
+
// let PM handle it so cut deletes the sync block as expected.
|
|
142
|
+
if (eventType === 'cut') {
|
|
143
|
+
var _selection = window.getSelection();
|
|
144
|
+
if (_selection && _selection.toString().length > 0) {
|
|
145
|
+
event.preventDefault();
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Stop keyboard events that would cause PM to replace the
|
|
152
|
+
// NodeSelection with typed text (deleting the sync block).
|
|
153
|
+
// Allow modifier-key combos (Cmd+C, Cmd+A, etc.) through.
|
|
154
|
+
// Allow Delete/Backspace through so PM's delete handler can
|
|
155
|
+
// process them (e.g. show offline error flag).
|
|
156
|
+
if ((eventType === 'keydown' || eventType === 'keypress') && event instanceof KeyboardEvent && !event.metaKey && !event.ctrlKey && event.key !== 'Delete' && event.key !== 'Backspace') {
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
if (STOPPED_EVENT_TYPES.includes(eventType)) {
|
|
160
|
+
// Ensure the syncBlock has a NodeSelection so the floating
|
|
161
|
+
// toolbar is visible while the user interacts with the renderer.
|
|
162
|
+
// stopEvent prevents PM from processing the mousedown, so we
|
|
163
|
+
// need to explicitly set the selection ourselves.
|
|
164
|
+
if (eventType === 'mousedown' && !(this.view.state.selection instanceof _state.NodeSelection && this.view.state.selection.node === this.node)) {
|
|
165
|
+
if (typeof this.getPos === 'function') {
|
|
166
|
+
var pos = this.getPos();
|
|
167
|
+
if (typeof pos === 'number') {
|
|
168
|
+
try {
|
|
169
|
+
var tr = this.view.state.tr;
|
|
170
|
+
this.view.dispatch(tr.setSelection(_state.NodeSelection.create(tr.doc, pos)));
|
|
171
|
+
} catch (_unused) {
|
|
172
|
+
// pos no longer valid — leave selection unchanged
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
63
182
|
}, {
|
|
64
183
|
key: "validUpdate",
|
|
65
184
|
value: function validUpdate(currentNode, newNode) {
|
|
@@ -135,6 +254,12 @@ var SyncBlock = exports.SyncBlock = /*#__PURE__*/function (_ReactNodeView) {
|
|
|
135
254
|
value: function destroy() {
|
|
136
255
|
var _this$unsubscribe;
|
|
137
256
|
(_this$unsubscribe = this.unsubscribe) === null || _this$unsubscribe === void 0 || _this$unsubscribe.call(this);
|
|
257
|
+
if (this.dragStartHandler) {
|
|
258
|
+
var _this$dom;
|
|
259
|
+
// eslint-disable-next-line @atlaskit/design-system/no-direct-use-of-web-platform-drag-and-drop, @repo/internal/dom-events/no-unsafe-event-listeners
|
|
260
|
+
(_this$dom = this.dom) === null || _this$dom === void 0 || _this$dom.removeEventListener('dragstart', this.dragStartHandler);
|
|
261
|
+
this.dragStartHandler = undefined;
|
|
262
|
+
}
|
|
138
263
|
_superPropGet(SyncBlock, "destroy", this, 3)([]);
|
|
139
264
|
}
|
|
140
265
|
}]);
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var _typeof = require("@babel/runtime/helpers/typeof");
|
|
4
4
|
Object.defineProperty(exports, "__esModule", {
|
|
5
5
|
value: true
|
|
6
6
|
});
|
|
7
7
|
exports.SyncBlockRendererWrapper = void 0;
|
|
8
|
-
var _react =
|
|
8
|
+
var _react = _interopRequireWildcard(require("react"));
|
|
9
9
|
var _syncBlock = require("@atlaskit/editor-common/sync-block");
|
|
10
10
|
var _editorSyncedBlockProvider = require("@atlaskit/editor-synced-block-provider");
|
|
11
|
+
var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
|
|
11
12
|
var _SyncBlockLabel = require("./SyncBlockLabel");
|
|
13
|
+
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
|
|
12
14
|
var SyncBlockRendererWrapperDataId = 'sync-block-plugin-renderer-wrapper';
|
|
13
15
|
var SyncBlockRendererWrapperComponent = function SyncBlockRendererWrapperComponent(_ref) {
|
|
14
16
|
var _api$analytics, _syncBlockFetchResult, _syncBlockFetchResult2, _syncBlockFetchResult3;
|
|
@@ -23,11 +25,32 @@ var SyncBlockRendererWrapperComponent = function SyncBlockRendererWrapperCompone
|
|
|
23
25
|
var contentUpdatedAt = syncBlockFetchResult === null || syncBlockFetchResult === void 0 || (_syncBlockFetchResult = syncBlockFetchResult.syncBlockInstance) === null || _syncBlockFetchResult === void 0 || (_syncBlockFetchResult = _syncBlockFetchResult.data) === null || _syncBlockFetchResult === void 0 ? void 0 : _syncBlockFetchResult.contentUpdatedAt;
|
|
24
26
|
var isUnpublishedBlock = ((_syncBlockFetchResult2 = syncBlockFetchResult.syncBlockInstance) === null || _syncBlockFetchResult2 === void 0 || (_syncBlockFetchResult2 = _syncBlockFetchResult2.data) === null || _syncBlockFetchResult2 === void 0 ? void 0 : _syncBlockFetchResult2.status) === 'unpublished';
|
|
25
27
|
var isUnsyncedBlock = isUnpublishedBlock || (syncBlockFetchResult === null || syncBlockFetchResult === void 0 || (_syncBlockFetchResult3 = syncBlockFetchResult.syncBlockInstance) === null || _syncBlockFetchResult3 === void 0 || (_syncBlockFetchResult3 = _syncBlockFetchResult3.error) === null || _syncBlockFetchResult3 === void 0 ? void 0 : _syncBlockFetchResult3.type) === _editorSyncedBlockProvider.SyncBlockError.NotFound;
|
|
28
|
+
var isTextSelectionEnabled = (0, _platformFeatureFlags.fg)('platform_synced_block_patch_14');
|
|
29
|
+
|
|
30
|
+
// Prevent editing in the contentEditable renderer wrapper. We set
|
|
31
|
+
// contentEditable="true" to enable text selection (creating an editable
|
|
32
|
+
// island inside ProseMirror's contentEditable="false" nodeview wrapper),
|
|
33
|
+
// but users must not be able to type into or modify the renderer content.
|
|
34
|
+
var preventInput = (0, _react.useCallback)(function (e) {
|
|
35
|
+
e.preventDefault();
|
|
36
|
+
}, []);
|
|
26
37
|
return /*#__PURE__*/_react.default.createElement("div", null, /*#__PURE__*/_react.default.createElement("div", {
|
|
27
38
|
"data-testid": SyncBlockRendererWrapperDataId
|
|
28
39
|
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop
|
|
29
40
|
,
|
|
30
|
-
className: _syncBlock.SyncBlockSharedCssClassName.renderer
|
|
41
|
+
className: _syncBlock.SyncBlockSharedCssClassName.renderer,
|
|
42
|
+
contentEditable: isTextSelectionEnabled || undefined,
|
|
43
|
+
suppressContentEditableWarning: true
|
|
44
|
+
// Prevent the contentEditable div from being keyboard-focusable.
|
|
45
|
+
// It is only used to enable text selection, not as an input target.
|
|
46
|
+
// eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
|
|
47
|
+
,
|
|
48
|
+
tabIndex: isTextSelectionEnabled ? -1 : undefined,
|
|
49
|
+
onBeforeInput: isTextSelectionEnabled ? preventInput : undefined,
|
|
50
|
+
onPaste: isTextSelectionEnabled ? preventInput : undefined
|
|
51
|
+
// eslint-disable-next-line @atlaskit/design-system/no-direct-use-of-web-platform-drag-and-drop
|
|
52
|
+
,
|
|
53
|
+
onDrop: isTextSelectionEnabled ? preventInput : undefined
|
|
31
54
|
}, syncedBlockRenderer({
|
|
32
55
|
syncBlockFetchResult: syncBlockFetchResult,
|
|
33
56
|
api: api
|
|
@@ -3,11 +3,19 @@ import React from 'react';
|
|
|
3
3
|
import { ACTION_SUBJECT } from '@atlaskit/editor-common/analytics';
|
|
4
4
|
import { ErrorBoundary } from '@atlaskit/editor-common/error-boundary';
|
|
5
5
|
import ReactNodeView from '@atlaskit/editor-common/react-node-view';
|
|
6
|
-
import { SyncBlockSharedCssClassName, SyncBlockActionsProvider } from '@atlaskit/editor-common/sync-block';
|
|
6
|
+
import { SyncBlockSharedCssClassName, SyncBlockLabelSharedCssClassName, SyncBlockActionsProvider } from '@atlaskit/editor-common/sync-block';
|
|
7
|
+
import { NodeSelection } from '@atlaskit/editor-prosemirror/state';
|
|
8
|
+
import { fg } from '@atlaskit/platform-feature-flags';
|
|
7
9
|
import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
|
|
8
10
|
import { removeSyncedBlockAtPos } from '../editor-commands';
|
|
9
11
|
import { SyncBlockRendererWrapper } from '../ui/SyncBlockRendererWrapper';
|
|
10
12
|
import { SyncBlockSSRReactContextsProvider } from '../ui/SyncBlockSSRReactContextsProvider';
|
|
13
|
+
|
|
14
|
+
// Event types that should be intercepted (returned as handled) when they
|
|
15
|
+
// originate inside the sync block content area, so ProseMirror does not
|
|
16
|
+
// convert them into node-level selections or drag operations and the browser
|
|
17
|
+
// can perform native text selection/cut instead.
|
|
18
|
+
const STOPPED_EVENT_TYPES = ['mousedown', 'mousemove', 'mouseup', 'click', 'dblclick', 'selectstart'];
|
|
11
19
|
export class SyncBlock extends ReactNodeView {
|
|
12
20
|
constructor(props) {
|
|
13
21
|
super(props.node, props.view, props.getPos, props.portalProviderAPI, props.eventDispatcher, props);
|
|
@@ -32,12 +40,123 @@ export class SyncBlock extends ReactNodeView {
|
|
|
32
40
|
this.syncBlockStore = props.syncBlockStore;
|
|
33
41
|
this.intl = props.intl;
|
|
34
42
|
}
|
|
43
|
+
// Stored reference so the listener can be removed in destroy() to
|
|
44
|
+
// avoid a memory leak on every nodeview destruction.
|
|
45
|
+
|
|
35
46
|
createDomRef() {
|
|
36
47
|
// eslint-disable-next-line @atlaskit/platform/no-direct-document-usage -- NodeView DOM must be created against active runtime document
|
|
37
48
|
const domRef = document.createElement('div');
|
|
38
49
|
domRef.classList.add(SyncBlockSharedCssClassName.prefix);
|
|
50
|
+
if (fg('platform_synced_block_patch_14')) {
|
|
51
|
+
// Prevent native browser drag on the contentEditable="false" wrapper.
|
|
52
|
+
// Without this, clicking in empty space (outside the contentEditable
|
|
53
|
+
// renderer but inside the domRef) initiates a native element drag.
|
|
54
|
+
domRef.draggable = false;
|
|
55
|
+
this.dragStartHandler = e => {
|
|
56
|
+
e.preventDefault();
|
|
57
|
+
};
|
|
58
|
+
// eslint-disable-next-line @atlaskit/design-system/no-direct-use-of-web-platform-drag-and-drop, @repo/internal/dom-events/no-unsafe-event-listeners
|
|
59
|
+
domRef.addEventListener('dragstart', this.dragStartHandler);
|
|
60
|
+
}
|
|
39
61
|
return domRef;
|
|
40
62
|
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Allow mouse and selection events inside the renderer content to pass
|
|
66
|
+
* through to the browser so that users can select and copy text within a
|
|
67
|
+
* reference sync block.
|
|
68
|
+
*
|
|
69
|
+
* Events that originate inside the sync block content area (but not the label)
|
|
70
|
+
* are stopped so ProseMirror does not intercept them for node-level selection.
|
|
71
|
+
* This includes the full click-drag cycle (mousedown, mousemove, mouseup),
|
|
72
|
+
* click, dblclick, selectstart and cut. The `cut` event is stopped because
|
|
73
|
+
* mousedown explicitly sets a NodeSelection on the sync block — without
|
|
74
|
+
* stopping `cut`, a subsequent Ctrl+X would cause ProseMirror to delete the
|
|
75
|
+
* entire sync block node instead of cutting the user's text selection.
|
|
76
|
+
*
|
|
77
|
+
* Copy events are conditionally stopped: when the user has an active native
|
|
78
|
+
* text selection inside the renderer, `copy` is stopped so the browser handles
|
|
79
|
+
* it natively (copying the selected text). When there is no text selection
|
|
80
|
+
* (just a PM NodeSelection), `copy` is NOT stopped so ProseMirror's copy
|
|
81
|
+
* handler runs and sets the "sync-block-copied" flag for the reference paste flow.
|
|
82
|
+
*
|
|
83
|
+
* Events on the SyncBlockLabel are left for ProseMirror to handle, preserving
|
|
84
|
+
* label click interactions and the floating toolbar.
|
|
85
|
+
*
|
|
86
|
+
* The renderer wrapper sets contentEditable="true" to create a re-editable
|
|
87
|
+
* island inside ProseMirror's contentEditable="false" nodeview, enabling
|
|
88
|
+
* native text selection and preventing browser drag behaviour.
|
|
89
|
+
*/
|
|
90
|
+
stopEvent(event) {
|
|
91
|
+
if (!fg('platform_synced_block_patch_14')) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
const target = event.target;
|
|
95
|
+
if (!(target instanceof Element)) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Stop events inside the sync block content area, but not on the
|
|
100
|
+
// SyncBlockLabel (to preserve label click interactions).
|
|
101
|
+
if (target.closest(`.${SyncBlockSharedCssClassName.prefix}`) && !target.closest(`.${SyncBlockLabelSharedCssClassName.labelClassName}`)) {
|
|
102
|
+
const eventType = event.type;
|
|
103
|
+
|
|
104
|
+
// Stop `copy` only when there is an active native text selection
|
|
105
|
+
// inside the renderer. This lets the browser handle text copy
|
|
106
|
+
// natively. When there is no text selection (just a PM
|
|
107
|
+
// NodeSelection), we let PM handle the copy event so the
|
|
108
|
+
// "sync-block-copied" flag is set for the reference paste flow.
|
|
109
|
+
if (eventType === 'copy') {
|
|
110
|
+
const selection = window.getSelection();
|
|
111
|
+
return !!(selection && selection.toString().length > 0);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// For `cut`: when text is selected inside the renderer, stop the
|
|
115
|
+
// event and prevent default to avoid the browser removing text from
|
|
116
|
+
// the read-only content. When no text is selected (NodeSelection),
|
|
117
|
+
// let PM handle it so cut deletes the sync block as expected.
|
|
118
|
+
if (eventType === 'cut') {
|
|
119
|
+
const selection = window.getSelection();
|
|
120
|
+
if (selection && selection.toString().length > 0) {
|
|
121
|
+
event.preventDefault();
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Stop keyboard events that would cause PM to replace the
|
|
128
|
+
// NodeSelection with typed text (deleting the sync block).
|
|
129
|
+
// Allow modifier-key combos (Cmd+C, Cmd+A, etc.) through.
|
|
130
|
+
// Allow Delete/Backspace through so PM's delete handler can
|
|
131
|
+
// process them (e.g. show offline error flag).
|
|
132
|
+
if ((eventType === 'keydown' || eventType === 'keypress') && event instanceof KeyboardEvent && !event.metaKey && !event.ctrlKey && event.key !== 'Delete' && event.key !== 'Backspace') {
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
if (STOPPED_EVENT_TYPES.includes(eventType)) {
|
|
136
|
+
// Ensure the syncBlock has a NodeSelection so the floating
|
|
137
|
+
// toolbar is visible while the user interacts with the renderer.
|
|
138
|
+
// stopEvent prevents PM from processing the mousedown, so we
|
|
139
|
+
// need to explicitly set the selection ourselves.
|
|
140
|
+
if (eventType === 'mousedown' && !(this.view.state.selection instanceof NodeSelection && this.view.state.selection.node === this.node)) {
|
|
141
|
+
if (typeof this.getPos === 'function') {
|
|
142
|
+
const pos = this.getPos();
|
|
143
|
+
if (typeof pos === 'number') {
|
|
144
|
+
try {
|
|
145
|
+
const {
|
|
146
|
+
tr
|
|
147
|
+
} = this.view.state;
|
|
148
|
+
this.view.dispatch(tr.setSelection(NodeSelection.create(tr.doc, pos)));
|
|
149
|
+
} catch {
|
|
150
|
+
// pos no longer valid — leave selection unchanged
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
41
160
|
validUpdate(currentNode, newNode) {
|
|
42
161
|
// Only consider as the valid update if the localId and resourceId are the same
|
|
43
162
|
// This prevents PM reusing the same node view for different sync block node in live page transition
|
|
@@ -100,6 +219,12 @@ export class SyncBlock extends ReactNodeView {
|
|
|
100
219
|
destroy() {
|
|
101
220
|
var _this$unsubscribe;
|
|
102
221
|
(_this$unsubscribe = this.unsubscribe) === null || _this$unsubscribe === void 0 ? void 0 : _this$unsubscribe.call(this);
|
|
222
|
+
if (this.dragStartHandler) {
|
|
223
|
+
var _this$dom;
|
|
224
|
+
// eslint-disable-next-line @atlaskit/design-system/no-direct-use-of-web-platform-drag-and-drop, @repo/internal/dom-events/no-unsafe-event-listeners
|
|
225
|
+
(_this$dom = this.dom) === null || _this$dom === void 0 ? void 0 : _this$dom.removeEventListener('dragstart', this.dragStartHandler);
|
|
226
|
+
this.dragStartHandler = undefined;
|
|
227
|
+
}
|
|
103
228
|
super.destroy();
|
|
104
229
|
}
|
|
105
230
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useCallback } from 'react';
|
|
2
2
|
import { SyncBlockSharedCssClassName } from '@atlaskit/editor-common/sync-block';
|
|
3
3
|
import { SyncBlockError, useFetchSyncBlockData, useFetchSyncBlockTitle } from '@atlaskit/editor-synced-block-provider';
|
|
4
|
+
import { fg } from '@atlaskit/platform-feature-flags';
|
|
4
5
|
import { SyncBlockLabel } from './SyncBlockLabel';
|
|
5
6
|
const SyncBlockRendererWrapperDataId = 'sync-block-plugin-renderer-wrapper';
|
|
6
7
|
const SyncBlockRendererWrapperComponent = ({
|
|
@@ -17,11 +18,32 @@ const SyncBlockRendererWrapperComponent = ({
|
|
|
17
18
|
const contentUpdatedAt = syncBlockFetchResult === null || syncBlockFetchResult === void 0 ? void 0 : (_syncBlockFetchResult = syncBlockFetchResult.syncBlockInstance) === null || _syncBlockFetchResult === void 0 ? void 0 : (_syncBlockFetchResult2 = _syncBlockFetchResult.data) === null || _syncBlockFetchResult2 === void 0 ? void 0 : _syncBlockFetchResult2.contentUpdatedAt;
|
|
18
19
|
const isUnpublishedBlock = ((_syncBlockFetchResult3 = syncBlockFetchResult.syncBlockInstance) === null || _syncBlockFetchResult3 === void 0 ? void 0 : (_syncBlockFetchResult4 = _syncBlockFetchResult3.data) === null || _syncBlockFetchResult4 === void 0 ? void 0 : _syncBlockFetchResult4.status) === 'unpublished';
|
|
19
20
|
const isUnsyncedBlock = isUnpublishedBlock || (syncBlockFetchResult === null || syncBlockFetchResult === void 0 ? void 0 : (_syncBlockFetchResult5 = syncBlockFetchResult.syncBlockInstance) === null || _syncBlockFetchResult5 === void 0 ? void 0 : (_syncBlockFetchResult6 = _syncBlockFetchResult5.error) === null || _syncBlockFetchResult6 === void 0 ? void 0 : _syncBlockFetchResult6.type) === SyncBlockError.NotFound;
|
|
21
|
+
const isTextSelectionEnabled = fg('platform_synced_block_patch_14');
|
|
22
|
+
|
|
23
|
+
// Prevent editing in the contentEditable renderer wrapper. We set
|
|
24
|
+
// contentEditable="true" to enable text selection (creating an editable
|
|
25
|
+
// island inside ProseMirror's contentEditable="false" nodeview wrapper),
|
|
26
|
+
// but users must not be able to type into or modify the renderer content.
|
|
27
|
+
const preventInput = useCallback(e => {
|
|
28
|
+
e.preventDefault();
|
|
29
|
+
}, []);
|
|
20
30
|
return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("div", {
|
|
21
31
|
"data-testid": SyncBlockRendererWrapperDataId
|
|
22
32
|
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop
|
|
23
33
|
,
|
|
24
|
-
className: SyncBlockSharedCssClassName.renderer
|
|
34
|
+
className: SyncBlockSharedCssClassName.renderer,
|
|
35
|
+
contentEditable: isTextSelectionEnabled || undefined,
|
|
36
|
+
suppressContentEditableWarning: true
|
|
37
|
+
// Prevent the contentEditable div from being keyboard-focusable.
|
|
38
|
+
// It is only used to enable text selection, not as an input target.
|
|
39
|
+
// eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
|
|
40
|
+
,
|
|
41
|
+
tabIndex: isTextSelectionEnabled ? -1 : undefined,
|
|
42
|
+
onBeforeInput: isTextSelectionEnabled ? preventInput : undefined,
|
|
43
|
+
onPaste: isTextSelectionEnabled ? preventInput : undefined
|
|
44
|
+
// eslint-disable-next-line @atlaskit/design-system/no-direct-use-of-web-platform-drag-and-drop
|
|
45
|
+
,
|
|
46
|
+
onDrop: isTextSelectionEnabled ? preventInput : undefined
|
|
25
47
|
}, syncedBlockRenderer({
|
|
26
48
|
syncBlockFetchResult,
|
|
27
49
|
api
|
|
@@ -12,11 +12,19 @@ import React from 'react';
|
|
|
12
12
|
import { ACTION_SUBJECT } from '@atlaskit/editor-common/analytics';
|
|
13
13
|
import { ErrorBoundary } from '@atlaskit/editor-common/error-boundary';
|
|
14
14
|
import ReactNodeView from '@atlaskit/editor-common/react-node-view';
|
|
15
|
-
import { SyncBlockSharedCssClassName, SyncBlockActionsProvider } from '@atlaskit/editor-common/sync-block';
|
|
15
|
+
import { SyncBlockSharedCssClassName, SyncBlockLabelSharedCssClassName, SyncBlockActionsProvider } from '@atlaskit/editor-common/sync-block';
|
|
16
|
+
import { NodeSelection } from '@atlaskit/editor-prosemirror/state';
|
|
17
|
+
import { fg } from '@atlaskit/platform-feature-flags';
|
|
16
18
|
import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
|
|
17
19
|
import { removeSyncedBlockAtPos } from '../editor-commands';
|
|
18
20
|
import { SyncBlockRendererWrapper } from '../ui/SyncBlockRendererWrapper';
|
|
19
21
|
import { SyncBlockSSRReactContextsProvider } from '../ui/SyncBlockSSRReactContextsProvider';
|
|
22
|
+
|
|
23
|
+
// Event types that should be intercepted (returned as handled) when they
|
|
24
|
+
// originate inside the sync block content area, so ProseMirror does not
|
|
25
|
+
// convert them into node-level selections or drag operations and the browser
|
|
26
|
+
// can perform native text selection/cut instead.
|
|
27
|
+
var STOPPED_EVENT_TYPES = ['mousedown', 'mousemove', 'mouseup', 'click', 'dblclick', 'selectstart'];
|
|
20
28
|
export var SyncBlock = /*#__PURE__*/function (_ReactNodeView) {
|
|
21
29
|
function SyncBlock(props) {
|
|
22
30
|
var _this;
|
|
@@ -47,12 +55,124 @@ export var SyncBlock = /*#__PURE__*/function (_ReactNodeView) {
|
|
|
47
55
|
_inherits(SyncBlock, _ReactNodeView);
|
|
48
56
|
return _createClass(SyncBlock, [{
|
|
49
57
|
key: "createDomRef",
|
|
50
|
-
value:
|
|
58
|
+
value:
|
|
59
|
+
// Stored reference so the listener can be removed in destroy() to
|
|
60
|
+
// avoid a memory leak on every nodeview destruction.
|
|
61
|
+
|
|
62
|
+
function createDomRef() {
|
|
51
63
|
// eslint-disable-next-line @atlaskit/platform/no-direct-document-usage -- NodeView DOM must be created against active runtime document
|
|
52
64
|
var domRef = document.createElement('div');
|
|
53
65
|
domRef.classList.add(SyncBlockSharedCssClassName.prefix);
|
|
66
|
+
if (fg('platform_synced_block_patch_14')) {
|
|
67
|
+
// Prevent native browser drag on the contentEditable="false" wrapper.
|
|
68
|
+
// Without this, clicking in empty space (outside the contentEditable
|
|
69
|
+
// renderer but inside the domRef) initiates a native element drag.
|
|
70
|
+
domRef.draggable = false;
|
|
71
|
+
this.dragStartHandler = function (e) {
|
|
72
|
+
e.preventDefault();
|
|
73
|
+
};
|
|
74
|
+
// eslint-disable-next-line @atlaskit/design-system/no-direct-use-of-web-platform-drag-and-drop, @repo/internal/dom-events/no-unsafe-event-listeners
|
|
75
|
+
domRef.addEventListener('dragstart', this.dragStartHandler);
|
|
76
|
+
}
|
|
54
77
|
return domRef;
|
|
55
78
|
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Allow mouse and selection events inside the renderer content to pass
|
|
82
|
+
* through to the browser so that users can select and copy text within a
|
|
83
|
+
* reference sync block.
|
|
84
|
+
*
|
|
85
|
+
* Events that originate inside the sync block content area (but not the label)
|
|
86
|
+
* are stopped so ProseMirror does not intercept them for node-level selection.
|
|
87
|
+
* This includes the full click-drag cycle (mousedown, mousemove, mouseup),
|
|
88
|
+
* click, dblclick, selectstart and cut. The `cut` event is stopped because
|
|
89
|
+
* mousedown explicitly sets a NodeSelection on the sync block — without
|
|
90
|
+
* stopping `cut`, a subsequent Ctrl+X would cause ProseMirror to delete the
|
|
91
|
+
* entire sync block node instead of cutting the user's text selection.
|
|
92
|
+
*
|
|
93
|
+
* Copy events are conditionally stopped: when the user has an active native
|
|
94
|
+
* text selection inside the renderer, `copy` is stopped so the browser handles
|
|
95
|
+
* it natively (copying the selected text). When there is no text selection
|
|
96
|
+
* (just a PM NodeSelection), `copy` is NOT stopped so ProseMirror's copy
|
|
97
|
+
* handler runs and sets the "sync-block-copied" flag for the reference paste flow.
|
|
98
|
+
*
|
|
99
|
+
* Events on the SyncBlockLabel are left for ProseMirror to handle, preserving
|
|
100
|
+
* label click interactions and the floating toolbar.
|
|
101
|
+
*
|
|
102
|
+
* The renderer wrapper sets contentEditable="true" to create a re-editable
|
|
103
|
+
* island inside ProseMirror's contentEditable="false" nodeview, enabling
|
|
104
|
+
* native text selection and preventing browser drag behaviour.
|
|
105
|
+
*/
|
|
106
|
+
}, {
|
|
107
|
+
key: "stopEvent",
|
|
108
|
+
value: function stopEvent(event) {
|
|
109
|
+
if (!fg('platform_synced_block_patch_14')) {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
var target = event.target;
|
|
113
|
+
if (!(target instanceof Element)) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Stop events inside the sync block content area, but not on the
|
|
118
|
+
// SyncBlockLabel (to preserve label click interactions).
|
|
119
|
+
if (target.closest(".".concat(SyncBlockSharedCssClassName.prefix)) && !target.closest(".".concat(SyncBlockLabelSharedCssClassName.labelClassName))) {
|
|
120
|
+
var eventType = event.type;
|
|
121
|
+
|
|
122
|
+
// Stop `copy` only when there is an active native text selection
|
|
123
|
+
// inside the renderer. This lets the browser handle text copy
|
|
124
|
+
// natively. When there is no text selection (just a PM
|
|
125
|
+
// NodeSelection), we let PM handle the copy event so the
|
|
126
|
+
// "sync-block-copied" flag is set for the reference paste flow.
|
|
127
|
+
if (eventType === 'copy') {
|
|
128
|
+
var selection = window.getSelection();
|
|
129
|
+
return !!(selection && selection.toString().length > 0);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// For `cut`: when text is selected inside the renderer, stop the
|
|
133
|
+
// event and prevent default to avoid the browser removing text from
|
|
134
|
+
// the read-only content. When no text is selected (NodeSelection),
|
|
135
|
+
// let PM handle it so cut deletes the sync block as expected.
|
|
136
|
+
if (eventType === 'cut') {
|
|
137
|
+
var _selection = window.getSelection();
|
|
138
|
+
if (_selection && _selection.toString().length > 0) {
|
|
139
|
+
event.preventDefault();
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Stop keyboard events that would cause PM to replace the
|
|
146
|
+
// NodeSelection with typed text (deleting the sync block).
|
|
147
|
+
// Allow modifier-key combos (Cmd+C, Cmd+A, etc.) through.
|
|
148
|
+
// Allow Delete/Backspace through so PM's delete handler can
|
|
149
|
+
// process them (e.g. show offline error flag).
|
|
150
|
+
if ((eventType === 'keydown' || eventType === 'keypress') && event instanceof KeyboardEvent && !event.metaKey && !event.ctrlKey && event.key !== 'Delete' && event.key !== 'Backspace') {
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
if (STOPPED_EVENT_TYPES.includes(eventType)) {
|
|
154
|
+
// Ensure the syncBlock has a NodeSelection so the floating
|
|
155
|
+
// toolbar is visible while the user interacts with the renderer.
|
|
156
|
+
// stopEvent prevents PM from processing the mousedown, so we
|
|
157
|
+
// need to explicitly set the selection ourselves.
|
|
158
|
+
if (eventType === 'mousedown' && !(this.view.state.selection instanceof NodeSelection && this.view.state.selection.node === this.node)) {
|
|
159
|
+
if (typeof this.getPos === 'function') {
|
|
160
|
+
var pos = this.getPos();
|
|
161
|
+
if (typeof pos === 'number') {
|
|
162
|
+
try {
|
|
163
|
+
var tr = this.view.state.tr;
|
|
164
|
+
this.view.dispatch(tr.setSelection(NodeSelection.create(tr.doc, pos)));
|
|
165
|
+
} catch (_unused) {
|
|
166
|
+
// pos no longer valid — leave selection unchanged
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
56
176
|
}, {
|
|
57
177
|
key: "validUpdate",
|
|
58
178
|
value: function validUpdate(currentNode, newNode) {
|
|
@@ -128,6 +248,12 @@ export var SyncBlock = /*#__PURE__*/function (_ReactNodeView) {
|
|
|
128
248
|
value: function destroy() {
|
|
129
249
|
var _this$unsubscribe;
|
|
130
250
|
(_this$unsubscribe = this.unsubscribe) === null || _this$unsubscribe === void 0 || _this$unsubscribe.call(this);
|
|
251
|
+
if (this.dragStartHandler) {
|
|
252
|
+
var _this$dom;
|
|
253
|
+
// eslint-disable-next-line @atlaskit/design-system/no-direct-use-of-web-platform-drag-and-drop, @repo/internal/dom-events/no-unsafe-event-listeners
|
|
254
|
+
(_this$dom = this.dom) === null || _this$dom === void 0 || _this$dom.removeEventListener('dragstart', this.dragStartHandler);
|
|
255
|
+
this.dragStartHandler = undefined;
|
|
256
|
+
}
|
|
131
257
|
_superPropGet(SyncBlock, "destroy", this, 3)([]);
|
|
132
258
|
}
|
|
133
259
|
}]);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useCallback } from 'react';
|
|
2
2
|
import { SyncBlockSharedCssClassName } from '@atlaskit/editor-common/sync-block';
|
|
3
3
|
import { SyncBlockError, useFetchSyncBlockData, useFetchSyncBlockTitle } from '@atlaskit/editor-synced-block-provider';
|
|
4
|
+
import { fg } from '@atlaskit/platform-feature-flags';
|
|
4
5
|
import { SyncBlockLabel } from './SyncBlockLabel';
|
|
5
6
|
var SyncBlockRendererWrapperDataId = 'sync-block-plugin-renderer-wrapper';
|
|
6
7
|
var SyncBlockRendererWrapperComponent = function SyncBlockRendererWrapperComponent(_ref) {
|
|
@@ -16,11 +17,32 @@ var SyncBlockRendererWrapperComponent = function SyncBlockRendererWrapperCompone
|
|
|
16
17
|
var contentUpdatedAt = syncBlockFetchResult === null || syncBlockFetchResult === void 0 || (_syncBlockFetchResult = syncBlockFetchResult.syncBlockInstance) === null || _syncBlockFetchResult === void 0 || (_syncBlockFetchResult = _syncBlockFetchResult.data) === null || _syncBlockFetchResult === void 0 ? void 0 : _syncBlockFetchResult.contentUpdatedAt;
|
|
17
18
|
var isUnpublishedBlock = ((_syncBlockFetchResult2 = syncBlockFetchResult.syncBlockInstance) === null || _syncBlockFetchResult2 === void 0 || (_syncBlockFetchResult2 = _syncBlockFetchResult2.data) === null || _syncBlockFetchResult2 === void 0 ? void 0 : _syncBlockFetchResult2.status) === 'unpublished';
|
|
18
19
|
var isUnsyncedBlock = isUnpublishedBlock || (syncBlockFetchResult === null || syncBlockFetchResult === void 0 || (_syncBlockFetchResult3 = syncBlockFetchResult.syncBlockInstance) === null || _syncBlockFetchResult3 === void 0 || (_syncBlockFetchResult3 = _syncBlockFetchResult3.error) === null || _syncBlockFetchResult3 === void 0 ? void 0 : _syncBlockFetchResult3.type) === SyncBlockError.NotFound;
|
|
20
|
+
var isTextSelectionEnabled = fg('platform_synced_block_patch_14');
|
|
21
|
+
|
|
22
|
+
// Prevent editing in the contentEditable renderer wrapper. We set
|
|
23
|
+
// contentEditable="true" to enable text selection (creating an editable
|
|
24
|
+
// island inside ProseMirror's contentEditable="false" nodeview wrapper),
|
|
25
|
+
// but users must not be able to type into or modify the renderer content.
|
|
26
|
+
var preventInput = useCallback(function (e) {
|
|
27
|
+
e.preventDefault();
|
|
28
|
+
}, []);
|
|
19
29
|
return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("div", {
|
|
20
30
|
"data-testid": SyncBlockRendererWrapperDataId
|
|
21
31
|
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop
|
|
22
32
|
,
|
|
23
|
-
className: SyncBlockSharedCssClassName.renderer
|
|
33
|
+
className: SyncBlockSharedCssClassName.renderer,
|
|
34
|
+
contentEditable: isTextSelectionEnabled || undefined,
|
|
35
|
+
suppressContentEditableWarning: true
|
|
36
|
+
// Prevent the contentEditable div from being keyboard-focusable.
|
|
37
|
+
// It is only used to enable text selection, not as an input target.
|
|
38
|
+
// eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
|
|
39
|
+
,
|
|
40
|
+
tabIndex: isTextSelectionEnabled ? -1 : undefined,
|
|
41
|
+
onBeforeInput: isTextSelectionEnabled ? preventInput : undefined,
|
|
42
|
+
onPaste: isTextSelectionEnabled ? preventInput : undefined
|
|
43
|
+
// eslint-disable-next-line @atlaskit/design-system/no-direct-use-of-web-platform-drag-and-drop
|
|
44
|
+
,
|
|
45
|
+
onDrop: isTextSelectionEnabled ? preventInput : undefined
|
|
24
46
|
}, syncedBlockRenderer({
|
|
25
47
|
syncBlockFetchResult: syncBlockFetchResult,
|
|
26
48
|
api: api
|
|
@@ -31,7 +31,35 @@ export declare class SyncBlock extends ReactNodeView<SyncBlockNodeViewProps> {
|
|
|
31
31
|
private removeSyncBlockStable;
|
|
32
32
|
private fetchSyncBlockSourceInfoStable;
|
|
33
33
|
unsubscribe: (() => void) | undefined;
|
|
34
|
+
private dragStartHandler;
|
|
34
35
|
createDomRef(): HTMLElement;
|
|
36
|
+
/**
|
|
37
|
+
* Allow mouse and selection events inside the renderer content to pass
|
|
38
|
+
* through to the browser so that users can select and copy text within a
|
|
39
|
+
* reference sync block.
|
|
40
|
+
*
|
|
41
|
+
* Events that originate inside the sync block content area (but not the label)
|
|
42
|
+
* are stopped so ProseMirror does not intercept them for node-level selection.
|
|
43
|
+
* This includes the full click-drag cycle (mousedown, mousemove, mouseup),
|
|
44
|
+
* click, dblclick, selectstart and cut. The `cut` event is stopped because
|
|
45
|
+
* mousedown explicitly sets a NodeSelection on the sync block — without
|
|
46
|
+
* stopping `cut`, a subsequent Ctrl+X would cause ProseMirror to delete the
|
|
47
|
+
* entire sync block node instead of cutting the user's text selection.
|
|
48
|
+
*
|
|
49
|
+
* Copy events are conditionally stopped: when the user has an active native
|
|
50
|
+
* text selection inside the renderer, `copy` is stopped so the browser handles
|
|
51
|
+
* it natively (copying the selected text). When there is no text selection
|
|
52
|
+
* (just a PM NodeSelection), `copy` is NOT stopped so ProseMirror's copy
|
|
53
|
+
* handler runs and sets the "sync-block-copied" flag for the reference paste flow.
|
|
54
|
+
*
|
|
55
|
+
* Events on the SyncBlockLabel are left for ProseMirror to handle, preserving
|
|
56
|
+
* label click interactions and the floating toolbar.
|
|
57
|
+
*
|
|
58
|
+
* The renderer wrapper sets contentEditable="true" to create a re-editable
|
|
59
|
+
* island inside ProseMirror's contentEditable="false" nodeview, enabling
|
|
60
|
+
* native text selection and preventing browser drag behaviour.
|
|
61
|
+
*/
|
|
62
|
+
stopEvent(event: Event): boolean;
|
|
35
63
|
validUpdate(currentNode: PMNode, newNode: PMNode): boolean;
|
|
36
64
|
update(node: PMNode, decorations: ReadonlyArray<Decoration>, innerDecorations?: DecorationSource): boolean;
|
|
37
65
|
render({ getPos }: SyncBlockNodeViewProps): React.JSX.Element | null;
|
|
@@ -31,7 +31,35 @@ export declare class SyncBlock extends ReactNodeView<SyncBlockNodeViewProps> {
|
|
|
31
31
|
private removeSyncBlockStable;
|
|
32
32
|
private fetchSyncBlockSourceInfoStable;
|
|
33
33
|
unsubscribe: (() => void) | undefined;
|
|
34
|
+
private dragStartHandler;
|
|
34
35
|
createDomRef(): HTMLElement;
|
|
36
|
+
/**
|
|
37
|
+
* Allow mouse and selection events inside the renderer content to pass
|
|
38
|
+
* through to the browser so that users can select and copy text within a
|
|
39
|
+
* reference sync block.
|
|
40
|
+
*
|
|
41
|
+
* Events that originate inside the sync block content area (but not the label)
|
|
42
|
+
* are stopped so ProseMirror does not intercept them for node-level selection.
|
|
43
|
+
* This includes the full click-drag cycle (mousedown, mousemove, mouseup),
|
|
44
|
+
* click, dblclick, selectstart and cut. The `cut` event is stopped because
|
|
45
|
+
* mousedown explicitly sets a NodeSelection on the sync block — without
|
|
46
|
+
* stopping `cut`, a subsequent Ctrl+X would cause ProseMirror to delete the
|
|
47
|
+
* entire sync block node instead of cutting the user's text selection.
|
|
48
|
+
*
|
|
49
|
+
* Copy events are conditionally stopped: when the user has an active native
|
|
50
|
+
* text selection inside the renderer, `copy` is stopped so the browser handles
|
|
51
|
+
* it natively (copying the selected text). When there is no text selection
|
|
52
|
+
* (just a PM NodeSelection), `copy` is NOT stopped so ProseMirror's copy
|
|
53
|
+
* handler runs and sets the "sync-block-copied" flag for the reference paste flow.
|
|
54
|
+
*
|
|
55
|
+
* Events on the SyncBlockLabel are left for ProseMirror to handle, preserving
|
|
56
|
+
* label click interactions and the floating toolbar.
|
|
57
|
+
*
|
|
58
|
+
* The renderer wrapper sets contentEditable="true" to create a re-editable
|
|
59
|
+
* island inside ProseMirror's contentEditable="false" nodeview, enabling
|
|
60
|
+
* native text selection and preventing browser drag behaviour.
|
|
61
|
+
*/
|
|
62
|
+
stopEvent(event: Event): boolean;
|
|
35
63
|
validUpdate(currentNode: PMNode, newNode: PMNode): boolean;
|
|
36
64
|
update(node: PMNode, decorations: ReadonlyArray<Decoration>, innerDecorations?: DecorationSource): boolean;
|
|
37
65
|
render({ getPos }: SyncBlockNodeViewProps): React.JSX.Element | null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlaskit/editor-plugin-synced-block",
|
|
3
|
-
"version": "9.0.
|
|
3
|
+
"version": "9.0.3",
|
|
4
4
|
"description": "SyncedBlock plugin for @atlaskit/editor-core",
|
|
5
5
|
"author": "Atlassian Pty Ltd",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -28,12 +28,12 @@
|
|
|
28
28
|
"sideEffects": false,
|
|
29
29
|
"atlaskit:src": "src/index.ts",
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@atlaskit/adf-schema": "^52.
|
|
31
|
+
"@atlaskit/adf-schema": "^52.16.0",
|
|
32
32
|
"@atlaskit/button": "23.11.8",
|
|
33
33
|
"@atlaskit/dropdown-menu": "16.10.1",
|
|
34
34
|
"@atlaskit/editor-json-transformer": "^8.33.0",
|
|
35
35
|
"@atlaskit/editor-plugin-analytics": "^11.0.0",
|
|
36
|
-
"@atlaskit/editor-plugin-block-menu": "^10.
|
|
36
|
+
"@atlaskit/editor-plugin-block-menu": "^10.1.0",
|
|
37
37
|
"@atlaskit/editor-plugin-connectivity": "11.0.0",
|
|
38
38
|
"@atlaskit/editor-plugin-content-format": "^5.0.0",
|
|
39
39
|
"@atlaskit/editor-plugin-decorations": "^11.0.0",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"@atlaskit/editor-prosemirror": "^7.3.0",
|
|
45
45
|
"@atlaskit/editor-shared-styles": "^3.11.0",
|
|
46
46
|
"@atlaskit/editor-synced-block-provider": "^7.0.0",
|
|
47
|
-
"@atlaskit/editor-toolbar": "^1.
|
|
47
|
+
"@atlaskit/editor-toolbar": "^1.10.0",
|
|
48
48
|
"@atlaskit/flag": "^17.12.0",
|
|
49
49
|
"@atlaskit/icon": "35.4.0",
|
|
50
50
|
"@atlaskit/icon-lab": "^6.13.0",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"@atlaskit/platform-feature-flags": "^1.1.0",
|
|
55
55
|
"@atlaskit/primitives": "^19.0.0",
|
|
56
56
|
"@atlaskit/spinner": "19.1.2",
|
|
57
|
-
"@atlaskit/tmp-editor-statsig": "^
|
|
57
|
+
"@atlaskit/tmp-editor-statsig": "^89.0.0",
|
|
58
58
|
"@atlaskit/tokens": "13.1.1",
|
|
59
59
|
"@atlaskit/tooltip": "^22.6.0",
|
|
60
60
|
"@atlaskit/visually-hidden": "^3.1.0",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"date-fns": "^2.17.0"
|
|
65
65
|
},
|
|
66
66
|
"peerDependencies": {
|
|
67
|
-
"@atlaskit/editor-common": "^115.
|
|
67
|
+
"@atlaskit/editor-common": "^115.2.0",
|
|
68
68
|
"react": "^18.2.0",
|
|
69
69
|
"react-intl": "^5.25.1 || ^6.0.0 || ^7.0.0"
|
|
70
70
|
},
|