@atlaskit/editor-core 219.5.0 → 219.6.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.
Files changed (33) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/dist/cjs/create-editor/ReactEditorView.js +45 -0
  3. package/dist/cjs/create-editor/create-editor.js +6 -1
  4. package/dist/cjs/create-editor/filter-plugins-for-reconfigure.js +49 -0
  5. package/dist/cjs/test-utils.js +3 -1
  6. package/dist/cjs/ui/Appearance/FullPage/FullPageToolbarNext.js +6 -49
  7. package/dist/cjs/ui/EditorContentContainer/EditorContentContainer-compiled.compiled.css +198 -1
  8. package/dist/cjs/ui/EditorContentContainer/EditorContentContainer-compiled.js +19 -14
  9. package/dist/cjs/ui/PluginSlot/mount-plugin-hooks.js +10 -3
  10. package/dist/cjs/version-wrapper.js +1 -1
  11. package/dist/es2019/create-editor/ReactEditorView.js +43 -1
  12. package/dist/es2019/create-editor/create-editor.js +6 -1
  13. package/dist/es2019/create-editor/filter-plugins-for-reconfigure.js +35 -0
  14. package/dist/es2019/test-utils.js +3 -1
  15. package/dist/es2019/ui/Appearance/FullPage/FullPageToolbarNext.js +4 -46
  16. package/dist/es2019/ui/EditorContentContainer/EditorContentContainer-compiled.compiled.css +198 -1
  17. package/dist/es2019/ui/EditorContentContainer/EditorContentContainer-compiled.js +21 -16
  18. package/dist/es2019/ui/PluginSlot/mount-plugin-hooks.js +18 -9
  19. package/dist/es2019/version-wrapper.js +1 -1
  20. package/dist/esm/create-editor/ReactEditorView.js +45 -0
  21. package/dist/esm/create-editor/create-editor.js +6 -1
  22. package/dist/esm/create-editor/filter-plugins-for-reconfigure.js +43 -0
  23. package/dist/esm/test-utils.js +3 -1
  24. package/dist/esm/ui/Appearance/FullPage/FullPageToolbarNext.js +6 -49
  25. package/dist/esm/ui/EditorContentContainer/EditorContentContainer-compiled.compiled.css +198 -1
  26. package/dist/esm/ui/EditorContentContainer/EditorContentContainer-compiled.js +21 -16
  27. package/dist/esm/ui/PluginSlot/mount-plugin-hooks.js +10 -3
  28. package/dist/esm/version-wrapper.js +1 -1
  29. package/dist/types/create-editor/filter-plugins-for-reconfigure.d.ts +19 -0
  30. package/dist/types/ui/PluginSlot/mount-plugin-hooks.d.ts +2 -2
  31. package/dist/types-ts4.5/create-editor/filter-plugins-for-reconfigure.d.ts +19 -0
  32. package/dist/types-ts4.5/ui/PluginSlot/mount-plugin-hooks.d.ts +2 -2
  33. package/package.json +13 -10
package/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  # @atlaskit/editor-core
2
2
 
3
+ ## 219.6.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`80e6f41ddfce4`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/80e6f41ddfce4) -
8
+ Reconcile editor plugin set on reconfigureState across preset switches: drop plugins whose schema
9
+ nodes/marks aren't in the current schema, and evict plugins from the injection API that aren't
10
+ part of the new preset. Behind feature gate `platform_editor_reconfigure_filter_plugins`.
11
+
12
+ Adds the `NamedReactHookFactory` type to `@atlaskit/editor-common/types` and annotates each
13
+ plugin's `usePluginHook` with its plugin name in `processPluginsList` so `MountPluginHooks` can
14
+ key React fibers stably per plugin. The shape of `EditorConfig.pluginHooks` is unchanged
15
+ (`ReactHookFactory[]`); the annotation is a non-breaking additive property on the function value
16
+ that falls back to the array index when absent.
17
+
18
+ ### Patch Changes
19
+
20
+ - Updated dependencies
21
+
22
+ ## 219.5.1
23
+
24
+ ### Patch Changes
25
+
26
+ - [`8db6f0336d199`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/8db6f0336d199) -
27
+ Cleans up experiment platform_editor_toolbar_two_stage_hydration
28
+ - Updated dependencies
29
+
3
30
  ## 219.5.0
4
31
 
5
32
  ### Minor Changes
@@ -49,6 +49,7 @@ var _consts = require("./consts");
49
49
  var _createEditor = require("./create-editor");
50
50
  var _createPluginsList = _interopRequireDefault(require("./create-plugins-list"));
51
51
  var _createSchema = require("./create-schema");
52
+ var _filterPluginsForReconfigure = require("./filter-plugins-for-reconfigure");
52
53
  var _messages = require("./messages");
53
54
  var _focusEditorElement = require("./ReactEditorView/focusEditorElement");
54
55
  var _getUAPrefix = require("./ReactEditorView/getUAPrefix");
@@ -362,6 +363,10 @@ function ReactEditorView(props) {
362
363
  var _useState = (0, _react.useState)(0),
363
364
  _useState2 = (0, _slicedToArray2.default)(_useState, 2),
364
365
  bumpConfigVersion = _useState2[1];
366
+
367
+ // Preset reference last processed by the schema/API reconciliation below.
368
+ // Used to skip that work when reconfigure is called with the same preset.
369
+ var lastFilteredPresetRef = (0, _react.useRef)(null);
365
370
  var reconfigureState = (0, _react.useCallback)(function (props) {
366
371
  if (!viewRef.current) {
367
372
  return;
@@ -371,7 +376,47 @@ function ReactEditorView(props) {
371
376
  // so we blur here to stop ProseMirror from trying to apply selection to detached nodes or
372
377
  // nodes that haven't been re-rendered to the document yet.
373
378
  blur();
379
+
380
+ // Snapshot plugin names registered before createPluginsList runs, so
381
+ // we can tell which plugins are newly added by the new preset vs.
382
+ // which ones already coexisted with the current schema.
383
+ var previousPluginNames = new Set(pluginInjectionAPI.current.getRegisteredPluginNames());
374
384
  var editorPlugins = (0, _createPluginsList.default)(props.preset, 'allowBlockType' in props.editorProps ? props.editorProps : {}, pluginInjectionAPI.current);
385
+
386
+ // `state.reconfigure` keeps the original schema, so switching presets
387
+ // can leave the editor inconsistent in two ways:
388
+ // 1. The new preset may add plugins that reference schema nodes or
389
+ // marks the original schema doesn't have.
390
+ // 2. Plugins registered by a previous preset can linger in the
391
+ // injection API even when the new preset doesn't re-register
392
+ // them, so listeners still fire against a state that no longer
393
+ // has their pmPlugin.
394
+ if (lastFilteredPresetRef.current !== props.preset && (0, _platformFeatureFlags.fg)('platform_editor_reconfigure_filter_plugins')) {
395
+ var _filterPluginsForReco = (0, _filterPluginsForReconfigure.filterPluginsForReconfigure)(editorPlugins, viewRef.current.state.schema, previousPluginNames),
396
+ kept = _filterPluginsForReco.kept,
397
+ dropped = _filterPluginsForReco.dropped;
398
+ editorPlugins = kept;
399
+
400
+ // Reconcile the injection API with the post-filter plugin set.
401
+ // This evicts both the plugins we just dropped above (re-registered
402
+ // by createPluginsList but no longer in editorPlugins) AND any
403
+ // plugin from a previous preset that the new preset doesn't
404
+ // re-register.
405
+ var keptPluginNames = new Set(editorPlugins.map(function (p) {
406
+ return p === null || p === void 0 ? void 0 : p.name;
407
+ }).filter(function (n) {
408
+ return Boolean(n);
409
+ }));
410
+ var evictedFromApi = pluginInjectionAPI.current.retainPlugins(keptPluginNames);
411
+ if (dropped.length > 0 || evictedFromApi.length > 0) {
412
+ // eslint-disable-next-line no-console
413
+ console.warn('[reconfigureState] Cleanup summary:', {
414
+ dropped: dropped,
415
+ evictedFromApi: evictedFromApi
416
+ });
417
+ }
418
+ lastFilteredPresetRef.current = props.preset;
419
+ }
375
420
  config.current = (0, _createEditor.processPluginsList)(editorPlugins);
376
421
  if ((0, _expValEquals.expValEquals)('platform_editor_appearance_shared_state', 'isEnabled', true)) {
377
422
  var _config$current$pmPlu2;
@@ -72,7 +72,12 @@ function processPluginsList(plugins) {
72
72
  acc.contentComponents.push(plugin.contentComponent);
73
73
  }
74
74
  if (plugin.usePluginHook) {
75
- acc.pluginHooks.push(plugin.usePluginHook);
75
+ // Wrap with .bind(null) so we can annotate the function with the
76
+ // plugin name without mutating the plugin's original hook reference.
77
+ // MountPluginHooks reads `pluginName` to derive a stable React key.
78
+ var named = plugin.usePluginHook.bind(null);
79
+ named.pluginName = plugin.name;
80
+ acc.pluginHooks.push(named);
76
81
  }
77
82
  if (plugin.primaryToolbarComponent) {
78
83
  acc.primaryToolbarComponents.push(plugin.primaryToolbarComponent);
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.filterPluginsForReconfigure = filterPluginsForReconfigure;
7
+ /**
8
+ * Used by `reconfigureState` to drop plugins from a freshly-built preset that
9
+ * declare schema nodes/marks the current schema doesn't have. Plugins already
10
+ * present before this reconfigure are kept untouched: they have demonstrably
11
+ * coexisted with the schema (e.g. tests that mock a stripped-down schema), so
12
+ * removing them now would be a regression.
13
+ */
14
+ function filterPluginsForReconfigure(editorPlugins, schema, previousPluginNames) {
15
+ var availableNodeNames = new Set(Object.keys(schema.nodes));
16
+ var availableMarkNames = new Set(Object.keys(schema.marks));
17
+ var dropped = [];
18
+ var kept = editorPlugins.filter(function (plugin) {
19
+ if (!plugin) {
20
+ return false;
21
+ }
22
+ if (previousPluginNames.has(plugin.name)) {
23
+ return true;
24
+ }
25
+ var missingNodes = plugin.nodes ? plugin.nodes().map(function (n) {
26
+ return n.name;
27
+ }).filter(function (n) {
28
+ return !availableNodeNames.has(n);
29
+ }) : [];
30
+ var missingMarks = plugin.marks ? plugin.marks().map(function (m) {
31
+ return m.name;
32
+ }).filter(function (m) {
33
+ return !availableMarkNames.has(m);
34
+ }) : [];
35
+ if (missingNodes.length > 0 || missingMarks.length > 0) {
36
+ dropped.push({
37
+ name: plugin.name,
38
+ missingNodes: missingNodes,
39
+ missingMarks: missingMarks
40
+ });
41
+ return false;
42
+ }
43
+ return true;
44
+ });
45
+ return {
46
+ kept: kept,
47
+ dropped: dropped
48
+ };
49
+ }
@@ -56,7 +56,9 @@ function lightProcessPluginsList(editorPlugins) {
56
56
  acc.contentComponents.push(editorPlugin.contentComponent);
57
57
  }
58
58
  if (editorPlugin.usePluginHook) {
59
- acc.pluginHooks.push(editorPlugin.usePluginHook);
59
+ var named = editorPlugin.usePluginHook.bind(null);
60
+ named.pluginName = editorPlugin.name;
61
+ acc.pluginHooks.push(named);
60
62
  }
61
63
  if (editorPlugin.onEditorViewStateUpdated) {
62
64
  acc.onEditorViewStateUpdatedCallbacks.push(editorPlugin.onEditorViewStateUpdated);
@@ -61,17 +61,6 @@ var SecondChildWrapper = function SecondChildWrapper(_ref3) {
61
61
  className: (0, _runtime.ax)([styles.secondChildWrapperOneLine])
62
62
  }, children);
63
63
  };
64
-
65
- /**
66
- * Placeholder component that reserves the toolbar's space during hydration
67
- * to prevent layout shift when the actual toolbar renders.
68
- */
69
- var ToolbarPlaceholder = function ToolbarPlaceholder() {
70
- return /*#__PURE__*/_react.default.createElement("div", {
71
- "data-testid": "ak-editor-main-toolbar-placeholder",
72
- className: (0, _runtime.ax)([styles.toolbarPlaceholder])
73
- });
74
- };
75
64
  var shouldShowToolbarContainer = function shouldShowToolbarContainer(toolbar, customPrimaryToolbarComponents) {
76
65
  return !!toolbar || !!customPrimaryToolbarComponents;
77
66
  };
@@ -125,41 +114,9 @@ var FullPageToolbarNext = exports.FullPageToolbarNext = function FullPageToolbar
125
114
  if (!shouldShowToolbarContainer(toolbar, customPrimaryToolbarComponents)) {
126
115
  return /*#__PURE__*/_react.default.createElement(ToolbarPortal, null, null);
127
116
  }
128
- if ((0, _expValEquals.expValEquals)('platform_editor_toolbar_two_stage_hydration', 'isEnabled', true)) {
129
- return /*#__PURE__*/_react.default.createElement(_contextPanel.ContextPanelConsumer, null, function (_ref6) {
130
- var ContextPanelWidth = _ref6.width;
131
- return /*#__PURE__*/_react.default.createElement(_uiMenu.ToolbarArrowKeyNavigationProvider, {
132
- editorView: editorView,
133
- childComponentSelector: "[data-testid='ak-editor-main-toolbar']",
134
- isShortcutToFocusToolbar: isShortcutToFocusToolbar,
135
- handleEscape: handleEscape,
136
- intl: intl
137
- }, /*#__PURE__*/_react.default.createElement(ToolbarPortal, null, /*#__PURE__*/_react.default.createElement(MainToolbarWrapper, {
138
- testId: "ak-editor-main-toolbar",
139
- showKeyline: showKeyline || ContextPanelWidth > 0
140
- }, beforeIcon && /*#__PURE__*/_react.default.createElement("div", {
141
- className: (0, _runtime.ax)([styles.mainToolbarIconBefore, styles.mainToolbarIconBeforeNew])
142
- }, beforeIcon), /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(FirstChildWrapper, null, /*#__PURE__*/_react.default.createElement(_ExcludeFromHydration.default, null, primaryToolbarDockingConfigEnabled && components && (0, _toolbar2.isToolbar)(toolbar) && editorView && !(0, _coreUtils.isSSR)() && /*#__PURE__*/_react.default.createElement(_Toolbar.ToolbarNext, {
143
- toolbar: toolbar,
144
- components: components,
145
- editorView: editorView,
146
- editorAPI: editorAPI,
147
- popupsMountPoint: mountPoint,
148
- editorAppearance: "full-page",
149
- isDisabled: disabled
150
- }))), /*#__PURE__*/_react.default.createElement(SecondChildWrapper, null, /*#__PURE__*/_react.default.createElement("div", {
151
- className: (0, _runtime.ax)([styles.customToolbarWrapperStyle])
152
- }, !!customPrimaryToolbarComponents && 'before' in customPrimaryToolbarComponents && /*#__PURE__*/_react.default.createElement("div", {
153
- "data-testid": 'before-primary-toolbar-components-plugin',
154
- className: (0, _runtime.ax)([styles.beforePrimaryToolbarComponents])
155
- }, customPrimaryToolbarComponents.before), !!customPrimaryToolbarComponents && 'after' in customPrimaryToolbarComponents ? customPrimaryToolbarComponents.after : customPrimaryToolbarComponents)), /*#__PURE__*/_react.default.createElement(_ToolbarPortal.ToolbarPortalMountPoint, null)))));
156
- });
157
- }
158
- return /*#__PURE__*/_react.default.createElement(_contextPanel.ContextPanelConsumer, null, function (_ref7) {
159
- var ContextPanelWidth = _ref7.width;
160
- return /*#__PURE__*/_react.default.createElement(_ExcludeFromHydration.default, {
161
- fallback: /*#__PURE__*/_react.default.createElement(ToolbarPlaceholder, null)
162
- }, /*#__PURE__*/_react.default.createElement(_uiMenu.ToolbarArrowKeyNavigationProvider, {
117
+ return /*#__PURE__*/_react.default.createElement(_contextPanel.ContextPanelConsumer, null, function (_ref6) {
118
+ var ContextPanelWidth = _ref6.width;
119
+ return /*#__PURE__*/_react.default.createElement(_uiMenu.ToolbarArrowKeyNavigationProvider, {
163
120
  editorView: editorView,
164
121
  childComponentSelector: "[data-testid='ak-editor-main-toolbar']",
165
122
  isShortcutToFocusToolbar: isShortcutToFocusToolbar,
@@ -170,7 +127,7 @@ var FullPageToolbarNext = exports.FullPageToolbarNext = function FullPageToolbar
170
127
  showKeyline: showKeyline || ContextPanelWidth > 0
171
128
  }, beforeIcon && /*#__PURE__*/_react.default.createElement("div", {
172
129
  className: (0, _runtime.ax)([styles.mainToolbarIconBefore, styles.mainToolbarIconBeforeNew])
173
- }, beforeIcon), /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(FirstChildWrapper, null, primaryToolbarDockingConfigEnabled && components && (0, _toolbar2.isToolbar)(toolbar) && editorView && !(0, _coreUtils.isSSR)() && /*#__PURE__*/_react.default.createElement(_Toolbar.ToolbarNext, {
130
+ }, beforeIcon), /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(FirstChildWrapper, null, /*#__PURE__*/_react.default.createElement(_ExcludeFromHydration.default, null, primaryToolbarDockingConfigEnabled && components && (0, _toolbar2.isToolbar)(toolbar) && editorView && !(0, _coreUtils.isSSR)() && /*#__PURE__*/_react.default.createElement(_Toolbar.ToolbarNext, {
174
131
  toolbar: toolbar,
175
132
  components: components,
176
133
  editorView: editorView,
@@ -178,11 +135,11 @@ var FullPageToolbarNext = exports.FullPageToolbarNext = function FullPageToolbar
178
135
  popupsMountPoint: mountPoint,
179
136
  editorAppearance: "full-page",
180
137
  isDisabled: disabled
181
- })), /*#__PURE__*/_react.default.createElement(SecondChildWrapper, null, /*#__PURE__*/_react.default.createElement("div", {
138
+ }))), /*#__PURE__*/_react.default.createElement(SecondChildWrapper, null, /*#__PURE__*/_react.default.createElement("div", {
182
139
  className: (0, _runtime.ax)([styles.customToolbarWrapperStyle])
183
140
  }, !!customPrimaryToolbarComponents && 'before' in customPrimaryToolbarComponents && /*#__PURE__*/_react.default.createElement("div", {
184
141
  "data-testid": 'before-primary-toolbar-components-plugin',
185
142
  className: (0, _runtime.ax)([styles.beforePrimaryToolbarComponents])
186
- }, customPrimaryToolbarComponents.before), !!customPrimaryToolbarComponents && 'after' in customPrimaryToolbarComponents ? customPrimaryToolbarComponents.after : customPrimaryToolbarComponents)), /*#__PURE__*/_react.default.createElement(_ToolbarPortal.ToolbarPortalMountPoint, null))))));
143
+ }, customPrimaryToolbarComponents.before), !!customPrimaryToolbarComponents && 'after' in customPrimaryToolbarComponents ? customPrimaryToolbarComponents.after : customPrimaryToolbarComponents)), /*#__PURE__*/_react.default.createElement(_ToolbarPortal.ToolbarPortalMountPoint, null)))));
187
144
  });
188
145
  };