@atlaskit/editor-plugin-block-controls 3.15.10 → 3.15.12
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 +19 -0
- package/dist/cjs/pm-plugins/decorations-drag-handle.js +29 -11
- package/dist/cjs/pm-plugins/decorations-quick-insert-button.js +51 -9
- package/dist/cjs/pm-plugins/quick-insert-calculate-position.js +70 -0
- package/dist/cjs/pm-plugins/vanilla-quick-insert.js +190 -0
- package/dist/cjs/pm-plugins/vanilla-tooltip.js +179 -0
- package/dist/cjs/ui/global-styles.js +86 -1
- package/dist/es2019/pm-plugins/decorations-drag-handle.js +29 -11
- package/dist/es2019/pm-plugins/decorations-quick-insert-button.js +51 -9
- package/dist/es2019/pm-plugins/quick-insert-calculate-position.js +64 -0
- package/dist/es2019/pm-plugins/vanilla-quick-insert.js +189 -0
- package/dist/es2019/pm-plugins/vanilla-tooltip.js +147 -0
- package/dist/es2019/ui/global-styles.js +84 -1
- package/dist/esm/pm-plugins/decorations-drag-handle.js +29 -11
- package/dist/esm/pm-plugins/decorations-quick-insert-button.js +51 -9
- package/dist/esm/pm-plugins/quick-insert-calculate-position.js +64 -0
- package/dist/esm/pm-plugins/vanilla-quick-insert.js +183 -0
- package/dist/esm/pm-plugins/vanilla-tooltip.js +172 -0
- package/dist/esm/ui/global-styles.js +86 -1
- package/dist/types/pm-plugins/quick-insert-calculate-position.d.ts +12 -0
- package/dist/types/pm-plugins/vanilla-quick-insert.d.ts +21 -0
- package/dist/types/pm-plugins/vanilla-tooltip.d.ts +27 -0
- package/dist/types-ts4.5/pm-plugins/quick-insert-calculate-position.d.ts +12 -0
- package/dist/types-ts4.5/pm-plugins/vanilla-quick-insert.d.ts +21 -0
- package/dist/types-ts4.5/pm-plugins/vanilla-tooltip.d.ts +27 -0
- package/package.json +5 -4
|
@@ -7,9 +7,11 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
7
7
|
exports.GlobalStylesWrapper = void 0;
|
|
8
8
|
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
9
9
|
var _react = require("@emotion/react");
|
|
10
|
+
var _styles = require("@atlaskit/editor-common/styles");
|
|
10
11
|
var _whitespace = require("@atlaskit/editor-common/whitespace");
|
|
11
12
|
var _editorSharedStyles = require("@atlaskit/editor-shared-styles");
|
|
12
13
|
var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
|
|
14
|
+
var _constants = require("@atlaskit/theme/constants");
|
|
13
15
|
var _experiments = require("@atlaskit/tmp-editor-statsig/experiments");
|
|
14
16
|
var _consts = require("./consts");
|
|
15
17
|
/**
|
|
@@ -125,6 +127,89 @@ var globalStyles = function globalStyles() {
|
|
|
125
127
|
}
|
|
126
128
|
});
|
|
127
129
|
};
|
|
130
|
+
var quickInsertStyles = function quickInsertStyles() {
|
|
131
|
+
return (0, _react.css)({
|
|
132
|
+
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors
|
|
133
|
+
'.blocks-quick-insert-button': {
|
|
134
|
+
backgroundColor: 'transparent',
|
|
135
|
+
top: "var(--top-override,8px)",
|
|
136
|
+
position: 'sticky',
|
|
137
|
+
boxSizing: 'border-box',
|
|
138
|
+
display: 'flex',
|
|
139
|
+
flexDirection: 'column',
|
|
140
|
+
justifyContent: 'center',
|
|
141
|
+
alignItems: 'center',
|
|
142
|
+
height: "var(--ds-space-300, 24px)",
|
|
143
|
+
width: "var(--ds-space-300, 24px)",
|
|
144
|
+
border: 'none',
|
|
145
|
+
borderRadius: '50%',
|
|
146
|
+
zIndex: _constants.layers.card(),
|
|
147
|
+
outline: 'none',
|
|
148
|
+
cursor: 'pointer',
|
|
149
|
+
color: "var(--ds-icon-subtle, #626F86)"
|
|
150
|
+
},
|
|
151
|
+
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors, @atlaskit/ui-styling-standard/no-unsafe-selectors
|
|
152
|
+
'[data-blocks-quick-insert-container]:has(~ [data-prosemirror-node-name="table"] .pm-table-with-controls tr.sticky) &': {
|
|
153
|
+
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/ui-styling-standard/no-unsafe-values
|
|
154
|
+
'--top-override': "".concat(_styles.tableControlsSpacing, "px")
|
|
155
|
+
},
|
|
156
|
+
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors, @atlaskit/ui-styling-standard/no-unsafe-selectors
|
|
157
|
+
'[data-prosemirror-mark-name="breakout"]:has([data-blocks-quick-insert-container]):has(~ [data-prosemirror-node-name="table"] .pm-table-with-controls tr.sticky) &': {
|
|
158
|
+
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/ui-styling-standard/no-unsafe-values
|
|
159
|
+
'--top-override': "".concat(_styles.tableControlsSpacing, "px")
|
|
160
|
+
},
|
|
161
|
+
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors
|
|
162
|
+
'.blocks-quick-insert-button:hover': {
|
|
163
|
+
backgroundColor: "var(--ds-background-neutral-subtle-hovered, #091E420F)"
|
|
164
|
+
},
|
|
165
|
+
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors
|
|
166
|
+
'.blocks-quick-insert-button:active': {
|
|
167
|
+
backgroundColor: "var(--ds-background-neutral-subtle-pressed, #091E4224)"
|
|
168
|
+
},
|
|
169
|
+
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors
|
|
170
|
+
'.blocks-quick-insert-button:focus': {
|
|
171
|
+
outline: "2px solid ".concat("var(--ds-border-focused, #388BFF)")
|
|
172
|
+
},
|
|
173
|
+
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors
|
|
174
|
+
'.blocks-quick-insert-visible-container': {
|
|
175
|
+
transition: 'opacity 0.1s ease-in-out, visibility 0.1s ease-in-out',
|
|
176
|
+
opacity: 1,
|
|
177
|
+
visibility: 'visible'
|
|
178
|
+
},
|
|
179
|
+
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors
|
|
180
|
+
'.blocks-quick-insert-invisible-container': {
|
|
181
|
+
transition: 'opacity 0.1s ease-in-out, visibility 0.1s ease-in-out',
|
|
182
|
+
opacity: 0,
|
|
183
|
+
visibility: 'hidden'
|
|
184
|
+
},
|
|
185
|
+
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors
|
|
186
|
+
'.blocks-quick-insert-tooltip': {
|
|
187
|
+
zIndex: _constants.layers.tooltip(),
|
|
188
|
+
borderRadius: "var(--ds-border-radius, 4px)",
|
|
189
|
+
padding: "var(--ds-space-050, 4px)".concat(" 0"),
|
|
190
|
+
boxSizing: 'border-box',
|
|
191
|
+
maxWidth: '240px',
|
|
192
|
+
backgroundColor: "var(--ds-background-neutral-bold, #44546F)",
|
|
193
|
+
color: "var(--ds-text-inverse, #FFFFFF)",
|
|
194
|
+
font: "var(--ds-font-body-UNSAFE_small, normal 400 12px/16px ui-sans-serif, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Ubuntu, \"Helvetica Neue\", sans-serif)",
|
|
195
|
+
insetBlockStart: "var(--ds-space-0, 0px)",
|
|
196
|
+
insetInlineStart: "var(--ds-space-0, 0px)",
|
|
197
|
+
overflowWrap: 'break-word',
|
|
198
|
+
paddingBlockEnd: "var(--ds-space-025, 2px)",
|
|
199
|
+
paddingBlockStart: "var(--ds-space-025, 2px)",
|
|
200
|
+
paddingInlineEnd: "var(--ds-space-075, 6px)",
|
|
201
|
+
paddingInlineStart: "var(--ds-space-075, 6px)",
|
|
202
|
+
wordWrap: 'break-word',
|
|
203
|
+
pointerEvents: 'none',
|
|
204
|
+
userSelect: 'none',
|
|
205
|
+
// Based on: platform/packages/design-system/motion/src/entering/keyframes-motion.tsx
|
|
206
|
+
transition: 'opacity .1s ease-in-out, transform .1s ease-in-out, visibility .1s ease-in-out',
|
|
207
|
+
'@media (prefers-reduced-motion: reduce)': {
|
|
208
|
+
transition: 'none'
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
};
|
|
128
213
|
var topLevelNodeMarginStyles = (0, _react.css)({
|
|
129
214
|
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors, @atlaskit/ui-styling-standard/no-unsafe-selectors -- Ignored via go/DSP-18766
|
|
130
215
|
'.ProseMirror': {
|
|
@@ -201,6 +286,6 @@ var blockCardWithoutLayout = (0, _react.css)({
|
|
|
201
286
|
});
|
|
202
287
|
var GlobalStylesWrapper = exports.GlobalStylesWrapper = function GlobalStylesWrapper() {
|
|
203
288
|
return (0, _react.jsx)(_react.Global, {
|
|
204
|
-
styles: [globalStyles(), globalDnDStyle, extendedHoverZone(), (0, _experiments.editorExperiment)('platform_editor_controls', 'variant1') && (0, _platformFeatureFlags.fg)('platform_editor_controls_widget_visibility') ? undefined : withInlineNodeStyle, withDeleteLinesStyleFix, withMediaSingleStyleFix, legacyBreakoutWideLayoutStyle, headingWithIndentationInLayoutStyleFix, (0, _experiments.editorExperiment)('advanced_layouts', true) ? blockCardWithoutLayout : undefined, withDividerInPanelStyleFix, withFormatInLayoutStyleFix, (0, _platformFeatureFlags.fg)('platform_editor_fix_safari_cursor_hidden_empty') ? withRelativePosStyle : withRelativePosStyleLegacy, topLevelNodeMarginStyles, withAnchorNameZindexStyle]
|
|
289
|
+
styles: [globalStyles(), globalDnDStyle, extendedHoverZone(), (0, _experiments.editorExperiment)('platform_editor_controls', 'variant1') && (0, _platformFeatureFlags.fg)('platform_editor_controls_widget_visibility') ? undefined : withInlineNodeStyle, (0, _experiments.editorExperiment)('platform_editor_block_control_optimise_render', true) ? quickInsertStyles : undefined, withDeleteLinesStyleFix, withMediaSingleStyleFix, legacyBreakoutWideLayoutStyle, headingWithIndentationInLayoutStyleFix, (0, _experiments.editorExperiment)('advanced_layouts', true) ? blockCardWithoutLayout : undefined, withDividerInPanelStyleFix, withFormatInLayoutStyleFix, (0, _platformFeatureFlags.fg)('platform_editor_fix_safari_cursor_hidden_empty') ? withRelativePosStyle : withRelativePosStyleLegacy, topLevelNodeMarginStyles, withAnchorNameZindexStyle]
|
|
205
290
|
});
|
|
206
291
|
};
|
|
@@ -28,6 +28,34 @@ export const dragHandleDecoration = (api, formatMessage, pos, anchorName, nodeTy
|
|
|
28
28
|
}
|
|
29
29
|
let unbind;
|
|
30
30
|
const key = uuid();
|
|
31
|
+
const widgetSpec = editorExperiment('platform_editor_breakout_resizing', true) ? {
|
|
32
|
+
side: -1,
|
|
33
|
+
type: TYPE_HANDLE_DEC,
|
|
34
|
+
testid: `${TYPE_HANDLE_DEC}-${uuid()}`,
|
|
35
|
+
/**
|
|
36
|
+
* sigh - `marks` influences the position that the widget is drawn (as described on the `side` property).
|
|
37
|
+
* Leaving this 'undefined' causes the widget to be wrapped in the mark before this position which creates
|
|
38
|
+
* weird stacking context issues. Providing an empty array causes the widget to correctly render before
|
|
39
|
+
* this exact position at the top of the DOM.
|
|
40
|
+
*/
|
|
41
|
+
marks: [],
|
|
42
|
+
destroy: node => {
|
|
43
|
+
unbind && unbind();
|
|
44
|
+
if (editorExperiment('platform_editor_block_control_optimise_render', true) && node instanceof HTMLElement) {
|
|
45
|
+
ReactDOM.unmountComponentAtNode(node);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
} : {
|
|
49
|
+
side: -1,
|
|
50
|
+
type: TYPE_HANDLE_DEC,
|
|
51
|
+
testid: `${TYPE_HANDLE_DEC}-${uuid()}`,
|
|
52
|
+
destroy: node => {
|
|
53
|
+
unbind && unbind();
|
|
54
|
+
if (editorExperiment('platform_editor_block_control_optimise_render', true) && node instanceof HTMLElement) {
|
|
55
|
+
ReactDOM.unmountComponentAtNode(node);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
};
|
|
31
59
|
return Decoration.widget(pos, (view, getPosUnsafe) => {
|
|
32
60
|
const element = document.createElement('span');
|
|
33
61
|
// inline decoration causes focus issues when refocusing Editor into first line
|
|
@@ -117,15 +145,5 @@ export const dragHandleDecoration = (api, formatMessage, pos, anchorName, nodeTy
|
|
|
117
145
|
}), element);
|
|
118
146
|
}
|
|
119
147
|
return element;
|
|
120
|
-
},
|
|
121
|
-
side: -1,
|
|
122
|
-
type: TYPE_HANDLE_DEC,
|
|
123
|
-
testid: `${TYPE_HANDLE_DEC}-${uuid()}`,
|
|
124
|
-
destroy: node => {
|
|
125
|
-
unbind && unbind();
|
|
126
|
-
if (editorExperiment('platform_editor_block_control_optimise_render', true) && node instanceof HTMLElement) {
|
|
127
|
-
ReactDOM.unmountComponentAtNode(node);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
});
|
|
148
|
+
}, widgetSpec);
|
|
131
149
|
};
|
|
@@ -2,18 +2,68 @@ import { createElement } from 'react';
|
|
|
2
2
|
import uuid from 'uuid';
|
|
3
3
|
import { Decoration } from '@atlaskit/editor-prosemirror/view';
|
|
4
4
|
import { fg } from '@atlaskit/platform-feature-flags';
|
|
5
|
+
import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
|
|
5
6
|
import { QuickInsertWithVisibility, TypeAheadControl } from '../ui/quick-insert-button';
|
|
7
|
+
import { createVanillaButton } from './vanilla-quick-insert';
|
|
6
8
|
const TYPE_QUICK_INSERT = 'INSERT_BUTTON';
|
|
7
9
|
export const findQuickInsertInsertButtonDecoration = (decorations, from, to) => {
|
|
8
10
|
return decorations.find(from, to, spec => spec.type === TYPE_QUICK_INSERT);
|
|
9
11
|
};
|
|
10
12
|
export const quickInsertButtonDecoration = (api, formatMessage, rootPos, anchorName, nodeType, nodeViewPortalProviderAPI, rootAnchorName, rootNodeType, anchorRectCache) => {
|
|
11
13
|
const key = uuid();
|
|
14
|
+
const cleanupCallbacks = [];
|
|
15
|
+
const widgetSpec = editorExperiment('platform_editor_breakout_resizing', true) ? {
|
|
16
|
+
side: -2,
|
|
17
|
+
type: TYPE_QUICK_INSERT,
|
|
18
|
+
/**
|
|
19
|
+
* sigh - `marks` influences the position that the widget is drawn (as described on the `side` property).
|
|
20
|
+
* Leaving this 'undefined' causes the widget to be wrapped in the mark before this position which creates
|
|
21
|
+
* weird stacking context issues. Providing an empty array causes the widget to correctly render before
|
|
22
|
+
* this exact position at the top of the DOM.
|
|
23
|
+
*/
|
|
24
|
+
marks: [],
|
|
25
|
+
destroy: _ => {
|
|
26
|
+
if (fg('platform_editor_fix_widget_destroy')) {
|
|
27
|
+
nodeViewPortalProviderAPI.remove(key);
|
|
28
|
+
}
|
|
29
|
+
cleanupCallbacks.forEach(cb => {
|
|
30
|
+
cb();
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
} : {
|
|
34
|
+
side: -2,
|
|
35
|
+
type: TYPE_QUICK_INSERT,
|
|
36
|
+
destroy: _ => {
|
|
37
|
+
if (fg('platform_editor_fix_widget_destroy')) {
|
|
38
|
+
nodeViewPortalProviderAPI.remove(key);
|
|
39
|
+
}
|
|
40
|
+
cleanupCallbacks.forEach(cb => {
|
|
41
|
+
cb();
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
};
|
|
12
45
|
return Decoration.widget(rootPos, (view, getPos) => {
|
|
13
46
|
const element = document.createElement('span');
|
|
14
47
|
element.contentEditable = 'false';
|
|
15
48
|
element.setAttribute('data-blocks-quick-insert-container', 'true');
|
|
16
49
|
element.setAttribute('data-testid', 'block-ctrl-quick-insert-button');
|
|
50
|
+
if (editorExperiment('platform_editor_block_control_optimise_render', true, {
|
|
51
|
+
exposure: true
|
|
52
|
+
})) {
|
|
53
|
+
const vanillaElement = createVanillaButton({
|
|
54
|
+
formatMessage,
|
|
55
|
+
api,
|
|
56
|
+
view,
|
|
57
|
+
getPos,
|
|
58
|
+
cleanupCallbacks,
|
|
59
|
+
rootAnchorName: rootAnchorName !== null && rootAnchorName !== void 0 ? rootAnchorName : nodeType,
|
|
60
|
+
anchorName,
|
|
61
|
+
rootNodeType: rootNodeType !== null && rootNodeType !== void 0 ? rootNodeType : nodeType,
|
|
62
|
+
anchorRectCache
|
|
63
|
+
});
|
|
64
|
+
element.appendChild(vanillaElement);
|
|
65
|
+
return element;
|
|
66
|
+
}
|
|
17
67
|
|
|
18
68
|
// all changes already under experiment
|
|
19
69
|
if (fg('platform_editor_controls_widget_visibility')) {
|
|
@@ -42,13 +92,5 @@ export const quickInsertButtonDecoration = (api, formatMessage, rootPos, anchorN
|
|
|
42
92
|
}), element, key);
|
|
43
93
|
}
|
|
44
94
|
return element;
|
|
45
|
-
},
|
|
46
|
-
side: -2,
|
|
47
|
-
type: TYPE_QUICK_INSERT,
|
|
48
|
-
destroy: _ => {
|
|
49
|
-
if (fg('platform_editor_fix_widget_destroy')) {
|
|
50
|
-
nodeViewPortalProviderAPI.remove(key);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
});
|
|
95
|
+
}, widgetSpec);
|
|
54
96
|
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { rootElementGap, topPositionAdjustment, QUICK_INSERT_DIMENSIONS, QUICK_INSERT_LEFT_OFFSET } from '../ui/consts';
|
|
2
|
+
import { refreshAnchorName } from '../ui/utils/anchor-name';
|
|
3
|
+
import { getControlBottomCSSValue, getControlHeightCSSValue, getNodeHeight, getTopPosition, shouldBeSticky } from './utils/drag-handle-positions';
|
|
4
|
+
import { getLeftPositionForRootElement } from './utils/widget-positions';
|
|
5
|
+
|
|
6
|
+
// Adapted from `src/ui/drag-handle.tsx` as positioning logic is similar
|
|
7
|
+
// CHANGES - added an offset so quick insert button can be positioned beside drag handle
|
|
8
|
+
// CHANGES - removed `editorExperiment('nested-dnd', true)` check and rootNodeType calculation
|
|
9
|
+
// CHANGES - replace anchorName with rootAnchorName
|
|
10
|
+
// CHANGES - `removed editorExperiment('advanced_layouts', true) && isLayoutColumn` checks as quick insert button will not be positioned for layout column
|
|
11
|
+
export const calculatePosition = ({
|
|
12
|
+
rootAnchorName,
|
|
13
|
+
anchorName,
|
|
14
|
+
view,
|
|
15
|
+
getPos,
|
|
16
|
+
rootNodeType,
|
|
17
|
+
macroInteractionUpdates,
|
|
18
|
+
anchorRectCache
|
|
19
|
+
}) => {
|
|
20
|
+
const supportsAnchor = CSS.supports('top', `anchor(${rootAnchorName} start)`) && CSS.supports('left', `anchor(${rootAnchorName} start)`);
|
|
21
|
+
const safeAnchorName = refreshAnchorName({
|
|
22
|
+
getPos,
|
|
23
|
+
view,
|
|
24
|
+
anchorName: rootAnchorName
|
|
25
|
+
});
|
|
26
|
+
const dom = view.dom.querySelector(`[data-drag-handler-anchor-name="${safeAnchorName}"]`);
|
|
27
|
+
const hasResizer = rootNodeType === 'table' || rootNodeType === 'mediaSingle';
|
|
28
|
+
const isExtension = rootNodeType === 'extension' || rootNodeType === 'bodiedExtension';
|
|
29
|
+
const isBlockCard = rootNodeType === 'blockCard';
|
|
30
|
+
const isEmbedCard = rootNodeType === 'embedCard';
|
|
31
|
+
const isMacroInteractionUpdates = macroInteractionUpdates && isExtension;
|
|
32
|
+
let innerContainer = null;
|
|
33
|
+
if (dom) {
|
|
34
|
+
if (isEmbedCard) {
|
|
35
|
+
innerContainer = dom.querySelector('.rich-media-item');
|
|
36
|
+
} else if (hasResizer) {
|
|
37
|
+
innerContainer = dom.querySelector('.resizer-item');
|
|
38
|
+
} else if (isExtension) {
|
|
39
|
+
innerContainer = dom.querySelector('.extension-container[data-layout]');
|
|
40
|
+
} else if (isBlockCard) {
|
|
41
|
+
//specific to datasource blockCard
|
|
42
|
+
innerContainer = dom.querySelector('.datasourceView-content-inner-wrap');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const isEdgeCase = (hasResizer || isExtension || isEmbedCard || isBlockCard) && innerContainer;
|
|
46
|
+
const isSticky = shouldBeSticky(rootNodeType);
|
|
47
|
+
const bottom = getControlBottomCSSValue(safeAnchorName || anchorName, isSticky, true);
|
|
48
|
+
if (supportsAnchor) {
|
|
49
|
+
return {
|
|
50
|
+
left: isEdgeCase ? `calc(anchor(${safeAnchorName} start) + ${getLeftPositionForRootElement(dom, rootNodeType, QUICK_INSERT_DIMENSIONS, innerContainer, isMacroInteractionUpdates)} + -${QUICK_INSERT_LEFT_OFFSET}px)` : `calc(anchor(${safeAnchorName} start) - ${QUICK_INSERT_DIMENSIONS.width}px - ${rootElementGap(rootNodeType)}px + -${QUICK_INSERT_LEFT_OFFSET}px)`,
|
|
51
|
+
top: `calc(anchor(${safeAnchorName} start) + ${topPositionAdjustment(rootNodeType)}px)`,
|
|
52
|
+
...bottom
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// expensive, calls offsetHeight
|
|
57
|
+
const nodeHeight = getNodeHeight(dom, safeAnchorName || anchorName, anchorRectCache) || 0;
|
|
58
|
+
const height = getControlHeightCSSValue(nodeHeight, isSticky, true, "var(--ds-space-300, 24px)");
|
|
59
|
+
return {
|
|
60
|
+
left: isEdgeCase ? `calc(${(dom === null || dom === void 0 ? void 0 : dom.offsetLeft) || 0}px + ${getLeftPositionForRootElement(dom, rootNodeType, QUICK_INSERT_DIMENSIONS, innerContainer, isMacroInteractionUpdates)} + -${QUICK_INSERT_LEFT_OFFSET}px)` : `calc(${getLeftPositionForRootElement(dom, rootNodeType, QUICK_INSERT_DIMENSIONS, innerContainer, isMacroInteractionUpdates)} + -${QUICK_INSERT_LEFT_OFFSET}px)`,
|
|
61
|
+
top: getTopPosition(dom, rootNodeType),
|
|
62
|
+
...height
|
|
63
|
+
};
|
|
64
|
+
};
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { convertToInlineCss } from '@atlaskit/editor-common/lazy-node-view';
|
|
2
|
+
import { blockControlsMessages as messages } from '@atlaskit/editor-common/messages';
|
|
3
|
+
import { DOMSerializer } from '@atlaskit/editor-prosemirror/model';
|
|
4
|
+
import { TextSelection } from '@atlaskit/editor-prosemirror/state';
|
|
5
|
+
import { findParentNode, findParentNodeOfType } from '@atlaskit/editor-prosemirror/utils';
|
|
6
|
+
import { CellSelection } from '@atlaskit/editor-tables/cell-selection';
|
|
7
|
+
import { isInTextSelection, isNestedNodeSelected, isNonEditableBlock, isSelectionInNode } from '../ui/utils/document-checks';
|
|
8
|
+
import { createNewLine } from '../ui/utils/editor-commands';
|
|
9
|
+
import { calculatePosition } from './quick-insert-calculate-position';
|
|
10
|
+
import { VanillaTooltip } from './vanilla-tooltip';
|
|
11
|
+
// Based on platform/packages/design-system/icon/svgs/utility/add.svg
|
|
12
|
+
const plusButtonDOM = ['http://www.w3.org/2000/svg svg', {
|
|
13
|
+
width: '12',
|
|
14
|
+
height: '12',
|
|
15
|
+
fill: 'none',
|
|
16
|
+
viewBox: '0 0 12 12',
|
|
17
|
+
style: 'pointer-events: none;'
|
|
18
|
+
}, ['http://www.w3.org/2000/svg path', {
|
|
19
|
+
fill: 'currentcolor',
|
|
20
|
+
'fill-rule': 'evenodd',
|
|
21
|
+
d: 'M5.25 6.75V11h1.5V6.75H11v-1.5H6.75V1h-1.5v4.25H1v1.5z',
|
|
22
|
+
'clip-rule': 'evenodd',
|
|
23
|
+
style: 'pointer-events: none;'
|
|
24
|
+
}]];
|
|
25
|
+
const vanillaQuickInsert = ({
|
|
26
|
+
view,
|
|
27
|
+
getPos,
|
|
28
|
+
rootNodeType,
|
|
29
|
+
anchorRectCache,
|
|
30
|
+
anchorName,
|
|
31
|
+
rootAnchorName,
|
|
32
|
+
api
|
|
33
|
+
}) => {
|
|
34
|
+
var _api$featureFlags$sha, _api$featureFlags, _api$featureFlags$sha2;
|
|
35
|
+
return ['div', {
|
|
36
|
+
style: convertToInlineCss({
|
|
37
|
+
position: 'absolute',
|
|
38
|
+
...calculatePosition({
|
|
39
|
+
rootAnchorName,
|
|
40
|
+
anchorName,
|
|
41
|
+
view,
|
|
42
|
+
getPos,
|
|
43
|
+
rootNodeType: rootNodeType,
|
|
44
|
+
macroInteractionUpdates: (_api$featureFlags$sha = api === null || api === void 0 ? void 0 : (_api$featureFlags = api.featureFlags) === null || _api$featureFlags === void 0 ? void 0 : (_api$featureFlags$sha2 = _api$featureFlags.sharedState.currentState()) === null || _api$featureFlags$sha2 === void 0 ? void 0 : _api$featureFlags$sha2.macroInteractionUpdates) !== null && _api$featureFlags$sha !== void 0 ? _api$featureFlags$sha : false,
|
|
45
|
+
anchorRectCache
|
|
46
|
+
})
|
|
47
|
+
})
|
|
48
|
+
}, ['button', {
|
|
49
|
+
class: 'blocks-quick-insert-button',
|
|
50
|
+
'data-testid': 'editor-quick-insert-button'
|
|
51
|
+
}, plusButtonDOM]];
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Create a Node which contains the quick insert button
|
|
56
|
+
*/
|
|
57
|
+
export const createVanillaButton = props => {
|
|
58
|
+
var _props$api$typeAhead, _props$api$typeAhead$, _props$api$blockContr, _props$api$blockContr2, _props$api$typeAhead2, _props$api$blockContr3;
|
|
59
|
+
const {
|
|
60
|
+
dom
|
|
61
|
+
} = DOMSerializer.renderSpec(document, vanillaQuickInsert(props));
|
|
62
|
+
if (dom instanceof HTMLElement) {
|
|
63
|
+
const button = dom.querySelector('button[data-testid="editor-quick-insert-button"]');
|
|
64
|
+
if (button instanceof HTMLButtonElement) {
|
|
65
|
+
button.onclick = () => handleQuickInsert(props);
|
|
66
|
+
const tooltip = new VanillaTooltip(button, props.formatMessage(messages.insert), 'quick-insert-button-tooltip');
|
|
67
|
+
props.cleanupCallbacks.push(() => {
|
|
68
|
+
tooltip.destroy();
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Dynamically control the visibility of the node
|
|
74
|
+
let isTypeAheadOpen = (_props$api$typeAhead = props.api.typeAhead) === null || _props$api$typeAhead === void 0 ? void 0 : (_props$api$typeAhead$ = _props$api$typeAhead.sharedState.currentState()) === null || _props$api$typeAhead$ === void 0 ? void 0 : _props$api$typeAhead$.isOpen;
|
|
75
|
+
let isEditing = (_props$api$blockContr = props.api.blockControls) === null || _props$api$blockContr === void 0 ? void 0 : (_props$api$blockContr2 = _props$api$blockContr.sharedState.currentState()) === null || _props$api$blockContr2 === void 0 ? void 0 : _props$api$blockContr2.isEditing;
|
|
76
|
+
const changeDOMVisibility = () => {
|
|
77
|
+
if (!(dom instanceof HTMLElement)) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (isTypeAheadOpen || isEditing) {
|
|
81
|
+
dom.classList.add('blocks-quick-insert-invisible-container');
|
|
82
|
+
dom.classList.remove('blocks-quick-insert-visible-container');
|
|
83
|
+
} else {
|
|
84
|
+
dom.classList.add('blocks-quick-insert-visible-container');
|
|
85
|
+
dom.classList.remove('blocks-quick-insert-invisible-container');
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
changeDOMVisibility();
|
|
89
|
+
props.cleanupCallbacks.push((_props$api$typeAhead2 = props.api.typeAhead) === null || _props$api$typeAhead2 === void 0 ? void 0 : _props$api$typeAhead2.sharedState.onChange(({
|
|
90
|
+
nextSharedState
|
|
91
|
+
}) => {
|
|
92
|
+
isTypeAheadOpen = nextSharedState === null || nextSharedState === void 0 ? void 0 : nextSharedState.isOpen;
|
|
93
|
+
changeDOMVisibility();
|
|
94
|
+
}));
|
|
95
|
+
props.cleanupCallbacks.push((_props$api$blockContr3 = props.api.blockControls) === null || _props$api$blockContr3 === void 0 ? void 0 : _props$api$blockContr3.sharedState.onChange(({
|
|
96
|
+
nextSharedState
|
|
97
|
+
}) => {
|
|
98
|
+
isEditing = nextSharedState === null || nextSharedState === void 0 ? void 0 : nextSharedState.isEditing;
|
|
99
|
+
changeDOMVisibility();
|
|
100
|
+
}));
|
|
101
|
+
return dom;
|
|
102
|
+
};
|
|
103
|
+
const TEXT_PARENT_TYPES = ['paragraph', 'heading', 'blockquote', 'taskItem', 'decisionItem'];
|
|
104
|
+
const handleQuickInsert = ({
|
|
105
|
+
api,
|
|
106
|
+
view,
|
|
107
|
+
getPos
|
|
108
|
+
}) => {
|
|
109
|
+
var _api$quickInsert;
|
|
110
|
+
// if the selection is not within the node this decoration is rendered at
|
|
111
|
+
// then insert a newline and trigger quick insert
|
|
112
|
+
const start = getPos();
|
|
113
|
+
if (start !== undefined) {
|
|
114
|
+
// if the selection is not within the node this decoration is rendered at
|
|
115
|
+
// or the node is non-editable, then insert a newline and trigger quick insert
|
|
116
|
+
const isSelectionInsideNode = isSelectionInNode(start, view);
|
|
117
|
+
if (!isSelectionInsideNode || isNonEditableBlock(start, view)) {
|
|
118
|
+
api.core.actions.execute(createNewLine(start));
|
|
119
|
+
}
|
|
120
|
+
const {
|
|
121
|
+
codeBlock
|
|
122
|
+
} = view.state.schema.nodes;
|
|
123
|
+
const {
|
|
124
|
+
selection
|
|
125
|
+
} = view.state;
|
|
126
|
+
const codeBlockParentNode = findParentNodeOfType(codeBlock)(selection);
|
|
127
|
+
if (codeBlockParentNode) {
|
|
128
|
+
// Slash command is not meant to be triggered inside code block, hence always insert slash in a new line following
|
|
129
|
+
api.core.actions.execute(createNewLine(codeBlockParentNode.pos));
|
|
130
|
+
} else if (isSelectionInsideNode) {
|
|
131
|
+
// text or element with be deselected and the / added immediately after the paragraph
|
|
132
|
+
// unless the selection is empty
|
|
133
|
+
const currentSelection = view.state.selection;
|
|
134
|
+
if (isInTextSelection(view) && currentSelection.from !== currentSelection.to) {
|
|
135
|
+
const currentParagraphNode = findParentNode(node => TEXT_PARENT_TYPES.includes(node.type.name))(currentSelection);
|
|
136
|
+
if (currentParagraphNode) {
|
|
137
|
+
const newPos =
|
|
138
|
+
//if the current selection is selected from right to left, then set the selection to the start of the paragraph
|
|
139
|
+
currentSelection.anchor === currentSelection.to ? currentParagraphNode.pos : currentParagraphNode.pos + currentParagraphNode.node.nodeSize - 1;
|
|
140
|
+
api.core.actions.execute(({
|
|
141
|
+
tr
|
|
142
|
+
}) => {
|
|
143
|
+
tr.setSelection(TextSelection.create(view.state.selection.$from.doc, newPos));
|
|
144
|
+
return tr;
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (isNestedNodeSelected(view)) {
|
|
149
|
+
// if the nested selected node is non-editable, then insert a newline below the selected node
|
|
150
|
+
if (isNonEditableBlock(view.state.selection.from, view)) {
|
|
151
|
+
api.core.actions.execute(createNewLine(view.state.selection.from));
|
|
152
|
+
} else {
|
|
153
|
+
// otherwise need to force the selection to be at the start of the node, because
|
|
154
|
+
// prosemirror is keeping it as NodeSelection for nested nodes. Do this to keep it
|
|
155
|
+
// consistent NodeSelection for root level nodes.
|
|
156
|
+
api.core.actions.execute(({
|
|
157
|
+
tr
|
|
158
|
+
}) => {
|
|
159
|
+
createNewLine(view.state.selection.from)({
|
|
160
|
+
tr
|
|
161
|
+
});
|
|
162
|
+
tr.setSelection(TextSelection.create(tr.doc, view.state.selection.from));
|
|
163
|
+
return tr;
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (currentSelection instanceof CellSelection) {
|
|
168
|
+
// find the last inline position in the selection
|
|
169
|
+
const lastInlinePosition = TextSelection.near(view.state.selection.$to, -1);
|
|
170
|
+
lastInlinePosition && api.core.actions.execute(({
|
|
171
|
+
tr
|
|
172
|
+
}) => {
|
|
173
|
+
if (!(lastInlinePosition instanceof TextSelection)) {
|
|
174
|
+
// this will create a new line after the node
|
|
175
|
+
createNewLine(lastInlinePosition.from)({
|
|
176
|
+
tr
|
|
177
|
+
});
|
|
178
|
+
// this will find the next valid text position after the node
|
|
179
|
+
tr.setSelection(TextSelection.create(tr.doc, lastInlinePosition.to));
|
|
180
|
+
} else {
|
|
181
|
+
tr.setSelection(lastInlinePosition);
|
|
182
|
+
}
|
|
183
|
+
return tr;
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
(_api$quickInsert = api.quickInsert) === null || _api$quickInsert === void 0 ? void 0 : _api$quickInsert.actions.openTypeAhead('blockControl', true);
|
|
189
|
+
};
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
2
|
+
import { createPopper } from '@popperjs/core';
|
|
3
|
+
import { bind } from 'bind-event-listener';
|
|
4
|
+
const startingOffset = {
|
|
5
|
+
name: 'offset',
|
|
6
|
+
options: {
|
|
7
|
+
offset: [0, 4]
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
const endingOffset = {
|
|
11
|
+
name: 'offset',
|
|
12
|
+
options: {
|
|
13
|
+
offset: [0, 8]
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* A tooltip component similar to "@atlaskit/tooltip" but built for vanilla scenarios
|
|
19
|
+
*
|
|
20
|
+
* Uses Popover API for accessibility + stacking context: https://developer.mozilla.org/en-US/docs/Web/API/Popover_API
|
|
21
|
+
* Uses popperJS for positioning
|
|
22
|
+
*
|
|
23
|
+
* @warning Still experimental. One day we can likely want to move this to a common package.
|
|
24
|
+
*/
|
|
25
|
+
export class VanillaTooltip {
|
|
26
|
+
constructor(button, content,
|
|
27
|
+
/**
|
|
28
|
+
* Id associated to the tooltip - must be unique.
|
|
29
|
+
*/
|
|
30
|
+
id, timeout = 300) {
|
|
31
|
+
_defineProperty(this, "listeners", []);
|
|
32
|
+
_defineProperty(this, "shouldHidePopover", false);
|
|
33
|
+
_defineProperty(this, "isDisplayed", false);
|
|
34
|
+
this.button = button;
|
|
35
|
+
this.timeout = timeout;
|
|
36
|
+
const tooltip = document.createElement('span');
|
|
37
|
+
tooltip.role = 'tooltip';
|
|
38
|
+
tooltip.popover = 'hint';
|
|
39
|
+
// Warning: Currently this is used for styling - only works in the block controls package
|
|
40
|
+
tooltip.className = 'blocks-quick-insert-tooltip';
|
|
41
|
+
tooltip.id = id;
|
|
42
|
+
tooltip.textContent = content;
|
|
43
|
+
this.tooltip = tooltip;
|
|
44
|
+
|
|
45
|
+
// Button preparation
|
|
46
|
+
button.appendChild(tooltip);
|
|
47
|
+
// Prepare the button to have the popover target and accessibility properties
|
|
48
|
+
button.setAttribute('popovertarget', tooltip.id);
|
|
49
|
+
button.setAttribute('aria-describedby', tooltip.id);
|
|
50
|
+
const showEvents = ['mouseenter', 'focus'];
|
|
51
|
+
const hideEvents = ['mouseleave', 'blur'];
|
|
52
|
+
showEvents.forEach(event => {
|
|
53
|
+
this.listeners.push(bind(button, {
|
|
54
|
+
type: event,
|
|
55
|
+
listener: () => this.show()
|
|
56
|
+
}));
|
|
57
|
+
});
|
|
58
|
+
hideEvents.forEach(event => {
|
|
59
|
+
this.listeners.push(bind(button, {
|
|
60
|
+
type: event,
|
|
61
|
+
listener: () => this.hide()
|
|
62
|
+
}));
|
|
63
|
+
});
|
|
64
|
+
this.listeners.push(bind(window, {
|
|
65
|
+
type: 'keydown',
|
|
66
|
+
listener: e => {
|
|
67
|
+
if (e.key === 'Escape') {
|
|
68
|
+
this.hide(true);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}));
|
|
72
|
+
|
|
73
|
+
// Hide the tooltip if the hide transition has completed
|
|
74
|
+
this.tooltip.ontransitionend = () => {
|
|
75
|
+
if (this.shouldHidePopover) {
|
|
76
|
+
this.tooltip.hidePopover();
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
createPopperInstance() {
|
|
81
|
+
this.popperInstance = createPopper(this.button, this.tooltip, {
|
|
82
|
+
placement: 'top',
|
|
83
|
+
modifiers: [startingOffset]
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
destroy() {
|
|
87
|
+
var _this$popperInstance;
|
|
88
|
+
(_this$popperInstance = this.popperInstance) === null || _this$popperInstance === void 0 ? void 0 : _this$popperInstance.destroy();
|
|
89
|
+
this.listeners.forEach(listener => {
|
|
90
|
+
listener();
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
hide(immediate = false) {
|
|
94
|
+
clearTimeout(this.currentTimeoutId);
|
|
95
|
+
this.shouldHidePopover = true;
|
|
96
|
+
// Disable the event listeners
|
|
97
|
+
this.currentTimeoutId = setTimeout(() => {
|
|
98
|
+
var _this$popperInstance2;
|
|
99
|
+
(_this$popperInstance2 = this.popperInstance) === null || _this$popperInstance2 === void 0 ? void 0 : _this$popperInstance2.setOptions(options => ({
|
|
100
|
+
...options,
|
|
101
|
+
modifiers: [startingOffset, {
|
|
102
|
+
name: 'eventListeners',
|
|
103
|
+
enabled: false
|
|
104
|
+
}]
|
|
105
|
+
}));
|
|
106
|
+
this.tooltip.style.opacity = '0';
|
|
107
|
+
this.isDisplayed = false;
|
|
108
|
+
// If transition animations are disabled immediately hide the popover
|
|
109
|
+
if (this.tooltip.style.transition === 'none') {
|
|
110
|
+
this.tooltip.hidePopover();
|
|
111
|
+
}
|
|
112
|
+
}, immediate ? 0 : this.timeout);
|
|
113
|
+
}
|
|
114
|
+
show() {
|
|
115
|
+
if (this.isDisplayed) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
clearTimeout(this.currentTimeoutId);
|
|
119
|
+
this.shouldHidePopover = false;
|
|
120
|
+
|
|
121
|
+
// Make the tooltip visible - but hide until
|
|
122
|
+
this.tooltip.style.visibility = 'hidden';
|
|
123
|
+
this.tooltip.showPopover();
|
|
124
|
+
|
|
125
|
+
// Update its position
|
|
126
|
+
if (!this.popperInstance) {
|
|
127
|
+
this.createPopperInstance();
|
|
128
|
+
} else {
|
|
129
|
+
this.popperInstance.update();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Enable the event listeners
|
|
133
|
+
this.currentTimeoutId = setTimeout(() => {
|
|
134
|
+
var _this$popperInstance3;
|
|
135
|
+
this.tooltip.style.opacity = '1';
|
|
136
|
+
this.tooltip.style.visibility = 'visible';
|
|
137
|
+
(_this$popperInstance3 = this.popperInstance) === null || _this$popperInstance3 === void 0 ? void 0 : _this$popperInstance3.setOptions(options => ({
|
|
138
|
+
...options,
|
|
139
|
+
modifiers: [endingOffset, {
|
|
140
|
+
name: 'eventListeners',
|
|
141
|
+
enabled: true
|
|
142
|
+
}]
|
|
143
|
+
}));
|
|
144
|
+
this.isDisplayed = true;
|
|
145
|
+
}, this.timeout);
|
|
146
|
+
}
|
|
147
|
+
}
|