@atlaskit/editor-core 201.1.4 → 201.1.6

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.
Files changed (59) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/dist/cjs/composable-editor/editor-internal.js +57 -1
  3. package/dist/cjs/create-editor/ReactEditorView/formatFullWidthAppearance.js +13 -0
  4. package/dist/cjs/create-editor/ReactEditorView/getUAPrefix.js +19 -0
  5. package/dist/cjs/create-editor/ReactEditorView/handleEditorFocus.js +44 -0
  6. package/dist/cjs/create-editor/ReactEditorView/useDispatchTransaction.js +90 -0
  7. package/dist/cjs/create-editor/ReactEditorView/useFireFullWidthEvent.js +28 -0
  8. package/dist/cjs/create-editor/ReactEditorView/usePluginPerformanceObserver.js +48 -0
  9. package/dist/cjs/create-editor/ReactEditorViewNext.js +538 -0
  10. package/dist/cjs/presets/universal.js +2 -1
  11. package/dist/cjs/ui/Addon/click-area-helper.js +0 -1
  12. package/dist/cjs/version-wrapper.js +1 -1
  13. package/dist/es2019/composable-editor/editor-internal.js +58 -1
  14. package/dist/es2019/create-editor/ReactEditorView/formatFullWidthAppearance.js +7 -0
  15. package/dist/es2019/create-editor/ReactEditorView/getUAPrefix.js +13 -0
  16. package/dist/es2019/create-editor/ReactEditorView/handleEditorFocus.js +38 -0
  17. package/dist/es2019/create-editor/ReactEditorView/useDispatchTransaction.js +82 -0
  18. package/dist/es2019/create-editor/ReactEditorView/useFireFullWidthEvent.js +22 -0
  19. package/dist/es2019/create-editor/ReactEditorView/usePluginPerformanceObserver.js +32 -0
  20. package/dist/es2019/create-editor/ReactEditorViewNext.js +515 -0
  21. package/dist/es2019/presets/universal.js +2 -1
  22. package/dist/es2019/ui/Addon/click-area-helper.js +0 -1
  23. package/dist/es2019/version-wrapper.js +1 -1
  24. package/dist/esm/composable-editor/editor-internal.js +57 -1
  25. package/dist/esm/create-editor/ReactEditorView/formatFullWidthAppearance.js +7 -0
  26. package/dist/esm/create-editor/ReactEditorView/getUAPrefix.js +13 -0
  27. package/dist/esm/create-editor/ReactEditorView/handleEditorFocus.js +38 -0
  28. package/dist/esm/create-editor/ReactEditorView/useDispatchTransaction.js +84 -0
  29. package/dist/esm/create-editor/ReactEditorView/useFireFullWidthEvent.js +22 -0
  30. package/dist/esm/create-editor/ReactEditorView/usePluginPerformanceObserver.js +42 -0
  31. package/dist/esm/create-editor/ReactEditorViewNext.js +528 -0
  32. package/dist/esm/presets/universal.js +2 -1
  33. package/dist/esm/ui/Addon/click-area-helper.js +0 -1
  34. package/dist/esm/version-wrapper.js +1 -1
  35. package/dist/types/create-editor/ReactEditorView/formatFullWidthAppearance.d.ts +3 -0
  36. package/dist/types/create-editor/ReactEditorView/getUAPrefix.d.ts +1 -0
  37. package/dist/types/create-editor/ReactEditorView/handleEditorFocus.d.ts +2 -0
  38. package/dist/types/create-editor/ReactEditorView/useDispatchTransaction.d.ts +12 -0
  39. package/dist/types/create-editor/ReactEditorView/useFireFullWidthEvent.d.ts +3 -0
  40. package/dist/types/create-editor/ReactEditorView/usePluginPerformanceObserver.d.ts +5 -0
  41. package/dist/types/create-editor/ReactEditorViewNext.d.ts +48 -0
  42. package/dist/types/create-editor/create-universal-preset.d.ts +12 -2
  43. package/dist/types/presets/default.d.ts +8 -0
  44. package/dist/types/presets/universal.d.ts +12 -2
  45. package/dist/types/presets/useUniversalPreset.d.ts +12 -2
  46. package/dist/types/types/editor-config.d.ts +1 -2
  47. package/dist/types-ts4.5/create-editor/ReactEditorView/formatFullWidthAppearance.d.ts +3 -0
  48. package/dist/types-ts4.5/create-editor/ReactEditorView/getUAPrefix.d.ts +1 -0
  49. package/dist/types-ts4.5/create-editor/ReactEditorView/handleEditorFocus.d.ts +2 -0
  50. package/dist/types-ts4.5/create-editor/ReactEditorView/useDispatchTransaction.d.ts +12 -0
  51. package/dist/types-ts4.5/create-editor/ReactEditorView/useFireFullWidthEvent.d.ts +3 -0
  52. package/dist/types-ts4.5/create-editor/ReactEditorView/usePluginPerformanceObserver.d.ts +5 -0
  53. package/dist/types-ts4.5/create-editor/ReactEditorViewNext.d.ts +48 -0
  54. package/dist/types-ts4.5/create-editor/create-universal-preset.d.ts +15 -2
  55. package/dist/types-ts4.5/presets/default.d.ts +8 -0
  56. package/dist/types-ts4.5/presets/universal.d.ts +15 -2
  57. package/dist/types-ts4.5/presets/useUniversalPreset.d.ts +15 -2
  58. package/dist/types-ts4.5/types/editor-config.d.ts +1 -2
  59. package/package.json +20 -9
@@ -0,0 +1,515 @@
1
+ import React, { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
2
+ import { injectIntl } from 'react-intl-next';
3
+ import uuid from 'uuid/v4';
4
+ import { ACTION, ACTION_SUBJECT, EVENT_TYPE, fireAnalyticsEvent, PLATFORMS } from '@atlaskit/editor-common/analytics';
5
+ import { useConstructor, usePreviousState } from '@atlaskit/editor-common/hooks';
6
+ import { getEnabledFeatureFlagKeys } from '@atlaskit/editor-common/normalize-feature-flags';
7
+ import { measureRender } from '@atlaskit/editor-common/performance/measure-render';
8
+ import { getResponseEndTime } from '@atlaskit/editor-common/performance/navigation';
9
+ import { EditorPluginInjectionAPI } from '@atlaskit/editor-common/preset';
10
+ import { processRawValue, processRawValueWithoutTransformation } from '@atlaskit/editor-common/process-raw-value';
11
+ import { analyticsEventKey, getAnalyticsEventSeverity } from '@atlaskit/editor-common/utils/analytics';
12
+ import { EditorState, Selection, TextSelection } from '@atlaskit/editor-prosemirror/state';
13
+ import { EditorView } from '@atlaskit/editor-prosemirror/view';
14
+ import { fg } from '@atlaskit/platform-feature-flags';
15
+ import { useProviders } from '../composable-editor/hooks/useProviders';
16
+ import { createDispatch, EventDispatcher } from '../event-dispatcher';
17
+ import { getNodesCount } from '../utils/getNodesCount';
18
+ import { isFullPage } from '../utils/is-full-page';
19
+ import { RenderTracking } from '../utils/performance/components/RenderTracking';
20
+ import measurements from '../utils/performance/measure-enum';
21
+ import { PROSEMIRROR_RENDERED_DEGRADED_SEVERITY_THRESHOLD, PROSEMIRROR_RENDERED_NORMAL_SEVERITY_THRESHOLD } from './consts';
22
+ import { createErrorReporter, createPMPlugins, processPluginsList } from './create-editor';
23
+ import createPluginsList from './create-plugins-list';
24
+ import { createSchema } from './create-schema';
25
+ import { createFeatureFlagsFromProps } from './feature-flags-from-props';
26
+ import { editorMessages } from './messages';
27
+ import { getUAPrefix } from './ReactEditorView/getUAPrefix';
28
+ import { handleEditorFocus } from './ReactEditorView/handleEditorFocus';
29
+ import { useDispatchTransaction } from './ReactEditorView/useDispatchTransaction';
30
+ import { useFireFullWidthEvent } from './ReactEditorView/useFireFullWidthEvent';
31
+ import { usePluginPerformanceObserver } from './ReactEditorView/usePluginPerformanceObserver';
32
+ import ReactEditorViewContext from './ReactEditorViewContext';
33
+ const EDIT_AREA_ID = 'ak-editor-textarea';
34
+ function ReactEditorView(props) {
35
+ var _media, _linking, _linking$smartLinks, _props$render, _props$render2;
36
+ const {
37
+ preset,
38
+ editorProps: {
39
+ appearance: nextAppearance,
40
+ disabled
41
+ }
42
+ } = props;
43
+ const [editorAPI, setEditorAPI] = useState(undefined);
44
+ const editorRef = useRef(null);
45
+ const viewRef = useRef();
46
+ const focusTimeoutId = useRef();
47
+ // ProseMirror is instantiated prior to the initial React render cycle,
48
+ // so we allow transactions by default, to avoid discarding the initial one.
49
+ const canDispatchTransactions = useRef(true);
50
+ const editorId = useRef(uuid());
51
+ const eventDispatcher = useMemo(() => new EventDispatcher(), []);
52
+ const config = useRef({
53
+ nodes: [],
54
+ marks: [],
55
+ pmPlugins: [],
56
+ contentComponents: [],
57
+ pluginHooks: [],
58
+ primaryToolbarComponents: [],
59
+ secondaryToolbarComponents: [],
60
+ onEditorViewStateUpdatedCallbacks: []
61
+ });
62
+ const contentTransformer = useRef(undefined);
63
+ const featureFlags = useRef(createFeatureFlagsFromProps(props.editorProps));
64
+ const getEditorState = useCallback(() => {
65
+ var _viewRef$current;
66
+ return (_viewRef$current = viewRef.current) === null || _viewRef$current === void 0 ? void 0 : _viewRef$current.state;
67
+ }, []);
68
+ const getEditorView = useCallback(() => viewRef.current, []);
69
+ const dispatch = useMemo(() => createDispatch(eventDispatcher), [eventDispatcher]);
70
+ const errorReporter = useRef(createErrorReporter(props.editorProps.errorReporterHandler));
71
+ const handleAnalyticsEvent = useCallback(payload => {
72
+ fireAnalyticsEvent(props.createAnalyticsEvent)(payload);
73
+ }, [props.createAnalyticsEvent]);
74
+ const dispatchAnalyticsEvent = useCallback(payload => {
75
+ const dispatch = createDispatch(eventDispatcher);
76
+ dispatch(analyticsEventKey, {
77
+ payload
78
+ });
79
+ }, [eventDispatcher]);
80
+ const pluginInjectionAPI = useRef(new EditorPluginInjectionAPI({
81
+ getEditorState: getEditorState,
82
+ getEditorView: getEditorView
83
+ }));
84
+ useLayoutEffect(() => {
85
+ setEditorAPI(pluginInjectionAPI.current.api());
86
+ }, []);
87
+ const blur = useCallback(() => {
88
+ if (!viewRef.current) {
89
+ return;
90
+ }
91
+ if (viewRef.current.dom instanceof HTMLElement && viewRef.current.hasFocus()) {
92
+ viewRef.current.dom.blur();
93
+ }
94
+
95
+ // The selectionToDOM method uses the document selection to determine currently selected node
96
+ // We need to mimic blurring this as it seems doing the above is not enough.
97
+ // @ts-expect-error
98
+ const sel = viewRef.current.root.getSelection();
99
+ if (sel) {
100
+ sel.removeAllRanges();
101
+ }
102
+ }, []);
103
+ const createEditorState = useCallback(options => {
104
+ var _api$editorViewMode;
105
+ let schema;
106
+ if (viewRef.current) {
107
+ if (options.resetting) {
108
+ /**
109
+ * ReactEditorView currently does NOT handle dynamic schema,
110
+ * We are reusing the existing schema, and rely on #reconfigureState
111
+ * to update `this.config`
112
+ */
113
+ schema = viewRef.current.state.schema;
114
+ } else {
115
+ /**
116
+ * There's presently a number of issues with changing the schema of a
117
+ * editor inflight. A significant issue is that we lose the ability
118
+ * to keep track of a user's history as the internal plugin state
119
+ * keeps a list of Steps to undo/redo (which are tied to the schema).
120
+ * Without a good way to do work around this, we prevent this for now.
121
+ */
122
+ // eslint-disable-next-line no-console
123
+ console.warn('The editor does not support changing the schema dynamically.');
124
+ return viewRef.current.state;
125
+ }
126
+ } else {
127
+ config.current = processPluginsList(createPluginsList(options.props.preset, props.editorProps, pluginInjectionAPI.current));
128
+ schema = createSchema(config.current);
129
+ }
130
+ const {
131
+ contentTransformerProvider
132
+ } = options.props.editorProps;
133
+ const plugins = createPMPlugins({
134
+ schema,
135
+ dispatch: dispatch,
136
+ errorReporter: errorReporter.current,
137
+ editorConfig: config.current,
138
+ eventDispatcher: eventDispatcher,
139
+ providerFactory: options.props.providerFactory,
140
+ portalProviderAPI: props.portalProviderAPI,
141
+ dispatchAnalyticsEvent: dispatchAnalyticsEvent,
142
+ featureFlags: featureFlags.current,
143
+ getIntl: () => props.intl
144
+ });
145
+ contentTransformer.current = contentTransformerProvider ? contentTransformerProvider(schema) : undefined;
146
+ const api = pluginInjectionAPI.current.api();
147
+
148
+ // If we have a doc prop, we need to process it into a PMNode
149
+ let doc;
150
+ if (options.doc) {
151
+ // if the collabEdit API is set, skip this validation due to potential pm validation errors
152
+ // from docs that end up with invalid marks after processing (See #hot-111702 for more details)
153
+ if ((api === null || api === void 0 ? void 0 : api.collabEdit) !== undefined && fg('editor_load_conf_collab_docs_without_checks')) {
154
+ doc = processRawValueWithoutTransformation(schema, options.doc);
155
+ } else {
156
+ doc = processRawValue(schema, options.doc, options.props.providerFactory, options.props.editorProps.sanitizePrivateContent, contentTransformer.current, dispatchAnalyticsEvent);
157
+ }
158
+ }
159
+ const isViewMode = (api === null || api === void 0 ? void 0 : (_api$editorViewMode = api.editorViewMode) === null || _api$editorViewMode === void 0 ? void 0 : _api$editorViewMode.sharedState.currentState().mode) === 'view';
160
+ let selection;
161
+ if (doc) {
162
+ if (isViewMode) {
163
+ const emptySelection = new TextSelection(doc.resolve(0));
164
+ return EditorState.create({
165
+ schema,
166
+ plugins: plugins,
167
+ doc,
168
+ selection: emptySelection
169
+ });
170
+ } else {
171
+ selection = options.selectionAtStart ? Selection.atStart(doc) : Selection.atEnd(doc);
172
+ }
173
+ }
174
+ // Workaround for ED-3507: When media node is the last element, scrollIntoView throws an error
175
+ const patchedSelection = selection ? Selection.findFrom(selection.$head, -1, true) || undefined : undefined;
176
+ return EditorState.create({
177
+ schema,
178
+ plugins: plugins,
179
+ doc,
180
+ selection: patchedSelection
181
+ });
182
+ }, [props.intl, props.portalProviderAPI, props.editorProps, dispatchAnalyticsEvent, eventDispatcher, dispatch]);
183
+ const resetEditorState = useCallback(({
184
+ doc,
185
+ shouldScrollToBottom
186
+ }) => {
187
+ var _props$editorProps$on, _props$editorProps;
188
+ if (!viewRef.current) {
189
+ return;
190
+ }
191
+
192
+ // We cannot currently guarentee when all the portals will have re-rendered during a reconfigure
193
+ // so we blur here to stop ProseMirror from trying to apply selection to detached nodes or
194
+ // nodes that haven't been re-rendered to the document yet.
195
+ blur();
196
+ featureFlags.current = createFeatureFlagsFromProps(props.editorProps);
197
+ const newEditorState = createEditorState({
198
+ props: props,
199
+ doc: doc,
200
+ resetting: true,
201
+ selectionAtStart: !shouldScrollToBottom
202
+ });
203
+ editorState.current = newEditorState;
204
+ viewRef.current.updateState(newEditorState);
205
+ (_props$editorProps$on = (_props$editorProps = props.editorProps).onChange) === null || _props$editorProps$on === void 0 ? void 0 : _props$editorProps$on.call(_props$editorProps, viewRef.current, {
206
+ source: 'local'
207
+ });
208
+ }, [blur, createEditorState, props]);
209
+
210
+ // Initialise phase
211
+ // Using constructor hook so we setup and dispatch analytics before anything else
212
+ useConstructor(() => {
213
+ dispatchAnalyticsEvent({
214
+ action: ACTION.STARTED,
215
+ actionSubject: ACTION_SUBJECT.EDITOR,
216
+ attributes: {
217
+ platform: PLATFORMS.WEB,
218
+ featureFlags: featureFlags.current ? getEnabledFeatureFlagKeys(featureFlags.current) : []
219
+ },
220
+ eventType: EVENT_TYPE.UI
221
+ });
222
+ // Transaction dispatching is already enabled by default prior to
223
+ // mounting, but we reset it here, just in case the editor view
224
+ // instance is ever recycled (mounted again after unmounting) with
225
+ // the same key.
226
+ // Although storing mounted state is an anti-pattern in React,
227
+ // we do so here so that we can intercept and abort asynchronous
228
+ // ProseMirror transactions when a dismount is imminent.
229
+ canDispatchTransactions.current = true;
230
+ // This needs to be before initialising editorState because
231
+ // we dispatch analytics events in plugin initialisation
232
+ eventDispatcher.on(analyticsEventKey, handleAnalyticsEvent);
233
+ eventDispatcher.on('resetEditorState', resetEditorState);
234
+ });
235
+
236
+ // Cleanup
237
+ useLayoutEffect(() => {
238
+ return () => {
239
+ const focusTimeoutIdCurrent = focusTimeoutId.current;
240
+ if (focusTimeoutIdCurrent) {
241
+ clearTimeout(focusTimeoutIdCurrent);
242
+ }
243
+ if (viewRef.current) {
244
+ // Destroy the state if the Editor is being unmounted
245
+ const editorState = viewRef.current.state;
246
+ editorState.plugins.forEach(plugin => {
247
+ const state = plugin.getState(editorState);
248
+ if (state && state.destroy) {
249
+ state.destroy();
250
+ }
251
+ });
252
+ }
253
+ eventDispatcher.destroy();
254
+ // this.view will be destroyed when React unmounts in handleEditorViewRef
255
+ };
256
+ }, [eventDispatcher]);
257
+ const reconfigureState = useCallback(props => {
258
+ if (!viewRef.current) {
259
+ return;
260
+ }
261
+
262
+ // We cannot currently guarentee when all the portals will have re-rendered during a reconfigure
263
+ // so we blur here to stop ProseMirror from trying to apply selection to detached nodes or
264
+ // nodes that haven't been re-rendered to the document yet.
265
+ blur();
266
+ const editorPlugins = createPluginsList(props.preset, props.editorProps, pluginInjectionAPI.current);
267
+ config.current = processPluginsList(editorPlugins);
268
+ const state = editorState.current;
269
+ const plugins = createPMPlugins({
270
+ schema: state.schema,
271
+ dispatch: dispatch,
272
+ errorReporter: errorReporter.current,
273
+ editorConfig: config.current,
274
+ eventDispatcher: eventDispatcher,
275
+ providerFactory: props.providerFactory,
276
+ portalProviderAPI: props.portalProviderAPI,
277
+ dispatchAnalyticsEvent: dispatchAnalyticsEvent,
278
+ featureFlags: createFeatureFlagsFromProps(props.editorProps),
279
+ getIntl: () => props.intl
280
+ });
281
+ const newState = state.reconfigure({
282
+ plugins: plugins
283
+ });
284
+
285
+ // need to update the state first so when the view builds the nodeviews it is
286
+ // using the latest plugins
287
+ viewRef.current.updateState(newState);
288
+ return viewRef.current.update({
289
+ ...viewRef.current.props,
290
+ state: newState
291
+ });
292
+ }, [blur, dispatchAnalyticsEvent, eventDispatcher, dispatch]);
293
+ const onEditorViewUpdated = useCallback(({
294
+ originalTransaction,
295
+ transactions,
296
+ oldEditorState,
297
+ newEditorState
298
+ }) => {
299
+ var _config$current;
300
+ pluginInjectionAPI.current.onEditorViewUpdated({
301
+ newEditorState,
302
+ oldEditorState
303
+ });
304
+ (_config$current = config.current) === null || _config$current === void 0 ? void 0 : _config$current.onEditorViewStateUpdatedCallbacks.forEach(entry => {
305
+ entry.callback({
306
+ originalTransaction,
307
+ transactions,
308
+ oldEditorState,
309
+ newEditorState
310
+ });
311
+ });
312
+ }, []);
313
+ const dispatchTransaction = useDispatchTransaction({
314
+ onChange: props.editorProps.onChange,
315
+ dispatchAnalyticsEvent,
316
+ onEditorViewUpdated
317
+ });
318
+
319
+ // TODO: Remove these when we deprecate these props from editor-props - smartLinks is unfortunately still used in some places, we can sidestep this problem if we move everyone across to ComposableEditor and deprecate Editor
320
+ const UNSAFE_cards = props.editorProps.UNSAFE_cards;
321
+ const smartLinks = props.editorProps.smartLinks;
322
+
323
+ // Temporary to replace provider factory while migration to `ComposableEditor` occurs
324
+ useProviders({
325
+ editorApi: editorAPI,
326
+ contextIdentifierProvider: props.editorProps.contextIdentifierProvider,
327
+ mediaProvider: (_media = props.editorProps.media) === null || _media === void 0 ? void 0 : _media.provider,
328
+ cardProvider: ((_linking = props.editorProps.linking) === null || _linking === void 0 ? void 0 : (_linking$smartLinks = _linking.smartLinks) === null || _linking$smartLinks === void 0 ? void 0 : _linking$smartLinks.provider) || smartLinks && smartLinks.provider || UNSAFE_cards && UNSAFE_cards.provider,
329
+ emojiProvider: props.editorProps.emojiProvider,
330
+ autoformattingProvider: props.editorProps.autoformattingProvider,
331
+ taskDecisionProvider: props.editorProps.taskDecisionProvider
332
+ });
333
+ const getDirectEditorProps = useCallback(state => {
334
+ return {
335
+ state: state || editorState.current,
336
+ dispatchTransaction: tr => {
337
+ // Block stale transactions:
338
+ // Prevent runtime exeptions from async transactions that would attempt to
339
+ // update the DOM after React has unmounted the Editor.
340
+ if (canDispatchTransactions.current) {
341
+ const newState = dispatchTransaction(viewRef.current, tr);
342
+ if (newState) {
343
+ editorState.current = newState;
344
+ }
345
+ }
346
+ },
347
+ // Disables the contentEditable attribute of the editor if the editor is disabled
348
+ editable: _state => !disabled,
349
+ attributes: {
350
+ 'data-gramm': 'false'
351
+ }
352
+ };
353
+ }, [dispatchTransaction, disabled]);
354
+ const createEditorView = useCallback(node => {
355
+ measureRender(measurements.PROSEMIRROR_RENDERED, ({
356
+ duration,
357
+ startTime,
358
+ distortedDuration
359
+ }) => {
360
+ const proseMirrorRenderedSeverity = getAnalyticsEventSeverity(duration, PROSEMIRROR_RENDERED_NORMAL_SEVERITY_THRESHOLD, PROSEMIRROR_RENDERED_DEGRADED_SEVERITY_THRESHOLD);
361
+ if (viewRef.current) {
362
+ var _pluginInjectionAPI$c;
363
+ const nodes = getNodesCount(viewRef.current.state.doc);
364
+ const ttfb = getResponseEndTime();
365
+ const contextIdentifier = (_pluginInjectionAPI$c = pluginInjectionAPI.current.api().base) === null || _pluginInjectionAPI$c === void 0 ? void 0 : _pluginInjectionAPI$c.sharedState.currentState();
366
+ dispatchAnalyticsEvent({
367
+ action: ACTION.PROSEMIRROR_RENDERED,
368
+ actionSubject: ACTION_SUBJECT.EDITOR,
369
+ attributes: {
370
+ duration,
371
+ startTime,
372
+ nodes,
373
+ ttfb,
374
+ severity: proseMirrorRenderedSeverity,
375
+ objectId: contextIdentifier === null || contextIdentifier === void 0 ? void 0 : contextIdentifier.objectId,
376
+ distortedDuration
377
+ },
378
+ eventType: EVENT_TYPE.OPERATIONAL
379
+ });
380
+ }
381
+ });
382
+
383
+ // Creates the editor-view from this.editorState. If an editor has been mounted
384
+ // previously, this will contain the previous state of the editor.
385
+ const view = new EditorView({
386
+ mount: node
387
+ }, getDirectEditorProps());
388
+ viewRef.current = view;
389
+ pluginInjectionAPI.current.onEditorViewUpdated({
390
+ newEditorState: viewRef.current.state,
391
+ oldEditorState: undefined
392
+ });
393
+ return view;
394
+ }, [getDirectEditorProps, dispatchAnalyticsEvent]);
395
+ const [_, setEditorView] = useState(undefined);
396
+ const {
397
+ onEditorCreated,
398
+ onEditorDestroyed,
399
+ editorProps: {
400
+ shouldFocus
401
+ }
402
+ } = props;
403
+ const handleEditorViewRef = useCallback(node => {
404
+ if (!viewRef.current && node) {
405
+ const view = createEditorView(node);
406
+ onEditorCreated({
407
+ view,
408
+ config: config.current,
409
+ eventDispatcher: eventDispatcher,
410
+ transformer: contentTransformer.current
411
+ });
412
+ if (shouldFocus && view.props.editable && view.props.editable(view.state)) {
413
+ focusTimeoutId.current = handleEditorFocus(view);
414
+ }
415
+
416
+ // Force React to re-render so consumers get a reference to the editor view
417
+ setEditorView(view);
418
+ } else if (viewRef.current && !node) {
419
+ // When the appearance is changed, React will call handleEditorViewRef with node === null
420
+ // to destroy the old EditorView, before calling this method again with node === div to
421
+ // create the new EditorView
422
+ onEditorDestroyed({
423
+ view: viewRef.current,
424
+ config: config.current,
425
+ eventDispatcher: eventDispatcher,
426
+ transformer: contentTransformer.current
427
+ });
428
+ const wasAnalyticsDisconnected = !eventDispatcher.has(analyticsEventKey, handleAnalyticsEvent);
429
+ // If we disabled event listening for some reason we should re-enable it temporarily while we destroy
430
+ // the view for any analytics that occur there.
431
+ if (wasAnalyticsDisconnected) {
432
+ eventDispatcher.on(analyticsEventKey, handleAnalyticsEvent);
433
+ viewRef.current.destroy(); // Destroys the dom node & all node views
434
+ eventDispatcher.off(analyticsEventKey, handleAnalyticsEvent);
435
+ } else {
436
+ viewRef.current.destroy(); // Destroys the dom node & all node views
437
+ }
438
+ viewRef.current = undefined;
439
+ }
440
+ }, [createEditorView, handleAnalyticsEvent, onEditorDestroyed, onEditorCreated, shouldFocus, eventDispatcher]);
441
+ const createEditor = useCallback((assistiveLabel, assistiveDescribedBy) => {
442
+ return /*#__PURE__*/React.createElement("div", {
443
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
444
+ className: getUAPrefix(),
445
+ key: "ProseMirror",
446
+ ref: handleEditorViewRef,
447
+ "aria-label": assistiveLabel || props.intl.formatMessage(editorMessages.editorAssistiveLabel)
448
+ // setting aria-multiline to true when not mobile appearance.
449
+ // because somehow mobile tests are failing when it set.
450
+ // don't know why that is happening.
451
+ // Created https://product-fabric.atlassian.net/jira/servicedesk/projects/DTR/queues/issue/DTR-1675
452
+ // to investigate further.
453
+ ,
454
+ "aria-multiline": true,
455
+ role: "textbox",
456
+ id: EDIT_AREA_ID,
457
+ "aria-describedby": assistiveDescribedBy,
458
+ "data-editor-id": editorId.current
459
+ });
460
+ }, [handleEditorViewRef, props.intl]);
461
+ const previousPreset = usePreviousState(preset);
462
+ useLayoutEffect(() => {
463
+ if (previousPreset && previousPreset !== preset) {
464
+ reconfigureState(props);
465
+ }
466
+ }, [reconfigureState, previousPreset, preset, props]);
467
+ const previousDisabledState = usePreviousState(disabled);
468
+ useLayoutEffect(() => {
469
+ if (viewRef.current && previousDisabledState !== disabled) {
470
+ // Disables the contentEditable attribute of the editor if the editor is disabled
471
+ viewRef.current.setProps({
472
+ editable: _state => !disabled
473
+ });
474
+ if (!disabled && shouldFocus) {
475
+ focusTimeoutId.current = handleEditorFocus(viewRef.current);
476
+ }
477
+ }
478
+ }, [disabled, shouldFocus, previousDisabledState]);
479
+ useFireFullWidthEvent(nextAppearance, dispatchAnalyticsEvent);
480
+ const editorState = useRef(createEditorState({
481
+ props,
482
+ doc: props.editorProps.defaultValue,
483
+ // ED-4759: Don't set selection at end for full-page editor - should be at start.
484
+ selectionAtStart: isFullPage(props.editorProps.appearance)
485
+ }));
486
+ usePluginPerformanceObserver(editorState, pluginInjectionAPI, dispatchAnalyticsEvent);
487
+ const editor = useMemo(() => createEditor(props.editorProps.assistiveLabel, props.editorProps.assistiveDescribedBy),
488
+ // `createEditor` changes a little too frequently - we don't want to recreate the editor view in this case
489
+ // We should follow-up
490
+ // eslint-disable-next-line react-hooks/exhaustive-deps
491
+ [props.editorProps.assistiveLabel, props.editorProps.assistiveDescribedBy]);
492
+ return /*#__PURE__*/React.createElement(ReactEditorViewContext.Provider, {
493
+ value: {
494
+ editorRef: editorRef,
495
+ editorView: viewRef.current,
496
+ popupsMountPoint: props.editorProps.popupsMountPoint
497
+ }
498
+ }, /*#__PURE__*/React.createElement(RenderTracking, {
499
+ componentProps: props,
500
+ action: ACTION.RE_RENDERED,
501
+ actionSubject: ACTION_SUBJECT.REACT_EDITOR_VIEW,
502
+ handleAnalyticsEvent: handleAnalyticsEvent,
503
+ useShallow: true
504
+ }), props.render ? (_props$render = (_props$render2 = props.render) === null || _props$render2 === void 0 ? void 0 : _props$render2.call(props, {
505
+ editor,
506
+ view: viewRef.current,
507
+ config: config.current,
508
+ eventDispatcher: eventDispatcher,
509
+ transformer: contentTransformer.current,
510
+ dispatchAnalyticsEvent: dispatchAnalyticsEvent,
511
+ editorRef: editorRef,
512
+ editorAPI: editorAPI
513
+ })) !== null && _props$render !== void 0 ? _props$render : editor : editor);
514
+ }
515
+ export default injectIntl(ReactEditorView);
@@ -204,7 +204,8 @@ export default function createUniversalPresetInternal({
204
204
  props.allowTemplatePlaceholders !== true ? props.allowTemplatePlaceholders : {}], Boolean(props.allowTemplatePlaceholders)).maybeAdd([layoutPlugin, {
205
205
  ...(typeof props.allowLayouts === 'object' ? props.allowLayouts : {}),
206
206
  useLongPressSelection: false,
207
- UNSAFE_allowSingleColumnLayout: typeof props.allowLayouts === 'object' ? props.allowLayouts.UNSAFE_allowSingleColumnLayout : undefined
207
+ UNSAFE_allowSingleColumnLayout: typeof props.allowLayouts === 'object' ? props.allowLayouts.UNSAFE_allowSingleColumnLayout : undefined,
208
+ editorAppearance: appearance
208
209
  }], Boolean(props.allowLayouts)).maybeAdd([cardPlugin, {
209
210
  ...props.UNSAFE_cards,
210
211
  ...props.smartLinks,
@@ -111,7 +111,6 @@ const outsideProsemirrorEditorClickHandler = (view, event) => {
111
111
  dispatch(tr);
112
112
  }
113
113
  view.focus();
114
- event.stopPropagation();
115
114
  event.preventDefault();
116
115
  };
117
116
  export { clickAreaClickHandler };
@@ -1,2 +1,2 @@
1
1
  export const name = "@atlaskit/editor-core";
2
- export const version = "201.1.4";
2
+ export const version = "201.1.6";
@@ -13,9 +13,11 @@ import { Fragment, memo, useState } from 'react';
13
13
  import { css, jsx } from '@emotion/react';
14
14
  import { ACTION, ACTION_SUBJECT } from '@atlaskit/editor-common/analytics';
15
15
  import { usePortalProvider } from '@atlaskit/editor-common/portal';
16
+ import { fg } from '@atlaskit/platform-feature-flags';
16
17
  import ErrorBoundary from '../create-editor/ErrorBoundary';
17
18
  import { createFeatureFlagsFromProps } from '../create-editor/feature-flags-from-props';
18
19
  import ReactEditorView from '../create-editor/ReactEditorView';
20
+ import ReactEditorViewNext from '../create-editor/ReactEditorViewNext';
19
21
  import { ContextAdapter } from '../nodeviews/context-adapter';
20
22
  import EditorContext from '../ui/EditorContext';
21
23
  import { IntlProviderIfMissingWrapper } from '../ui/IntlProviderIfMissingWrapper/IntlProviderIfMissingWrapper';
@@ -72,7 +74,7 @@ export var EditorInternal = /*#__PURE__*/memo(function (_ref) {
72
74
  css: editorContainerStyles
73
75
  }, jsx(EditorContext, {
74
76
  editorActions: editorActions
75
- }, jsx(ContextAdapter, null, jsx(IntlProviderIfMissingWrapper, null, jsx(Fragment, null, jsx(ReactEditorViewContextWrapper, {
77
+ }, jsx(ContextAdapter, null, jsx(IntlProviderIfMissingWrapper, null, jsx(Fragment, null, fg('platform_editor_react_editor_view_react_18') ? jsx(ReactEditorViewNext, {
76
78
  editorProps: overriddenEditorProps,
77
79
  createAnalyticsEvent: createAnalyticsEvent,
78
80
  portalProviderAPI: portalProviderAPI,
@@ -126,6 +128,60 @@ export var EditorInternal = /*#__PURE__*/memo(function (_ref) {
126
128
  pluginHooks: config.pluginHooks
127
129
  }));
128
130
  }
131
+ }) : jsx(ReactEditorViewContextWrapper, {
132
+ editorProps: overriddenEditorProps,
133
+ createAnalyticsEvent: createAnalyticsEvent,
134
+ portalProviderAPI: portalProviderAPI,
135
+ providerFactory: providerFactory,
136
+ onEditorCreated: onEditorCreated,
137
+ onEditorDestroyed: onEditorDestroyed,
138
+ disabled: props.disabled,
139
+ preset: preset,
140
+ render: function render(_ref3) {
141
+ var _props$featureFlags3, _props$featureFlags4;
142
+ var editor = _ref3.editor,
143
+ view = _ref3.view,
144
+ eventDispatcher = _ref3.eventDispatcher,
145
+ config = _ref3.config,
146
+ dispatchAnalyticsEvent = _ref3.dispatchAnalyticsEvent,
147
+ editorRef = _ref3.editorRef,
148
+ editorAPI = _ref3.editorAPI;
149
+ return jsx(BaseThemeWrapper, {
150
+ baseFontSize: getBaseFontSize(props.appearance)
151
+ }, jsx(AppearanceComponent, {
152
+ innerRef: editorRef,
153
+ editorAPI: editorAPI,
154
+ appearance: props.appearance,
155
+ disabled: props.disabled,
156
+ editorActions: editorActions,
157
+ editorDOMElement: editor,
158
+ editorView: view,
159
+ providerFactory: providerFactory,
160
+ eventDispatcher: eventDispatcher,
161
+ dispatchAnalyticsEvent: dispatchAnalyticsEvent,
162
+ maxHeight: props.maxHeight,
163
+ minHeight: props.minHeight,
164
+ onSave: props.onSave ? handleSave : undefined,
165
+ onCancel: props.onCancel,
166
+ popupsMountPoint: props.popupsMountPoint,
167
+ popupsBoundariesElement: props.popupsBoundariesElement,
168
+ popupsScrollableElement: props.popupsScrollableElement,
169
+ contentComponents: config.contentComponents,
170
+ primaryToolbarComponents: config.primaryToolbarComponents,
171
+ primaryToolbarIconBefore: props.primaryToolbarIconBefore,
172
+ secondaryToolbarComponents: config.secondaryToolbarComponents,
173
+ customContentComponents: props.contentComponents,
174
+ customPrimaryToolbarComponents: props.primaryToolbarComponents,
175
+ customSecondaryToolbarComponents: props.secondaryToolbarComponents,
176
+ contextPanel: props.contextPanel,
177
+ collabEdit: props.collabEdit,
178
+ persistScrollGutter: props.persistScrollGutter,
179
+ enableToolbarMinWidth: ((_props$featureFlags3 = props.featureFlags) === null || _props$featureFlags3 === void 0 ? void 0 : _props$featureFlags3.toolbarMinWidthOverflow) != null ? !!((_props$featureFlags4 = props.featureFlags) !== null && _props$featureFlags4 !== void 0 && _props$featureFlags4.toolbarMinWidthOverflow) : props.allowUndoRedoButtons,
180
+ useStickyToolbar: props.useStickyToolbar,
181
+ featureFlags: featureFlags,
182
+ pluginHooks: config.pluginHooks
183
+ }));
184
+ }
129
185
  }), jsx(PortalRenderer, null))))))));
130
186
  });
131
187
  function ReactEditorViewContextWrapper(props) {
@@ -0,0 +1,7 @@
1
+ import { FULL_WIDTH_MODE } from '@atlaskit/editor-common/analytics';
2
+ export var formatFullWidthAppearance = function formatFullWidthAppearance(appearance) {
3
+ if (appearance === 'full-width') {
4
+ return FULL_WIDTH_MODE.FULL_WIDTH;
5
+ }
6
+ return FULL_WIDTH_MODE.FIXED_WIDTH;
7
+ };
@@ -0,0 +1,13 @@
1
+ import { browser } from '@atlaskit/editor-common/browser';
2
+ export function getUAPrefix() {
3
+ if (browser.chrome) {
4
+ return 'ua-chrome';
5
+ } else if (browser.ie) {
6
+ return 'ua-ie';
7
+ } else if (browser.gecko) {
8
+ return 'ua-firefox';
9
+ } else if (browser.safari) {
10
+ return 'ua-safari';
11
+ }
12
+ return '';
13
+ }
@@ -0,0 +1,38 @@
1
+ import { TextSelection } from '@atlaskit/editor-prosemirror/state';
2
+ export function handleEditorFocus(view) {
3
+ if (view.hasFocus()) {
4
+ return;
5
+ }
6
+ return window.setTimeout(function () {
7
+ if (view.hasFocus()) {
8
+ return;
9
+ }
10
+ if (!window.getSelection) {
11
+ view.focus();
12
+ return;
13
+ }
14
+ var domSelection = window.getSelection();
15
+ if (!domSelection || domSelection.rangeCount === 0) {
16
+ view.focus();
17
+ return;
18
+ }
19
+ var range = domSelection.getRangeAt(0);
20
+ // if selection is outside editor focus and exit
21
+ if (range.startContainer.contains(view.dom)) {
22
+ view.focus();
23
+ return;
24
+ }
25
+ // set cursor/selection and focus
26
+ var anchor = view.posAtDOM(range.startContainer, range.startOffset);
27
+ var head = view.posAtDOM(range.endContainer, range.endOffset);
28
+ // if anchor or head < 0 focus and exit
29
+ if (anchor < 0 || head < 0) {
30
+ view.focus();
31
+ return;
32
+ }
33
+ var selection = TextSelection.create(view.state.doc, anchor, head);
34
+ var tr = view.state.tr.setSelection(selection);
35
+ view.dispatch(tr);
36
+ view.focus();
37
+ }, 0);
38
+ }