@atlaskit/editor-common 86.0.0 → 86.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 +33 -0
- package/dist/cjs/lazy-node-view/index.js +217 -0
- package/dist/cjs/link/ConfigureLinkOverlay/Dropdown.js +19 -4
- package/dist/cjs/link/ConfigureLinkOverlay/index.js +2 -1
- package/dist/cjs/messages/help-dialog.js +5 -0
- package/dist/cjs/monitoring/error.js +1 -1
- package/dist/cjs/ui/DropList/index.js +1 -1
- package/dist/cjs/ui/WidthProvider/index.js +18 -2
- package/dist/cjs/ui-menu/DropdownMenu/index.js +4 -4
- package/dist/cjs/ui-menu/ToolbarButton/index.js +7 -1
- package/dist/cjs/utils/index.js +7 -0
- package/dist/cjs/utils/page-element-counts.js +44 -0
- package/dist/es2019/lazy-node-view/index.js +212 -0
- package/dist/es2019/link/ConfigureLinkOverlay/Dropdown.js +17 -4
- package/dist/es2019/link/ConfigureLinkOverlay/index.js +2 -1
- package/dist/es2019/messages/help-dialog.js +5 -0
- package/dist/es2019/monitoring/error.js +1 -1
- package/dist/es2019/ui/DropList/index.js +1 -1
- package/dist/es2019/ui/WidthProvider/index.js +18 -2
- package/dist/es2019/ui-menu/DropdownMenu/index.js +4 -4
- package/dist/es2019/ui-menu/ToolbarButton/index.js +7 -1
- package/dist/es2019/utils/index.js +2 -1
- package/dist/es2019/utils/page-element-counts.js +38 -0
- package/dist/esm/lazy-node-view/index.js +211 -0
- package/dist/esm/link/ConfigureLinkOverlay/Dropdown.js +19 -4
- package/dist/esm/link/ConfigureLinkOverlay/index.js +2 -1
- package/dist/esm/messages/help-dialog.js +5 -0
- package/dist/esm/monitoring/error.js +1 -1
- package/dist/esm/ui/DropList/index.js +1 -1
- package/dist/esm/ui/WidthProvider/index.js +18 -2
- package/dist/esm/ui-menu/DropdownMenu/index.js +4 -4
- package/dist/esm/ui-menu/ToolbarButton/index.js +7 -1
- package/dist/esm/utils/index.js +2 -1
- package/dist/esm/utils/page-element-counts.js +38 -0
- package/dist/types/lazy-node-view/index.d.ts +109 -0
- package/dist/types/link/ConfigureLinkOverlay/Dropdown.d.ts +2 -0
- package/dist/types/messages/help-dialog.d.ts +5 -0
- package/dist/types/ui-menu/ToolbarButton/index.d.ts +3 -0
- package/dist/types/utils/index.d.ts +1 -0
- package/dist/types/utils/page-element-counts.d.ts +17 -0
- package/dist/types-ts4.5/lazy-node-view/index.d.ts +109 -0
- package/dist/types-ts4.5/link/ConfigureLinkOverlay/Dropdown.d.ts +2 -0
- package/dist/types-ts4.5/messages/help-dialog.d.ts +5 -0
- package/dist/types-ts4.5/ui-menu/ToolbarButton/index.d.ts +3 -0
- package/dist/types-ts4.5/utils/index.d.ts +1 -0
- package/dist/types-ts4.5/utils/page-element-counts.d.ts +17 -0
- package/lazy-node-view/package.json +15 -0
- package/package.json +4 -3
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import debounce from 'lodash/debounce';
|
|
2
|
+
import { DOMSerializer } from '@atlaskit/editor-prosemirror/model';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 📢 Public Type
|
|
6
|
+
*
|
|
7
|
+
* @see {withLazyLoading}
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 📢 Public Type
|
|
12
|
+
*
|
|
13
|
+
* @see {withLazyLoading}
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 🧱 Internal: Editor FE Platform
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 🧱 Internal: Editor FE Platform
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 🧱 Internal: Editor FE Platform
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 🧱 Internal: Editor FE Platform
|
|
30
|
+
*
|
|
31
|
+
* A cache to store loaded React NodeViews for each EditorView.
|
|
32
|
+
*
|
|
33
|
+
* This cache will help us to avoid any race condition
|
|
34
|
+
* when multiple Editors exist in the same page.
|
|
35
|
+
*
|
|
36
|
+
* @type {CacheType}
|
|
37
|
+
*/
|
|
38
|
+
const cachePerEditorView = new WeakMap();
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 🧱 Internal: Editor FE Platform
|
|
42
|
+
*/
|
|
43
|
+
const isFirefox = /gecko\/\d/i.test(navigator.userAgent);
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 🧱 Internal: Editor FE Platform
|
|
47
|
+
*
|
|
48
|
+
* A NodeView that serves as a placeholder until the actual NodeView is loaded.
|
|
49
|
+
*/
|
|
50
|
+
class LazyNodeView {
|
|
51
|
+
constructor(node, view, getPos) {
|
|
52
|
+
var _node$type, _node$type$spec;
|
|
53
|
+
if (typeof ((_node$type = node.type) === null || _node$type === void 0 ? void 0 : (_node$type$spec = _node$type.spec) === null || _node$type$spec === void 0 ? void 0 : _node$type$spec.toDOM) !== 'function') {
|
|
54
|
+
this.dom = document.createElement('div');
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const fallback = DOMSerializer.renderSpec(document, node.type.spec.toDOM(node));
|
|
58
|
+
this.dom = fallback.dom;
|
|
59
|
+
this.contentDOM = fallback.contentDOM;
|
|
60
|
+
if (this.dom instanceof HTMLElement) {
|
|
61
|
+
// This attribute is mostly used for debugging purposed
|
|
62
|
+
// It will help us to found out when the node was replaced
|
|
63
|
+
this.dom.setAttribute('data-lazy-node-view', node.type.name);
|
|
64
|
+
// This is used on Libra tests
|
|
65
|
+
// We are using this to make sure all lazy noded were replaced
|
|
66
|
+
// before the test started
|
|
67
|
+
this.dom.setAttribute('data-lazy-node-view-fallback', 'true');
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* 🧱 Internal: Editor FE Platform
|
|
74
|
+
*
|
|
75
|
+
* Debounces and replaces the node views in a ProseMirror editor with lazy-loaded node views.
|
|
76
|
+
*
|
|
77
|
+
* This function is used to update the `nodeViews` property of the `EditorView` after lazy-loaded
|
|
78
|
+
* node views have been loaded. It uses a debounced approach to ensure that the replacement does
|
|
79
|
+
* not happen too frequently, which can be performance-intensive.
|
|
80
|
+
*
|
|
81
|
+
* The function checks if there are any loaded node views in the cache associated with the given
|
|
82
|
+
* `EditorView`. If there are, it replaces the current `nodeViews` in the `EditorView` with the
|
|
83
|
+
* loaded node views. The replacement is scheduled using `requestIdleCallback` or
|
|
84
|
+
* `requestAnimationFrame` to avoid blocking the main thread, especially in Firefox where
|
|
85
|
+
* `requestIdleCallback` may not be supported.
|
|
86
|
+
*
|
|
87
|
+
* @param {WeakMap<EditorView, Record<string, NodeViewConstructor>>} cache - A WeakMap that stores
|
|
88
|
+
* the loaded node views for each `EditorView`. The key is the `EditorView`, and the value
|
|
89
|
+
* is a record of node type names to their corresponding `NodeViewConstructor`.
|
|
90
|
+
* @param {EditorView} view - The ProseMirror `EditorView` instance whose `nodeViews` property
|
|
91
|
+
* needs to be updated.
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* const cache = new WeakMap();
|
|
95
|
+
* const view = new EditorView(...);
|
|
96
|
+
*
|
|
97
|
+
* // Assume some node views have been loaded and stored in the cache
|
|
98
|
+
* cache.set(view, {
|
|
99
|
+
* 'table': TableViewConstructor,
|
|
100
|
+
* 'tableCell': TableCellViewConstructor,
|
|
101
|
+
* });
|
|
102
|
+
*
|
|
103
|
+
* debouncedReplaceNodeviews(cache, view);
|
|
104
|
+
*/
|
|
105
|
+
export const debouncedReplaceNodeviews = debounce((cache, view) => {
|
|
106
|
+
const loadedNodeviews = cache.get(view);
|
|
107
|
+
if (!loadedNodeviews) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
cache.delete(view);
|
|
111
|
+
|
|
112
|
+
// eslint-disable-next-line compat/compat
|
|
113
|
+
const idle = window.requestIdleCallback;
|
|
114
|
+
|
|
115
|
+
/*
|
|
116
|
+
* For reasons that goes beyond my knowledge
|
|
117
|
+
* some Firefox versions aren't calling the requestIdleCallback.
|
|
118
|
+
*
|
|
119
|
+
* So, we need this check to make sure we use the requestAnimationFrame instead
|
|
120
|
+
*/
|
|
121
|
+
const later = isFirefox || typeof idle !== 'function' ? window.requestAnimationFrame : idle;
|
|
122
|
+
later(() => {
|
|
123
|
+
const currentNodeViews = view.props.nodeViews;
|
|
124
|
+
const nextNodeViews = {
|
|
125
|
+
...currentNodeViews,
|
|
126
|
+
...loadedNodeviews
|
|
127
|
+
};
|
|
128
|
+
view.setProps({
|
|
129
|
+
nodeViews: nextNodeViews
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* 📢 Public: Any EditorPlugin can use this function
|
|
136
|
+
*
|
|
137
|
+
* Wraps a NodeView constructor with laziness, allowing the NodeView to be loaded only when required.
|
|
138
|
+
*
|
|
139
|
+
* This higher-order function is designed to optimize the loading and rendering performance
|
|
140
|
+
* of ProseMirror editor nodes by deferring the loading of their associated NodeViews until they are actually needed.
|
|
141
|
+
* This is particularly useful for complex or heavy NodeViews, such as tables, table cells, rows, and headers within
|
|
142
|
+
* the ProseMirror editor. By using dynamic imports (with promises), the initial load time of the editor can be significantly
|
|
143
|
+
* reduced, leading to a smoother and faster user experience.
|
|
144
|
+
*
|
|
145
|
+
* The function accepts configuration parameters including the node name, a loader function that dynamically imports
|
|
146
|
+
* the NodeView, and a function to retrieve NodeView options. It returns a NodeViewConstructor that ProseMirror
|
|
147
|
+
* can use when rendering nodes of the specified type.
|
|
148
|
+
*
|
|
149
|
+
* @template NodeViewOptions - The type parameter that describes the shape of the options object for the NodeView.
|
|
150
|
+
* @param {LazyLoadingProps<NodeViewOptions>} params - Configuration parameters for lazy loading.
|
|
151
|
+
* @param {string} params.nodeName - The name of the node (e.g., 'table', 'tableCell', 'tableHeader', 'tableRow') for which the lazy-loaded NodeView is intended.
|
|
152
|
+
* @param {() => Promise<CreateReactNodeViewProps<NodeViewOptions>>} params.loader - A function that, when called, returns a promise that resolves to the actual NodeView constructor. This function typically uses dynamic `import()` to load the NodeView code.
|
|
153
|
+
* @param {() => NodeViewOptions} params.getNodeViewOptions - A function that returns the options to be passed to the NodeView constructor. These options can include dependencies like `portalProviderAPI`, `eventDispatcher`, and others, which are necessary for the NodeView's operation.
|
|
154
|
+
* @param {DispatchAnalyticsEvent} [params.dispatchAnalyticsEvent] - An optional function for dispatching analytics events, which can be used to monitor the performance and usage of the lazy-loaded NodeViews.
|
|
155
|
+
* @returns {NodeViewConstructor} A constructor function for creating a NodeView that ProseMirror can instantiate when it encounters a node of the specified type. This constructor is a lightweight placeholder until the actual NodeView is loaded.
|
|
156
|
+
*
|
|
157
|
+
* @example
|
|
158
|
+
* // Lazy load a table NodeView with specific options
|
|
159
|
+
* const lazyTableView = withLazyLoading({
|
|
160
|
+
* nodeName: 'table',
|
|
161
|
+
* loader: () => import('./table').then(module => module.createTableView),
|
|
162
|
+
* getNodeViewOptions: () => ({
|
|
163
|
+
* portalProviderAPI,
|
|
164
|
+
* eventDispatcher,
|
|
165
|
+
* getEditorContainerWidth,
|
|
166
|
+
* getEditorFeatureFlags,
|
|
167
|
+
* dispatchAnalyticsEvent,
|
|
168
|
+
* pluginInjectionApi,
|
|
169
|
+
* }),
|
|
170
|
+
* });
|
|
171
|
+
*
|
|
172
|
+
* // Then, use `lazyTableView` in ProseMirror editor setup to enhance 'table' nodes with lazy loading
|
|
173
|
+
*/
|
|
174
|
+
export const withLazyLoading = ({
|
|
175
|
+
nodeName,
|
|
176
|
+
loader,
|
|
177
|
+
getNodeViewOptions,
|
|
178
|
+
dispatchAnalyticsEvent
|
|
179
|
+
}) => {
|
|
180
|
+
const createLazyNodeView = (node, view, getPos, decorations) => {
|
|
181
|
+
var _node$type2, _node$type2$spec;
|
|
182
|
+
let cachedMap = cachePerEditorView.get(view);
|
|
183
|
+
if (!cachedMap) {
|
|
184
|
+
cachedMap = {};
|
|
185
|
+
cachePerEditorView.set(view, cachedMap);
|
|
186
|
+
}
|
|
187
|
+
const wasAlreadyRequested = cachedMap.hasOwnProperty(nodeName);
|
|
188
|
+
if (wasAlreadyRequested) {
|
|
189
|
+
return new LazyNodeView(node, view, getPos);
|
|
190
|
+
}
|
|
191
|
+
loader().then(nodeViewFuncModule => {
|
|
192
|
+
cachedMap[nodeName] = (node, view, getPos, decorations) => {
|
|
193
|
+
return nodeViewFuncModule(node, view, getPos, decorations, getNodeViewOptions);
|
|
194
|
+
};
|
|
195
|
+
debouncedReplaceNodeviews(cachePerEditorView, view);
|
|
196
|
+
});
|
|
197
|
+
if (typeof ((_node$type2 = node.type) === null || _node$type2 === void 0 ? void 0 : (_node$type2$spec = _node$type2.spec) === null || _node$type2$spec === void 0 ? void 0 : _node$type2$spec.toDOM) !== 'function') {
|
|
198
|
+
// TODO: Analytics ED-23982
|
|
199
|
+
// dispatchAnalyticsEvent({
|
|
200
|
+
// action: ACTION.LAZY_NODE_VIEW_ERROR,
|
|
201
|
+
// actionSubject: ACTION_SUBJECT.LAZY_NODE_VIEW,
|
|
202
|
+
// eventType: EVENT_TYPE.OPERATIONAL,
|
|
203
|
+
// attributes: {
|
|
204
|
+
// nodeName,
|
|
205
|
+
// error: 'No spec found',
|
|
206
|
+
// },
|
|
207
|
+
// });
|
|
208
|
+
}
|
|
209
|
+
return new LazyNodeView(node, view, getPos);
|
|
210
|
+
};
|
|
211
|
+
return createLazyNodeView;
|
|
212
|
+
};
|
|
@@ -17,6 +17,7 @@ const SMALL_LINK_TOOLBAR_ANALYTICS_SOURCE = 'smallLinkToolbar';
|
|
|
17
17
|
const Dropdown = ({
|
|
18
18
|
onConfigureClick: onConfigureClickCallback,
|
|
19
19
|
onDropdownChange,
|
|
20
|
+
editorView,
|
|
20
21
|
testId
|
|
21
22
|
}) => {
|
|
22
23
|
const {
|
|
@@ -29,21 +30,33 @@ const Dropdown = ({
|
|
|
29
30
|
fireLinkClickEvent,
|
|
30
31
|
fireToolbarViewEvent
|
|
31
32
|
} = useLinkOverlayAnalyticsEvents();
|
|
33
|
+
const focusEditor = useCallback(() => {
|
|
34
|
+
// Fix dropdown giving focus back to the trigger async which is then unmounted and losing focus
|
|
35
|
+
// this is happening deep within atlaskit dropdown as a result of this code: https://github.com/focus-trap/focus-trap/blob/master/index.js#L987
|
|
36
|
+
// use setTimeout to run this async after that call
|
|
37
|
+
setTimeout(() => editorView.focus(), 0);
|
|
38
|
+
}, [editorView]);
|
|
32
39
|
const onOpenChange = useCallback(({
|
|
33
|
-
isOpen
|
|
40
|
+
isOpen,
|
|
41
|
+
event
|
|
34
42
|
}) => {
|
|
35
43
|
onDropdownChange === null || onDropdownChange === void 0 ? void 0 : onDropdownChange(isOpen);
|
|
36
44
|
if (isOpen) {
|
|
37
45
|
fireToolbarViewEvent();
|
|
38
46
|
}
|
|
39
|
-
|
|
47
|
+
if (!isOpen && event instanceof KeyboardEvent) {
|
|
48
|
+
focusEditor();
|
|
49
|
+
}
|
|
50
|
+
}, [fireToolbarViewEvent, focusEditor, onDropdownChange]);
|
|
40
51
|
const onGoToLinkClick = useCallback(() => {
|
|
41
52
|
fireActionClickEvent('goToLink');
|
|
42
|
-
|
|
53
|
+
focusEditor();
|
|
54
|
+
}, [fireActionClickEvent, focusEditor]);
|
|
43
55
|
const onConfigureClick = useCallback(() => {
|
|
44
56
|
fireActionClickEvent('configureLink');
|
|
45
57
|
onConfigureClickCallback === null || onConfigureClickCallback === void 0 ? void 0 : onConfigureClickCallback();
|
|
46
|
-
|
|
58
|
+
focusEditor();
|
|
59
|
+
}, [fireActionClickEvent, focusEditor, onConfigureClickCallback]);
|
|
47
60
|
return jsx(DropdownMenu, {
|
|
48
61
|
trigger: ({
|
|
49
62
|
onClick,
|
|
@@ -86,7 +86,8 @@ export const OverlayButton = withAnalyticsContext()(({
|
|
|
86
86
|
}, showDropdown ? jsx(Dropdown, {
|
|
87
87
|
testId: testId,
|
|
88
88
|
onConfigureClick: handleConfigureClick,
|
|
89
|
-
onDropdownChange: onDropdownChange
|
|
89
|
+
onDropdownChange: onDropdownChange,
|
|
90
|
+
editorView: editorView
|
|
90
91
|
}) : jsx(Tooltip, {
|
|
91
92
|
content: configureLinkLabel,
|
|
92
93
|
hideTooltipOnClick: true,
|
|
@@ -70,6 +70,11 @@ export const helpDialogMessages = defineMessages({
|
|
|
70
70
|
defaultMessage: 'Decrease table or media size',
|
|
71
71
|
description: 'The text is shown as an shortcut description in help dialog modal, when the user uses the described shortcut, he is able to decrease the width of the selected element. Optimal characters less than 21.'
|
|
72
72
|
},
|
|
73
|
+
openCellOptions: {
|
|
74
|
+
id: 'fabric.editor.openCellOptions',
|
|
75
|
+
defaultMessage: 'Open cell options',
|
|
76
|
+
description: 'Keyboard shortcut to open cell options.'
|
|
77
|
+
},
|
|
73
78
|
focusTableResizeHandle: {
|
|
74
79
|
id: 'fabric.editor.focusTableResizeHandle',
|
|
75
80
|
defaultMessage: 'Focus table resize handle',
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { isFedRamp } from './environment';
|
|
2
2
|
const SENTRY_DSN = 'https://0b10c8e02fb44d8796c047b102c9bee8@o55978.ingest.sentry.io/4505129224110080';
|
|
3
3
|
const packageName = 'editor-common'; // Sentry doesn't accept '/' in its releases https://docs.sentry.io/platforms/javascript/configuration/releases/
|
|
4
|
-
const packageVersion = "86.
|
|
4
|
+
const packageVersion = "86.2.0";
|
|
5
5
|
const sanitiseSentryEvents = (data, _hint) => {
|
|
6
6
|
// Remove URL as it has UGC
|
|
7
7
|
// TODO: Sanitise the URL instead of just removing it
|
|
@@ -9,7 +9,7 @@ import { createAndFireEvent, withAnalyticsContext, withAnalyticsEvents } from '@
|
|
|
9
9
|
import { N0, N50A, N60A, N900 } from '@atlaskit/theme/colors';
|
|
10
10
|
import Layer from '../Layer';
|
|
11
11
|
const packageName = "@atlaskit/editor-common";
|
|
12
|
-
const packageVersion = "86.
|
|
12
|
+
const packageVersion = "86.2.0";
|
|
13
13
|
const halfFocusRing = 1;
|
|
14
14
|
const dropOffset = '0, 8';
|
|
15
15
|
class DropList extends Component {
|
|
@@ -3,6 +3,7 @@ import React, { Fragment } from 'react';
|
|
|
3
3
|
|
|
4
4
|
// eslint-disable-next-line @atlaskit/ui-styling-standard/use-compiled -- Ignored via go/DSP-18766
|
|
5
5
|
import { css, jsx } from '@emotion/react';
|
|
6
|
+
import memoizeOne from 'memoize-one';
|
|
6
7
|
import rafSchedule from 'raf-schd';
|
|
7
8
|
import { WidthObserver } from '@atlaskit/width-detector';
|
|
8
9
|
const styles = css({
|
|
@@ -31,14 +32,29 @@ const {
|
|
|
31
32
|
Provider,
|
|
32
33
|
Consumer
|
|
33
34
|
} = WidthContext;
|
|
35
|
+
/**
|
|
36
|
+
* 🧱 Internal function: Editor FE Platform
|
|
37
|
+
*
|
|
38
|
+
* Returns the width of the document body.
|
|
39
|
+
*
|
|
40
|
+
* This function is memoized to avoid forcing a layout reflow multiple times.
|
|
41
|
+
* It uses `document.body.offsetWidth` as the source of the width, which can lead to
|
|
42
|
+
* a layout reflow if accessed repeatedly. To mitigate performance issues, the result
|
|
43
|
+
* is cached using `memoizeOne`.
|
|
44
|
+
*
|
|
45
|
+
* @returns {number} The width of the document body or 0 if the document is undefined.
|
|
46
|
+
*/
|
|
47
|
+
const getBodyWidth = memoizeOne(() => {
|
|
48
|
+
var _document$body$offset, _document$body;
|
|
49
|
+
return typeof document !== 'undefined' ? (_document$body$offset = (_document$body = document.body) === null || _document$body === void 0 ? void 0 : _document$body.offsetWidth) !== null && _document$body$offset !== void 0 ? _document$body$offset : 0 : 0;
|
|
50
|
+
});
|
|
34
51
|
export const WidthProvider = ({
|
|
35
52
|
className,
|
|
36
53
|
shouldCheckExistingValue,
|
|
37
54
|
children
|
|
38
55
|
}) => {
|
|
39
|
-
var _document$body$offset, _document$body;
|
|
40
56
|
const existingContextValue = React.useContext(WidthContext);
|
|
41
|
-
const [width, setWidth] = React.useState(
|
|
57
|
+
const [width, setWidth] = React.useState(getBodyWidth);
|
|
42
58
|
const widthRef = React.useRef(width);
|
|
43
59
|
const isMountedRef = React.useRef(true);
|
|
44
60
|
const providerValue = React.useMemo(() => createWidthContext(width), [width]);
|
|
@@ -106,7 +106,7 @@ export default class DropdownMenuWrapper extends PureComponent {
|
|
|
106
106
|
_defineProperty(this, "handleCloseAndFocus", event => {
|
|
107
107
|
var _this$state$target, _this$state$target$qu;
|
|
108
108
|
(_this$state$target = this.state.target) === null || _this$state$target === void 0 ? void 0 : (_this$state$target$qu = _this$state$target.querySelector('button')) === null || _this$state$target$qu === void 0 ? void 0 : _this$state$target$qu.focus();
|
|
109
|
-
if (fg('
|
|
109
|
+
if (fg('platform_editor_a11y_table_context_menu')) {
|
|
110
110
|
this.handleClose(event);
|
|
111
111
|
} else {
|
|
112
112
|
this.handleClose();
|
|
@@ -117,7 +117,7 @@ export default class DropdownMenuWrapper extends PureComponent {
|
|
|
117
117
|
onOpenChange
|
|
118
118
|
} = this.props;
|
|
119
119
|
if (onOpenChange) {
|
|
120
|
-
if (fg('
|
|
120
|
+
if (fg('platform_editor_a11y_table_context_menu')) {
|
|
121
121
|
onOpenChange({
|
|
122
122
|
isOpen: false,
|
|
123
123
|
event: event
|
|
@@ -195,7 +195,7 @@ export default class DropdownMenuWrapper extends PureComponent {
|
|
|
195
195
|
handleClickOutside: this.handleClose,
|
|
196
196
|
handleEscapeKeydown: fg('platform-editor-a11y-image-border-options-dropdown') ? handleEscapeKeydown || this.handleCloseAndFocus : this.handleCloseAndFocus,
|
|
197
197
|
handleEnterKeydown: e => {
|
|
198
|
-
if (fg('
|
|
198
|
+
if (fg('platform_editor_a11y_table_context_menu') || fg('platform-editor-a11y-image-border-options-dropdown')) {
|
|
199
199
|
if (!allowEnterDefaultBehavior) {
|
|
200
200
|
e.preventDefault();
|
|
201
201
|
}
|
|
@@ -353,7 +353,7 @@ export function DropdownMenuItem({
|
|
|
353
353
|
onMouseLeave: () => onMouseLeave && onMouseLeave({
|
|
354
354
|
item
|
|
355
355
|
}),
|
|
356
|
-
"aria-expanded": fg('
|
|
356
|
+
"aria-expanded": fg('platform_editor_a11y_table_context_menu') ? item['aria-expanded'] : undefined
|
|
357
357
|
}, item.content));
|
|
358
358
|
if (item.tooltipDescription) {
|
|
359
359
|
var _item$key3;
|
|
@@ -6,8 +6,10 @@ import React, { useCallback } from 'react';
|
|
|
6
6
|
// eslint-disable-next-line @atlaskit/ui-styling-standard/use-compiled -- Ignored via go/DSP-18766
|
|
7
7
|
import { css, jsx } from '@emotion/react';
|
|
8
8
|
import { FabricChannel } from '@atlaskit/analytics-listeners';
|
|
9
|
+
import { fg } from '@atlaskit/platform-feature-flags';
|
|
9
10
|
import Tooltip from '@atlaskit/tooltip';
|
|
10
11
|
import { ACTION, ACTION_SUBJECT, EVENT_TYPE, TOOLBAR_ACTION_SUBJECT_ID } from '../../analytics';
|
|
12
|
+
import { ToolTipContent } from '../../keymaps';
|
|
11
13
|
import Button from './styles';
|
|
12
14
|
export const TOOLBAR_BUTTON = TOOLBAR_ACTION_SUBJECT_ID;
|
|
13
15
|
const buttonWrapper = css({
|
|
@@ -29,6 +31,7 @@ const ToolbarButton = /*#__PURE__*/React.forwardRef((props, ref) => {
|
|
|
29
31
|
children,
|
|
30
32
|
hideTooltip,
|
|
31
33
|
title,
|
|
34
|
+
keymap,
|
|
32
35
|
titlePosition = 'top',
|
|
33
36
|
item,
|
|
34
37
|
'aria-label': ariaLabel,
|
|
@@ -88,7 +91,10 @@ const ToolbarButton = /*#__PURE__*/React.forwardRef((props, ref) => {
|
|
|
88
91
|
if (!title) {
|
|
89
92
|
return button;
|
|
90
93
|
}
|
|
91
|
-
const tooltipContent =
|
|
94
|
+
const tooltipContent = hideTooltip ? null : fg('platform_editor_a11y_table_context_menu') ? jsx(ToolTipContent, {
|
|
95
|
+
description: title,
|
|
96
|
+
keymap: keymap
|
|
97
|
+
}) : title;
|
|
92
98
|
return jsx(Tooltip, {
|
|
93
99
|
content: tooltipContent,
|
|
94
100
|
hideTooltipOnClick: true,
|
|
@@ -221,4 +221,5 @@ export { transformNodeIntoListItem } from './insert-node-into-ordered-list';
|
|
|
221
221
|
export { wrapSelectionIn } from './wrap-selection-in';
|
|
222
222
|
export { toJSON, nodeToJSON } from './nodes';
|
|
223
223
|
export { calculateToolbarPositionAboveSelection, calculateToolbarPositionTrackHead } from './calculate-toolbar-position';
|
|
224
|
-
export { findNodePosByLocalIds } from './nodes-by-localIds';
|
|
224
|
+
export { findNodePosByLocalIds } from './nodes-by-localIds';
|
|
225
|
+
export { getPageElementCounts } from './page-element-counts';
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { reduce } from '@atlaskit/adf-utils/traverse';
|
|
2
|
+
/**
|
|
3
|
+
* Traverses a JSON document and counts the number of unique elements, text formatting and macros.
|
|
4
|
+
*
|
|
5
|
+
**/
|
|
6
|
+
export const getPageElementCounts = doc => {
|
|
7
|
+
const pageElementCounts = {
|
|
8
|
+
elements: {},
|
|
9
|
+
textFormats: {},
|
|
10
|
+
macros: {}
|
|
11
|
+
};
|
|
12
|
+
reduce(doc, (acc, node) => {
|
|
13
|
+
if (node.type === 'text') {
|
|
14
|
+
var _acc$elements$node$ty;
|
|
15
|
+
if (node.marks) {
|
|
16
|
+
node.marks.forEach(mark => {
|
|
17
|
+
var _acc$textFormats$mark;
|
|
18
|
+
const markType = mark.type;
|
|
19
|
+
acc.textFormats[markType] = ((_acc$textFormats$mark = acc.textFormats[markType]) !== null && _acc$textFormats$mark !== void 0 ? _acc$textFormats$mark : 0) + 1;
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
acc.elements[node.type] = ((_acc$elements$node$ty = acc.elements[node.type]) !== null && _acc$elements$node$ty !== void 0 ? _acc$elements$node$ty : 0) + 1;
|
|
23
|
+
}
|
|
24
|
+
// If the node is a 'macro'or extension
|
|
25
|
+
else if (node.type === 'extension' || node.type === 'inlineExtension' || node.type === 'bodiedExtension' || node.type === 'multiBodiedExtension' || node.type === 'extensionFrame') {
|
|
26
|
+
if ('attrs' in node && node.attrs && 'extensionKey' in node.attrs && node.attrs.extensionKey) {
|
|
27
|
+
var _acc$macros$extension;
|
|
28
|
+
const extensionKey = node.attrs.extensionKey;
|
|
29
|
+
acc.macros[extensionKey] = ((_acc$macros$extension = acc.macros[extensionKey]) !== null && _acc$macros$extension !== void 0 ? _acc$macros$extension : 0) + 1;
|
|
30
|
+
}
|
|
31
|
+
} else {
|
|
32
|
+
var _acc$elements$node$ty2;
|
|
33
|
+
acc.elements[node.type] = ((_acc$elements$node$ty2 = acc.elements[node.type]) !== null && _acc$elements$node$ty2 !== void 0 ? _acc$elements$node$ty2 : 0) + 1;
|
|
34
|
+
}
|
|
35
|
+
return acc;
|
|
36
|
+
}, pageElementCounts);
|
|
37
|
+
return pageElementCounts;
|
|
38
|
+
};
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
2
|
+
import _createClass from "@babel/runtime/helpers/createClass";
|
|
3
|
+
import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
|
|
4
|
+
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
5
|
+
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
6
|
+
import debounce from 'lodash/debounce';
|
|
7
|
+
import { DOMSerializer } from '@atlaskit/editor-prosemirror/model';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 📢 Public Type
|
|
11
|
+
*
|
|
12
|
+
* @see {withLazyLoading}
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 📢 Public Type
|
|
17
|
+
*
|
|
18
|
+
* @see {withLazyLoading}
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 🧱 Internal: Editor FE Platform
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 🧱 Internal: Editor FE Platform
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 🧱 Internal: Editor FE Platform
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 🧱 Internal: Editor FE Platform
|
|
35
|
+
*
|
|
36
|
+
* A cache to store loaded React NodeViews for each EditorView.
|
|
37
|
+
*
|
|
38
|
+
* This cache will help us to avoid any race condition
|
|
39
|
+
* when multiple Editors exist in the same page.
|
|
40
|
+
*
|
|
41
|
+
* @type {CacheType}
|
|
42
|
+
*/
|
|
43
|
+
var cachePerEditorView = new WeakMap();
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 🧱 Internal: Editor FE Platform
|
|
47
|
+
*/
|
|
48
|
+
var isFirefox = /gecko\/\d/i.test(navigator.userAgent);
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 🧱 Internal: Editor FE Platform
|
|
52
|
+
*
|
|
53
|
+
* A NodeView that serves as a placeholder until the actual NodeView is loaded.
|
|
54
|
+
*/
|
|
55
|
+
var LazyNodeView = /*#__PURE__*/_createClass(function LazyNodeView(node, view, getPos) {
|
|
56
|
+
var _node$type;
|
|
57
|
+
_classCallCheck(this, LazyNodeView);
|
|
58
|
+
if (typeof ((_node$type = node.type) === null || _node$type === void 0 || (_node$type = _node$type.spec) === null || _node$type === void 0 ? void 0 : _node$type.toDOM) !== 'function') {
|
|
59
|
+
this.dom = document.createElement('div');
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
var fallback = DOMSerializer.renderSpec(document, node.type.spec.toDOM(node));
|
|
63
|
+
this.dom = fallback.dom;
|
|
64
|
+
this.contentDOM = fallback.contentDOM;
|
|
65
|
+
if (this.dom instanceof HTMLElement) {
|
|
66
|
+
// This attribute is mostly used for debugging purposed
|
|
67
|
+
// It will help us to found out when the node was replaced
|
|
68
|
+
this.dom.setAttribute('data-lazy-node-view', node.type.name);
|
|
69
|
+
// This is used on Libra tests
|
|
70
|
+
// We are using this to make sure all lazy noded were replaced
|
|
71
|
+
// before the test started
|
|
72
|
+
this.dom.setAttribute('data-lazy-node-view-fallback', 'true');
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
/**
|
|
76
|
+
* 🧱 Internal: Editor FE Platform
|
|
77
|
+
*
|
|
78
|
+
* Debounces and replaces the node views in a ProseMirror editor with lazy-loaded node views.
|
|
79
|
+
*
|
|
80
|
+
* This function is used to update the `nodeViews` property of the `EditorView` after lazy-loaded
|
|
81
|
+
* node views have been loaded. It uses a debounced approach to ensure that the replacement does
|
|
82
|
+
* not happen too frequently, which can be performance-intensive.
|
|
83
|
+
*
|
|
84
|
+
* The function checks if there are any loaded node views in the cache associated with the given
|
|
85
|
+
* `EditorView`. If there are, it replaces the current `nodeViews` in the `EditorView` with the
|
|
86
|
+
* loaded node views. The replacement is scheduled using `requestIdleCallback` or
|
|
87
|
+
* `requestAnimationFrame` to avoid blocking the main thread, especially in Firefox where
|
|
88
|
+
* `requestIdleCallback` may not be supported.
|
|
89
|
+
*
|
|
90
|
+
* @param {WeakMap<EditorView, Record<string, NodeViewConstructor>>} cache - A WeakMap that stores
|
|
91
|
+
* the loaded node views for each `EditorView`. The key is the `EditorView`, and the value
|
|
92
|
+
* is a record of node type names to their corresponding `NodeViewConstructor`.
|
|
93
|
+
* @param {EditorView} view - The ProseMirror `EditorView` instance whose `nodeViews` property
|
|
94
|
+
* needs to be updated.
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* const cache = new WeakMap();
|
|
98
|
+
* const view = new EditorView(...);
|
|
99
|
+
*
|
|
100
|
+
* // Assume some node views have been loaded and stored in the cache
|
|
101
|
+
* cache.set(view, {
|
|
102
|
+
* 'table': TableViewConstructor,
|
|
103
|
+
* 'tableCell': TableCellViewConstructor,
|
|
104
|
+
* });
|
|
105
|
+
*
|
|
106
|
+
* debouncedReplaceNodeviews(cache, view);
|
|
107
|
+
*/
|
|
108
|
+
export var debouncedReplaceNodeviews = debounce(function (cache, view) {
|
|
109
|
+
var loadedNodeviews = cache.get(view);
|
|
110
|
+
if (!loadedNodeviews) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
cache.delete(view);
|
|
114
|
+
|
|
115
|
+
// eslint-disable-next-line compat/compat
|
|
116
|
+
var idle = window.requestIdleCallback;
|
|
117
|
+
|
|
118
|
+
/*
|
|
119
|
+
* For reasons that goes beyond my knowledge
|
|
120
|
+
* some Firefox versions aren't calling the requestIdleCallback.
|
|
121
|
+
*
|
|
122
|
+
* So, we need this check to make sure we use the requestAnimationFrame instead
|
|
123
|
+
*/
|
|
124
|
+
var later = isFirefox || typeof idle !== 'function' ? window.requestAnimationFrame : idle;
|
|
125
|
+
later(function () {
|
|
126
|
+
var currentNodeViews = view.props.nodeViews;
|
|
127
|
+
var nextNodeViews = _objectSpread(_objectSpread({}, currentNodeViews), loadedNodeviews);
|
|
128
|
+
view.setProps({
|
|
129
|
+
nodeViews: nextNodeViews
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* 📢 Public: Any EditorPlugin can use this function
|
|
136
|
+
*
|
|
137
|
+
* Wraps a NodeView constructor with laziness, allowing the NodeView to be loaded only when required.
|
|
138
|
+
*
|
|
139
|
+
* This higher-order function is designed to optimize the loading and rendering performance
|
|
140
|
+
* of ProseMirror editor nodes by deferring the loading of their associated NodeViews until they are actually needed.
|
|
141
|
+
* This is particularly useful for complex or heavy NodeViews, such as tables, table cells, rows, and headers within
|
|
142
|
+
* the ProseMirror editor. By using dynamic imports (with promises), the initial load time of the editor can be significantly
|
|
143
|
+
* reduced, leading to a smoother and faster user experience.
|
|
144
|
+
*
|
|
145
|
+
* The function accepts configuration parameters including the node name, a loader function that dynamically imports
|
|
146
|
+
* the NodeView, and a function to retrieve NodeView options. It returns a NodeViewConstructor that ProseMirror
|
|
147
|
+
* can use when rendering nodes of the specified type.
|
|
148
|
+
*
|
|
149
|
+
* @template NodeViewOptions - The type parameter that describes the shape of the options object for the NodeView.
|
|
150
|
+
* @param {LazyLoadingProps<NodeViewOptions>} params - Configuration parameters for lazy loading.
|
|
151
|
+
* @param {string} params.nodeName - The name of the node (e.g., 'table', 'tableCell', 'tableHeader', 'tableRow') for which the lazy-loaded NodeView is intended.
|
|
152
|
+
* @param {() => Promise<CreateReactNodeViewProps<NodeViewOptions>>} params.loader - A function that, when called, returns a promise that resolves to the actual NodeView constructor. This function typically uses dynamic `import()` to load the NodeView code.
|
|
153
|
+
* @param {() => NodeViewOptions} params.getNodeViewOptions - A function that returns the options to be passed to the NodeView constructor. These options can include dependencies like `portalProviderAPI`, `eventDispatcher`, and others, which are necessary for the NodeView's operation.
|
|
154
|
+
* @param {DispatchAnalyticsEvent} [params.dispatchAnalyticsEvent] - An optional function for dispatching analytics events, which can be used to monitor the performance and usage of the lazy-loaded NodeViews.
|
|
155
|
+
* @returns {NodeViewConstructor} A constructor function for creating a NodeView that ProseMirror can instantiate when it encounters a node of the specified type. This constructor is a lightweight placeholder until the actual NodeView is loaded.
|
|
156
|
+
*
|
|
157
|
+
* @example
|
|
158
|
+
* // Lazy load a table NodeView with specific options
|
|
159
|
+
* const lazyTableView = withLazyLoading({
|
|
160
|
+
* nodeName: 'table',
|
|
161
|
+
* loader: () => import('./table').then(module => module.createTableView),
|
|
162
|
+
* getNodeViewOptions: () => ({
|
|
163
|
+
* portalProviderAPI,
|
|
164
|
+
* eventDispatcher,
|
|
165
|
+
* getEditorContainerWidth,
|
|
166
|
+
* getEditorFeatureFlags,
|
|
167
|
+
* dispatchAnalyticsEvent,
|
|
168
|
+
* pluginInjectionApi,
|
|
169
|
+
* }),
|
|
170
|
+
* });
|
|
171
|
+
*
|
|
172
|
+
* // Then, use `lazyTableView` in ProseMirror editor setup to enhance 'table' nodes with lazy loading
|
|
173
|
+
*/
|
|
174
|
+
export var withLazyLoading = function withLazyLoading(_ref) {
|
|
175
|
+
var nodeName = _ref.nodeName,
|
|
176
|
+
loader = _ref.loader,
|
|
177
|
+
getNodeViewOptions = _ref.getNodeViewOptions,
|
|
178
|
+
dispatchAnalyticsEvent = _ref.dispatchAnalyticsEvent;
|
|
179
|
+
var createLazyNodeView = function createLazyNodeView(node, view, getPos, decorations) {
|
|
180
|
+
var _node$type2;
|
|
181
|
+
var cachedMap = cachePerEditorView.get(view);
|
|
182
|
+
if (!cachedMap) {
|
|
183
|
+
cachedMap = {};
|
|
184
|
+
cachePerEditorView.set(view, cachedMap);
|
|
185
|
+
}
|
|
186
|
+
var wasAlreadyRequested = cachedMap.hasOwnProperty(nodeName);
|
|
187
|
+
if (wasAlreadyRequested) {
|
|
188
|
+
return new LazyNodeView(node, view, getPos);
|
|
189
|
+
}
|
|
190
|
+
loader().then(function (nodeViewFuncModule) {
|
|
191
|
+
cachedMap[nodeName] = function (node, view, getPos, decorations) {
|
|
192
|
+
return nodeViewFuncModule(node, view, getPos, decorations, getNodeViewOptions);
|
|
193
|
+
};
|
|
194
|
+
debouncedReplaceNodeviews(cachePerEditorView, view);
|
|
195
|
+
});
|
|
196
|
+
if (typeof ((_node$type2 = node.type) === null || _node$type2 === void 0 || (_node$type2 = _node$type2.spec) === null || _node$type2 === void 0 ? void 0 : _node$type2.toDOM) !== 'function') {
|
|
197
|
+
// TODO: Analytics ED-23982
|
|
198
|
+
// dispatchAnalyticsEvent({
|
|
199
|
+
// action: ACTION.LAZY_NODE_VIEW_ERROR,
|
|
200
|
+
// actionSubject: ACTION_SUBJECT.LAZY_NODE_VIEW,
|
|
201
|
+
// eventType: EVENT_TYPE.OPERATIONAL,
|
|
202
|
+
// attributes: {
|
|
203
|
+
// nodeName,
|
|
204
|
+
// error: 'No spec found',
|
|
205
|
+
// },
|
|
206
|
+
// });
|
|
207
|
+
}
|
|
208
|
+
return new LazyNodeView(node, view, getPos);
|
|
209
|
+
};
|
|
210
|
+
return createLazyNodeView;
|
|
211
|
+
};
|