@atlaskit/editor-core 219.7.2 → 219.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # @atlaskit/editor-core
2
2
 
3
+ ## 219.7.3
4
+
5
+ ### Patch Changes
6
+
7
+ - [`911ea1f065d0f`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/911ea1f065d0f) -
8
+ [EDITOR-6965] Rebuild the schema in `ReactEditorView.reconfigureState` when toggling presets under
9
+ the `cc-markdown-mode` experiment, so each preset declares its honest schema (rather than sharing
10
+ the schema from first mount via `state.reconfigure`). Skips the existing plugin-availability
11
+ filter (which would evaluate against the OLD schema and drop e.g. `extensionPlugin` on a
12
+ markdown→rich toggle), and re-seeds `collabInitialised: true` on the new state because the
13
+ Confluence NCS provider does not implement `getInitPayload` (so the rebind path in
14
+ `editor-plugin-collab-edit/initialize.ts` is skipped). Also makes
15
+ `extensionProviderToQuickInsertProvider.getItems` schema-aware so the `/` menu does not surface
16
+ extension items when the live schema cannot accept them.
17
+
3
18
  ## 219.7.2
4
19
 
5
20
  ### Patch Changes
@@ -28,6 +28,7 @@ var _processRawValue = require("@atlaskit/editor-common/process-raw-value");
28
28
  var _uiReact = require("@atlaskit/editor-common/ui-react");
29
29
  var _analytics2 = require("@atlaskit/editor-common/utils/analytics");
30
30
  var _document = require("@atlaskit/editor-common/utils/document");
31
+ var _model = require("@atlaskit/editor-prosemirror/model");
31
32
  var _state2 = require("@atlaskit/editor-prosemirror/state");
32
33
  var _view = require("@atlaskit/editor-prosemirror/view");
33
34
  var _editorSsrRenderer = require("@atlaskit/editor-ssr-renderer");
@@ -57,14 +58,45 @@ var _handleEditorFocus = require("./ReactEditorView/handleEditorFocus");
57
58
  var _useDispatchTransaction = require("./ReactEditorView/useDispatchTransaction");
58
59
  var _useFireFullWidthEvent = require("./ReactEditorView/useFireFullWidthEvent");
59
60
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
61
+ 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; }
62
+ 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) { (0, _defineProperty2.default)(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; }
60
63
  function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
61
64
  function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
62
- function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
63
- 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; }
64
- 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) { (0, _defineProperty2.default)(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; } // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
65
+ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
65
66
  var EDIT_AREA_ID = 'ak-editor-textarea';
66
67
  var SSR_TRACE_SEGMENT_NAME = 'reactEditorView';
67
68
  var bootStartTime = (0, _isPerformanceApiAvailable.isPerformanceAPIAvailable)() ? performance.now() : undefined;
69
+ // `markdown↔rich` toggles drop different node/mark sets, so the unique
70
+ // name set is enough to detect when a destructive rebuild is needed.
71
+ function sameNames(a, b) {
72
+ var setA = new Set(a);
73
+ var setB = new Set(b);
74
+ if (setA.size !== setB.size) {
75
+ return false;
76
+ }
77
+ var _iterator = _createForOfIteratorHelper(setA),
78
+ _step;
79
+ try {
80
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
81
+ var name = _step.value;
82
+ if (!setB.has(name)) {
83
+ return false;
84
+ }
85
+ }
86
+ } catch (err) {
87
+ _iterator.e(err);
88
+ } finally {
89
+ _iterator.f();
90
+ }
91
+ return true;
92
+ }
93
+ function schemaShapeChanged(current, next) {
94
+ return !sameNames(Object.keys(current.nodes), next.nodes.map(function (n) {
95
+ return n.name;
96
+ })) || !sameNames(Object.keys(current.marks), next.marks.map(function (m) {
97
+ return m.name;
98
+ }));
99
+ }
68
100
  function ReactEditorView(props) {
69
101
  var _pluginInjectionAPI$c, _media, _linking, _document$querySelect, _props$render, _props$render2;
70
102
  // Should be always the first statement in the component
@@ -364,9 +396,10 @@ function ReactEditorView(props) {
364
396
  _useState2 = (0, _slicedToArray2.default)(_useState, 2),
365
397
  bumpConfigVersion = _useState2[1];
366
398
 
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);
399
+ // Preset reference last processed by reconfigureState. Used to skip the
400
+ // destructive work (plugin filter, schema rebuild) when reconfigure is
401
+ // called with the same preset.
402
+ var lastProcessedPresetRef = (0, _react.useRef)(null);
370
403
  var reconfigureState = (0, _react.useCallback)(function (props) {
371
404
  if (!viewRef.current) {
372
405
  return;
@@ -383,6 +416,35 @@ function ReactEditorView(props) {
383
416
  var previousPluginNames = new Set(pluginInjectionAPI.current.getRegisteredPluginNames());
384
417
  var editorPlugins = (0, _createPluginsList.default)(props.preset, 'allowBlockType' in props.editorProps ? props.editorProps : {}, pluginInjectionAPI.current);
385
418
 
419
+ // Capture once, before either downstream block updates the ref —
420
+ // both the filter and the schema rebuild are destructive and only
421
+ // want to run when the preset has actually changed.
422
+ var presetChanged = lastProcessedPresetRef.current !== props.preset;
423
+
424
+ // Build a candidate config from the *unfiltered* plugin list so we can
425
+ // decide whether the schema rebuild path will run. Both the rebuild
426
+ // decision and the drop-filter decision below depend on this answer,
427
+ // so it has to be computed up-front.
428
+ var buildConfig = function buildConfig(plugins) {
429
+ var c = (0, _createEditor.processPluginsList)(plugins);
430
+ if ((0, _expValEquals.expValEquals)('platform_editor_appearance_shared_state', 'isEnabled', true)) {
431
+ var _c$pmPlugins;
432
+ (_c$pmPlugins = c.pmPlugins).push.apply(_c$pmPlugins, (0, _toConsumableArray2.default)(pluginInjectionAPI.current.getInternalPMPlugins()));
433
+ }
434
+ return c;
435
+ };
436
+ var nextConfig = buildConfig(editorPlugins);
437
+
438
+ // `state.reconfigure` preserves the original schema, so a preset
439
+ // toggle that should change schema (markdown↔rich) needs a fresh
440
+ // `EditorState`. Resets all plugin state including undo history.
441
+ //
442
+ // Compare schema *shape* (node + mark name sets) rather than preset
443
+ // identity: consumers commonly recreate the preset object on every
444
+ // parent re-render, and a destructive rebuild on a no-op identity
445
+ // change tears down all plugin state (e.g. unmounts the AI palette).
446
+ var shouldRebuildSchema = presetChanged && schemaShapeChanged(viewRef.current.state.schema, nextConfig) && (0, _expValEqualsNoExposure.expValEqualsNoExposure)('cc-markdown-mode', 'isEnabled', true);
447
+
386
448
  // `state.reconfigure` keeps the original schema, so switching presets
387
449
  // can leave the editor inconsistent in two ways:
388
450
  // 1. The new preset may add plugins that reference schema nodes or
@@ -391,17 +453,26 @@ function ReactEditorView(props) {
391
453
  // injection API even when the new preset doesn't re-register
392
454
  // them, so listeners still fire against a state that no longer
393
455
  // 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.
456
+ //
457
+ // When the schema is being rebuilt below, the new schema is built
458
+ // from the *unfiltered* plugin list — so dropping plugins whose
459
+ // nodes/marks the OLD schema lacks would wrongly remove the very
460
+ // plugins the rebuild is meant to admit. Skip the drop step in that
461
+ // case (purpose 1) but always reconcile the injection API
462
+ // (purpose 2). When NOT rebuilding, run both even under the
463
+ // `cc-markdown-mode` experiment, otherwise no-op preset identity
464
+ // changes would silently leave a broken plugin/schema mismatch.
465
+ if (presetChanged && (0, _platformFeatureFlags.fg)('platform_editor_reconfigure_filter_plugins')) {
466
+ var dropped = [];
467
+ if (!shouldRebuildSchema) {
468
+ var _result = (0, _filterPluginsForReconfigure.filterPluginsForReconfigure)(editorPlugins, viewRef.current.state.schema, previousPluginNames);
469
+ if (_result.dropped.length > 0) {
470
+ editorPlugins = _result.kept;
471
+ // Plugin list changed — rebuild candidate config to match.
472
+ nextConfig = buildConfig(editorPlugins);
473
+ }
474
+ dropped = _result.dropped;
475
+ }
405
476
  var keptPluginNames = new Set(editorPlugins.map(function (p) {
406
477
  return p === null || p === void 0 ? void 0 : p.name;
407
478
  }).filter(function (n) {
@@ -415,33 +486,77 @@ function ReactEditorView(props) {
415
486
  evictedFromApi: evictedFromApi
416
487
  });
417
488
  }
418
- lastFilteredPresetRef.current = props.preset;
419
- }
420
- config.current = (0, _createEditor.processPluginsList)(editorPlugins);
421
- if ((0, _expValEquals.expValEquals)('platform_editor_appearance_shared_state', 'isEnabled', true)) {
422
- var _config$current$pmPlu2;
423
- (_config$current$pmPlu2 = config.current.pmPlugins).push.apply(_config$current$pmPlu2, (0, _toConsumableArray2.default)(pluginInjectionAPI.current.getInternalPMPlugins()));
424
489
  }
490
+ config.current = nextConfig;
425
491
  var state = viewRef.current.state;
426
- var plugins = (0, _createEditor.createPMPlugins)({
427
- schema: state.schema,
428
- dispatch: dispatch,
429
- errorReporter: errorReporter,
430
- editorConfig: config.current,
431
- eventDispatcher: eventDispatcher,
432
- providerFactory: props.providerFactory,
433
- portalProviderAPI: props.portalProviderAPI,
434
- nodeViewPortalProviderAPI: props.nodeViewPortalProviderAPI,
435
- dispatchAnalyticsEvent: dispatchAnalyticsEvent,
436
- featureFlags: featureFlags,
437
- getIntl: function getIntl() {
438
- return props.intl;
439
- },
440
- onEditorStateUpdated: pluginInjectionAPI.current.onEditorViewUpdated
441
- });
442
- var newState = state.reconfigure({
443
- plugins: plugins
444
- });
492
+ var newState;
493
+ if (shouldRebuildSchema) {
494
+ var newSchema = (0, _createSchema.createSchema)(config.current);
495
+ var newDoc;
496
+ try {
497
+ newDoc = _model.Node.fromJSON(newSchema, state.doc.toJSON());
498
+ } catch (e) {
499
+ // eslint-disable-next-line no-console
500
+ console.error('[reconfigureState] Failed to migrate doc to new schema; resetting to empty doc', e);
501
+ var empty = newSchema.topNodeType.createAndFill();
502
+ if (!empty) {
503
+ throw new Error('reconfigureState: doc migration failed and new schema cannot create an empty top node');
504
+ }
505
+ newDoc = empty;
506
+ }
507
+ var newSelection;
508
+ try {
509
+ newSelection = _state2.Selection.fromJSON(newDoc, state.selection.toJSON());
510
+ } catch (_unused) {
511
+ // Old selection's positions / node types may not map onto the new schema.
512
+ newSelection = _state2.Selection.atStart(newDoc);
513
+ }
514
+ var plugins = (0, _createEditor.createPMPlugins)({
515
+ schema: newSchema,
516
+ dispatch: dispatch,
517
+ errorReporter: errorReporter,
518
+ editorConfig: config.current,
519
+ eventDispatcher: eventDispatcher,
520
+ providerFactory: props.providerFactory,
521
+ portalProviderAPI: props.portalProviderAPI,
522
+ nodeViewPortalProviderAPI: props.nodeViewPortalProviderAPI,
523
+ dispatchAnalyticsEvent: dispatchAnalyticsEvent,
524
+ featureFlags: featureFlags,
525
+ getIntl: function getIntl() {
526
+ return props.intl;
527
+ },
528
+ onEditorStateUpdated: pluginInjectionAPI.current.onEditorViewUpdated
529
+ });
530
+ newState = _state2.EditorState.create({
531
+ schema: newSchema,
532
+ doc: newDoc,
533
+ selection: newSelection,
534
+ plugins: plugins
535
+ });
536
+ } else {
537
+ var _plugins = (0, _createEditor.createPMPlugins)({
538
+ schema: state.schema,
539
+ dispatch: dispatch,
540
+ errorReporter: errorReporter,
541
+ editorConfig: config.current,
542
+ eventDispatcher: eventDispatcher,
543
+ providerFactory: props.providerFactory,
544
+ portalProviderAPI: props.portalProviderAPI,
545
+ nodeViewPortalProviderAPI: props.nodeViewPortalProviderAPI,
546
+ dispatchAnalyticsEvent: dispatchAnalyticsEvent,
547
+ featureFlags: featureFlags,
548
+ getIntl: function getIntl() {
549
+ return props.intl;
550
+ },
551
+ onEditorStateUpdated: pluginInjectionAPI.current.onEditorViewUpdated
552
+ });
553
+ newState = state.reconfigure({
554
+ plugins: _plugins
555
+ });
556
+ }
557
+ if (presetChanged) {
558
+ lastProcessedPresetRef.current = props.preset;
559
+ }
445
560
 
446
561
  // need to update the state first so when the view builds the nodeviews it is
447
562
  // using the latest plugins
@@ -450,9 +565,32 @@ function ReactEditorView(props) {
450
565
  state: newState
451
566
  }));
452
567
 
568
+ // The new collab-edit plugin instance starts with `isReady=false`.
569
+ // The rebind path in editor-plugin-collab-edit's initialize.ts is
570
+ // gated on `provider.getInitPayload`, which the Confluence NCS
571
+ // provider does not implement, so the placeholder spinner would
572
+ // never clear. Re-seeding here is safe: the prior state must have
573
+ // had `isReady=true` for the user to have triggered the toggle.
574
+ //
575
+ // Must run AFTER `view.update({ state: newState })`: that call resets
576
+ // the view's state to the captured `newState` reference, so a
577
+ // dispatch placed before it would advance `view.state` to a value
578
+ // that `update` then silently overwrites — discarding the meta and
579
+ // leaving `isReady=false`.
580
+ if (shouldRebuildSchema) {
581
+ // `state.collabEditPlugin$` is the property PM derives from the
582
+ // collab plugin's PluginKey; cast through `unknown` to read it.
583
+ var collabState = viewRef.current.state.collabEditPlugin$;
584
+ if (collabState && collabState.isReady !== true) {
585
+ viewRef.current.dispatch(viewRef.current.state.tr.setMeta('collabInitialised', true));
586
+ }
587
+ }
588
+
453
589
  // EDITOR-6702: gated until we have a broader gate; reconfigure is a
454
590
  // low-level path so use NoExposure.
455
591
  if ((0, _expValEqualsNoExposure.expValEqualsNoExposure)('cc-markdown-mode', 'isEnabled', true)) {
592
+ // Force a render so PluginSlot picks up the new preset's content
593
+ // components against the new state.
456
594
  bumpConfigVersion(function (v) {
457
595
  return v + 1;
458
596
  });
@@ -695,19 +833,19 @@ function ReactEditorView(props) {
695
833
  return function () {
696
834
  if (scrollElement.current) {
697
835
  // eslint-disable-next-line react-hooks/exhaustive-deps
698
- var _iterator = _createForOfIteratorHelper(possibleListeners.current),
699
- _step;
836
+ var _iterator2 = _createForOfIteratorHelper(possibleListeners.current),
837
+ _step2;
700
838
  try {
701
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
839
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
702
840
  var _scrollElement$curren;
703
- var possibleListener = _step.value;
841
+ var possibleListener = _step2.value;
704
842
  // eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
705
843
  (_scrollElement$curren = scrollElement.current) === null || _scrollElement$curren === void 0 || _scrollElement$curren.removeEventListener.apply(_scrollElement$curren, (0, _toConsumableArray2.default)(possibleListener));
706
844
  }
707
845
  } catch (err) {
708
- _iterator.e(err);
846
+ _iterator2.e(err);
709
847
  } finally {
710
- _iterator.f();
848
+ _iterator2.f();
711
849
  }
712
850
  }
713
851
  scrollElement.current = null;
@@ -719,19 +857,19 @@ function ReactEditorView(props) {
719
857
  scrollElement.current = document.querySelector('[data-editor-scroll-container]');
720
858
  var cleanupListeners = function cleanupListeners() {
721
859
  // eslint-disable-next-line react-hooks/exhaustive-deps
722
- var _iterator2 = _createForOfIteratorHelper(possibleListeners.current),
723
- _step2;
860
+ var _iterator3 = _createForOfIteratorHelper(possibleListeners.current),
861
+ _step3;
724
862
  try {
725
- for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
863
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
726
864
  var _scrollElement$curren2;
727
- var possibleListener = _step2.value;
865
+ var possibleListener = _step3.value;
728
866
  // eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
729
867
  (_scrollElement$curren2 = scrollElement.current) === null || _scrollElement$curren2 === void 0 || _scrollElement$curren2.removeEventListener.apply(_scrollElement$curren2, (0, _toConsumableArray2.default)(possibleListener));
730
868
  }
731
869
  } catch (err) {
732
- _iterator2.e(err);
870
+ _iterator3.e(err);
733
871
  } finally {
734
- _iterator2.f();
872
+ _iterator3.f();
735
873
  }
736
874
  };
737
875
  if (scrollElement.current) {
@@ -18,6 +18,11 @@ var _analytics2 = require("@atlaskit/editor-common/utils/analytics");
18
18
  var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
19
19
  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; }
20
20
  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) { (0, _defineProperty2.default)(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; }
21
+ // Structural shape of the markdown-mode plugin's slice of the injection API.
22
+ // Used to read `isMarkdownMode` without importing the `MarkdownModePlugin`
23
+ // type — that would pull editor-plugin-markdown-mode into editor-core's
24
+ // dependency graph and force every consuming product to rebuild.
25
+
21
26
  /**
22
27
  * Utils to send analytics event when a extension is inserted using quickInsert
23
28
  */
@@ -89,6 +94,15 @@ function _extensionProviderToQuickInsertProvider() {
89
94
  extensions = _context.sent;
90
95
  return _context.abrupt("return", {
91
96
  getItems: function getItems() {
97
+ var _apiRef$current;
98
+ // `extensionProvider` is supplied independently of the preset, so
99
+ // suppress its items in markdown mode where rich-only content cannot
100
+ // be inserted. See `MarkdownModeReader` above for why this is read
101
+ // via a structural cast rather than the typed plugin API.
102
+ var isMarkdownMode = (_apiRef$current = apiRef.current) === null || _apiRef$current === void 0 || (_apiRef$current = _apiRef$current.markdownMode) === null || _apiRef$current === void 0 || (_apiRef$current = _apiRef$current.sharedState.currentState()) === null || _apiRef$current === void 0 ? void 0 : _apiRef$current.isMarkdownMode;
103
+ if (isMarkdownMode) {
104
+ return Promise.resolve([]);
105
+ }
92
106
  var quickInsertItems = (0, _extensions.getQuickInsertItemsFromModule)(extensions, function (item) {
93
107
  var Icon = (0, _reactLoadable.default)({
94
108
  loader: item.icon,
@@ -120,8 +134,8 @@ function _extensionProviderToQuickInsertProvider() {
120
134
  isDisabledOffline: true,
121
135
  action: function action(insert, state, source) {
122
136
  if (typeof item.node === 'function') {
123
- var _apiRef$current;
124
- var extensionAPI = apiRef === null || apiRef === void 0 || (_apiRef$current = apiRef.current) === null || _apiRef$current === void 0 || (_apiRef$current = _apiRef$current.extension) === null || _apiRef$current === void 0 || (_apiRef$current = _apiRef$current.actions) === null || _apiRef$current === void 0 ? void 0 : _apiRef$current.api();
137
+ var _apiRef$current2;
138
+ var extensionAPI = apiRef === null || apiRef === void 0 || (_apiRef$current2 = apiRef.current) === null || _apiRef$current2 === void 0 || (_apiRef$current2 = _apiRef$current2.extension) === null || _apiRef$current2 === void 0 || (_apiRef$current2 = _apiRef$current2.actions) === null || _apiRef$current2 === void 0 ? void 0 : _apiRef$current2.api();
125
139
  // While this should only run when the extension some setups of editor
126
140
  // may not have the extension API
127
141
  if (extensionAPI) {
@@ -5,4 +5,4 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.version = exports.name = void 0;
7
7
  var name = exports.name = "@atlaskit/editor-core";
8
- var version = exports.version = "219.7.1";
8
+ var version = exports.version = "219.7.2";
@@ -17,6 +17,7 @@ import { processRawValue, processRawValueWithoutValidation } from '@atlaskit/edi
17
17
  import { ReactEditorViewContext } from '@atlaskit/editor-common/ui-react';
18
18
  import { analyticsEventKey, getAnalyticsEventSeverity } from '@atlaskit/editor-common/utils/analytics';
19
19
  import { isEmptyDocument } from '@atlaskit/editor-common/utils/document';
20
+ import { Node as PMNode } from '@atlaskit/editor-prosemirror/model';
20
21
  import { EditorState, Selection, TextSelection } from '@atlaskit/editor-prosemirror/state';
21
22
  import { EditorView } from '@atlaskit/editor-prosemirror/view';
22
23
  import { EditorSSRRenderer } from '@atlaskit/editor-ssr-renderer';
@@ -48,6 +49,24 @@ import { useFireFullWidthEvent } from './ReactEditorView/useFireFullWidthEvent';
48
49
  const EDIT_AREA_ID = 'ak-editor-textarea';
49
50
  const SSR_TRACE_SEGMENT_NAME = 'reactEditorView';
50
51
  const bootStartTime = isPerformanceAPIAvailable() ? performance.now() : undefined;
52
+ // `markdown↔rich` toggles drop different node/mark sets, so the unique
53
+ // name set is enough to detect when a destructive rebuild is needed.
54
+ function sameNames(a, b) {
55
+ const setA = new Set(a);
56
+ const setB = new Set(b);
57
+ if (setA.size !== setB.size) {
58
+ return false;
59
+ }
60
+ for (const name of setA) {
61
+ if (!setB.has(name)) {
62
+ return false;
63
+ }
64
+ }
65
+ return true;
66
+ }
67
+ function schemaShapeChanged(current, next) {
68
+ return !sameNames(Object.keys(current.nodes), next.nodes.map(n => n.name)) || !sameNames(Object.keys(current.marks), next.marks.map(m => m.name));
69
+ }
51
70
  export function ReactEditorView(props) {
52
71
  var _pluginInjectionAPI$c, _pluginInjectionAPI$c2, _pluginInjectionAPI$c3, _media, _linking, _linking$smartLinks, _document$querySelect, _props$render, _props$render2;
53
72
  // Should be always the first statement in the component
@@ -338,9 +357,10 @@ export function ReactEditorView(props) {
338
357
  // components from the rebuilt preset).
339
358
  const [, bumpConfigVersion] = useState(0);
340
359
 
341
- // Preset reference last processed by the schema/API reconciliation below.
342
- // Used to skip that work when reconfigure is called with the same preset.
343
- const lastFilteredPresetRef = useRef(null);
360
+ // Preset reference last processed by reconfigureState. Used to skip the
361
+ // destructive work (plugin filter, schema rebuild) when reconfigure is
362
+ // called with the same preset.
363
+ const lastProcessedPresetRef = useRef(null);
344
364
  const reconfigureState = useCallback(props => {
345
365
  if (!viewRef.current) {
346
366
  return;
@@ -357,6 +377,34 @@ export function ReactEditorView(props) {
357
377
  const previousPluginNames = new Set(pluginInjectionAPI.current.getRegisteredPluginNames());
358
378
  let editorPlugins = createPluginsList(props.preset, 'allowBlockType' in props.editorProps ? props.editorProps : {}, pluginInjectionAPI.current);
359
379
 
380
+ // Capture once, before either downstream block updates the ref —
381
+ // both the filter and the schema rebuild are destructive and only
382
+ // want to run when the preset has actually changed.
383
+ const presetChanged = lastProcessedPresetRef.current !== props.preset;
384
+
385
+ // Build a candidate config from the *unfiltered* plugin list so we can
386
+ // decide whether the schema rebuild path will run. Both the rebuild
387
+ // decision and the drop-filter decision below depend on this answer,
388
+ // so it has to be computed up-front.
389
+ const buildConfig = plugins => {
390
+ const c = processPluginsList(plugins);
391
+ if (expValEquals('platform_editor_appearance_shared_state', 'isEnabled', true)) {
392
+ c.pmPlugins.push(...pluginInjectionAPI.current.getInternalPMPlugins());
393
+ }
394
+ return c;
395
+ };
396
+ let nextConfig = buildConfig(editorPlugins);
397
+
398
+ // `state.reconfigure` preserves the original schema, so a preset
399
+ // toggle that should change schema (markdown↔rich) needs a fresh
400
+ // `EditorState`. Resets all plugin state including undo history.
401
+ //
402
+ // Compare schema *shape* (node + mark name sets) rather than preset
403
+ // identity: consumers commonly recreate the preset object on every
404
+ // parent re-render, and a destructive rebuild on a no-op identity
405
+ // change tears down all plugin state (e.g. unmounts the AI palette).
406
+ const shouldRebuildSchema = presetChanged && schemaShapeChanged(viewRef.current.state.schema, nextConfig) && expValEqualsNoExposure('cc-markdown-mode', 'isEnabled', true);
407
+
360
408
  // `state.reconfigure` keeps the original schema, so switching presets
361
409
  // can leave the editor inconsistent in two ways:
362
410
  // 1. The new preset may add plugins that reference schema nodes or
@@ -365,18 +413,26 @@ export function ReactEditorView(props) {
365
413
  // injection API even when the new preset doesn't re-register
366
414
  // them, so listeners still fire against a state that no longer
367
415
  // has their pmPlugin.
368
- if (lastFilteredPresetRef.current !== props.preset && fg('platform_editor_reconfigure_filter_plugins')) {
369
- const {
370
- kept,
371
- dropped
372
- } = filterPluginsForReconfigure(editorPlugins, viewRef.current.state.schema, previousPluginNames);
373
- editorPlugins = kept;
374
-
375
- // Reconcile the injection API with the post-filter plugin set.
376
- // This evicts both the plugins we just dropped above (re-registered
377
- // by createPluginsList but no longer in editorPlugins) AND any
378
- // plugin from a previous preset that the new preset doesn't
379
- // re-register.
416
+ //
417
+ // When the schema is being rebuilt below, the new schema is built
418
+ // from the *unfiltered* plugin list — so dropping plugins whose
419
+ // nodes/marks the OLD schema lacks would wrongly remove the very
420
+ // plugins the rebuild is meant to admit. Skip the drop step in that
421
+ // case (purpose 1) but always reconcile the injection API
422
+ // (purpose 2). When NOT rebuilding, run both — even under the
423
+ // `cc-markdown-mode` experiment, otherwise no-op preset identity
424
+ // changes would silently leave a broken plugin/schema mismatch.
425
+ if (presetChanged && fg('platform_editor_reconfigure_filter_plugins')) {
426
+ let dropped = [];
427
+ if (!shouldRebuildSchema) {
428
+ const result = filterPluginsForReconfigure(editorPlugins, viewRef.current.state.schema, previousPluginNames);
429
+ if (result.dropped.length > 0) {
430
+ editorPlugins = result.kept;
431
+ // Plugin list changed — rebuild candidate config to match.
432
+ nextConfig = buildConfig(editorPlugins);
433
+ }
434
+ dropped = result.dropped;
435
+ }
380
436
  const keptPluginNames = new Set(editorPlugins.map(p => p === null || p === void 0 ? void 0 : p.name).filter(n => Boolean(n)));
381
437
  const evictedFromApi = pluginInjectionAPI.current.retainPlugins(keptPluginNames);
382
438
  if (dropped.length > 0 || evictedFromApi.length > 0) {
@@ -386,30 +442,73 @@ export function ReactEditorView(props) {
386
442
  evictedFromApi
387
443
  });
388
444
  }
389
- lastFilteredPresetRef.current = props.preset;
390
- }
391
- config.current = processPluginsList(editorPlugins);
392
- if (expValEquals('platform_editor_appearance_shared_state', 'isEnabled', true)) {
393
- config.current.pmPlugins.push(...pluginInjectionAPI.current.getInternalPMPlugins());
394
445
  }
446
+ config.current = nextConfig;
395
447
  const state = viewRef.current.state;
396
- const plugins = createPMPlugins({
397
- schema: state.schema,
398
- dispatch: dispatch,
399
- errorReporter: errorReporter,
400
- editorConfig: config.current,
401
- eventDispatcher: eventDispatcher,
402
- providerFactory: props.providerFactory,
403
- portalProviderAPI: props.portalProviderAPI,
404
- nodeViewPortalProviderAPI: props.nodeViewPortalProviderAPI,
405
- dispatchAnalyticsEvent: dispatchAnalyticsEvent,
406
- featureFlags,
407
- getIntl: () => props.intl,
408
- onEditorStateUpdated: pluginInjectionAPI.current.onEditorViewUpdated
409
- });
410
- const newState = state.reconfigure({
411
- plugins: plugins
412
- });
448
+ let newState;
449
+ if (shouldRebuildSchema) {
450
+ const newSchema = createSchema(config.current);
451
+ let newDoc;
452
+ try {
453
+ newDoc = PMNode.fromJSON(newSchema, state.doc.toJSON());
454
+ } catch (e) {
455
+ // eslint-disable-next-line no-console
456
+ console.error('[reconfigureState] Failed to migrate doc to new schema; resetting to empty doc', e);
457
+ const empty = newSchema.topNodeType.createAndFill();
458
+ if (!empty) {
459
+ throw new Error('reconfigureState: doc migration failed and new schema cannot create an empty top node');
460
+ }
461
+ newDoc = empty;
462
+ }
463
+ let newSelection;
464
+ try {
465
+ newSelection = Selection.fromJSON(newDoc, state.selection.toJSON());
466
+ } catch {
467
+ // Old selection's positions / node types may not map onto the new schema.
468
+ newSelection = Selection.atStart(newDoc);
469
+ }
470
+ const plugins = createPMPlugins({
471
+ schema: newSchema,
472
+ dispatch: dispatch,
473
+ errorReporter: errorReporter,
474
+ editorConfig: config.current,
475
+ eventDispatcher: eventDispatcher,
476
+ providerFactory: props.providerFactory,
477
+ portalProviderAPI: props.portalProviderAPI,
478
+ nodeViewPortalProviderAPI: props.nodeViewPortalProviderAPI,
479
+ dispatchAnalyticsEvent: dispatchAnalyticsEvent,
480
+ featureFlags,
481
+ getIntl: () => props.intl,
482
+ onEditorStateUpdated: pluginInjectionAPI.current.onEditorViewUpdated
483
+ });
484
+ newState = EditorState.create({
485
+ schema: newSchema,
486
+ doc: newDoc,
487
+ selection: newSelection,
488
+ plugins: plugins
489
+ });
490
+ } else {
491
+ const plugins = createPMPlugins({
492
+ schema: state.schema,
493
+ dispatch: dispatch,
494
+ errorReporter: errorReporter,
495
+ editorConfig: config.current,
496
+ eventDispatcher: eventDispatcher,
497
+ providerFactory: props.providerFactory,
498
+ portalProviderAPI: props.portalProviderAPI,
499
+ nodeViewPortalProviderAPI: props.nodeViewPortalProviderAPI,
500
+ dispatchAnalyticsEvent: dispatchAnalyticsEvent,
501
+ featureFlags,
502
+ getIntl: () => props.intl,
503
+ onEditorStateUpdated: pluginInjectionAPI.current.onEditorViewUpdated
504
+ });
505
+ newState = state.reconfigure({
506
+ plugins: plugins
507
+ });
508
+ }
509
+ if (presetChanged) {
510
+ lastProcessedPresetRef.current = props.preset;
511
+ }
413
512
 
414
513
  // need to update the state first so when the view builds the nodeviews it is
415
514
  // using the latest plugins
@@ -419,9 +518,32 @@ export function ReactEditorView(props) {
419
518
  state: newState
420
519
  });
421
520
 
521
+ // The new collab-edit plugin instance starts with `isReady=false`.
522
+ // The rebind path in editor-plugin-collab-edit's initialize.ts is
523
+ // gated on `provider.getInitPayload`, which the Confluence NCS
524
+ // provider does not implement, so the placeholder spinner would
525
+ // never clear. Re-seeding here is safe: the prior state must have
526
+ // had `isReady=true` for the user to have triggered the toggle.
527
+ //
528
+ // Must run AFTER `view.update({ state: newState })`: that call resets
529
+ // the view's state to the captured `newState` reference, so a
530
+ // dispatch placed before it would advance `view.state` to a value
531
+ // that `update` then silently overwrites — discarding the meta and
532
+ // leaving `isReady=false`.
533
+ if (shouldRebuildSchema) {
534
+ // `state.collabEditPlugin$` is the property PM derives from the
535
+ // collab plugin's PluginKey; cast through `unknown` to read it.
536
+ const collabState = viewRef.current.state.collabEditPlugin$;
537
+ if (collabState && collabState.isReady !== true) {
538
+ viewRef.current.dispatch(viewRef.current.state.tr.setMeta('collabInitialised', true));
539
+ }
540
+ }
541
+
422
542
  // EDITOR-6702: gated until we have a broader gate; reconfigure is a
423
543
  // low-level path so use NoExposure.
424
544
  if (expValEqualsNoExposure('cc-markdown-mode', 'isEnabled', true)) {
545
+ // Force a render so PluginSlot picks up the new preset's content
546
+ // components against the new state.
425
547
  bumpConfigVersion(v => v + 1);
426
548
  }
427
549
  return result;
@@ -5,6 +5,12 @@ import { getQuickInsertItemsFromModule, resolveImport } from '@atlaskit/editor-c
5
5
  import { combineProviders } from '@atlaskit/editor-common/provider-helpers';
6
6
  import { findInsertLocation } from '@atlaskit/editor-common/utils/analytics';
7
7
  import { fg } from '@atlaskit/platform-feature-flags';
8
+
9
+ // Structural shape of the markdown-mode plugin's slice of the injection API.
10
+ // Used to read `isMarkdownMode` without importing the `MarkdownModePlugin`
11
+ // type — that would pull editor-plugin-markdown-mode into editor-core's
12
+ // dependency graph and force every consuming product to rebuild.
13
+
8
14
  /**
9
15
  * Utils to send analytics event when a extension is inserted using quickInsert
10
16
  */
@@ -54,6 +60,15 @@ export async function extensionProviderToQuickInsertProvider(extensionProvider,
54
60
  const extensions = await extensionProvider.getExtensions();
55
61
  return {
56
62
  getItems: () => {
63
+ var _apiRef$current, _apiRef$current$markd, _apiRef$current$markd2;
64
+ // `extensionProvider` is supplied independently of the preset, so
65
+ // suppress its items in markdown mode where rich-only content cannot
66
+ // be inserted. See `MarkdownModeReader` above for why this is read
67
+ // via a structural cast rather than the typed plugin API.
68
+ const isMarkdownMode = (_apiRef$current = apiRef.current) === null || _apiRef$current === void 0 ? void 0 : (_apiRef$current$markd = _apiRef$current.markdownMode) === null || _apiRef$current$markd === void 0 ? void 0 : (_apiRef$current$markd2 = _apiRef$current$markd.sharedState.currentState()) === null || _apiRef$current$markd2 === void 0 ? void 0 : _apiRef$current$markd2.isMarkdownMode;
69
+ if (isMarkdownMode) {
70
+ return Promise.resolve([]);
71
+ }
57
72
  const quickInsertItems = getQuickInsertItemsFromModule(extensions, item => {
58
73
  const Icon = Loadable({
59
74
  loader: item.icon,
@@ -81,8 +96,8 @@ export async function extensionProviderToQuickInsertProvider(extensionProvider,
81
96
  isDisabledOffline: true,
82
97
  action: (insert, state, source) => {
83
98
  if (typeof item.node === 'function') {
84
- var _apiRef$current, _apiRef$current$exten, _apiRef$current$exten2;
85
- const extensionAPI = apiRef === null || apiRef === void 0 ? void 0 : (_apiRef$current = apiRef.current) === null || _apiRef$current === void 0 ? void 0 : (_apiRef$current$exten = _apiRef$current.extension) === null || _apiRef$current$exten === void 0 ? void 0 : (_apiRef$current$exten2 = _apiRef$current$exten.actions) === null || _apiRef$current$exten2 === void 0 ? void 0 : _apiRef$current$exten2.api();
99
+ var _apiRef$current2, _apiRef$current2$exte, _apiRef$current2$exte2;
100
+ const extensionAPI = apiRef === null || apiRef === void 0 ? void 0 : (_apiRef$current2 = apiRef.current) === null || _apiRef$current2 === void 0 ? void 0 : (_apiRef$current2$exte = _apiRef$current2.extension) === null || _apiRef$current2$exte === void 0 ? void 0 : (_apiRef$current2$exte2 = _apiRef$current2$exte.actions) === null || _apiRef$current2$exte2 === void 0 ? void 0 : _apiRef$current2$exte2.api();
86
101
  // While this should only run when the extension some setups of editor
87
102
  // may not have the extension API
88
103
  if (extensionAPI) {
@@ -1,2 +1,2 @@
1
1
  export const name = "@atlaskit/editor-core";
2
- export const version = "219.7.1";
2
+ export const version = "219.7.2";
@@ -1,11 +1,11 @@
1
1
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
2
  import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
3
3
  import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
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; }
4
6
  function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
5
7
  function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
6
8
  function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
7
- 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; }
8
- 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; }
9
9
  import React, { useCallback, useLayoutEffect, useMemo, useRef, useState, useEffect } from 'react';
10
10
  import { injectIntl } from 'react-intl';
11
11
  // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
@@ -25,6 +25,7 @@ import { processRawValue, processRawValueWithoutValidation } from '@atlaskit/edi
25
25
  import { ReactEditorViewContext } from '@atlaskit/editor-common/ui-react';
26
26
  import { analyticsEventKey, getAnalyticsEventSeverity } from '@atlaskit/editor-common/utils/analytics';
27
27
  import { isEmptyDocument } from '@atlaskit/editor-common/utils/document';
28
+ import { Node as PMNode } from '@atlaskit/editor-prosemirror/model';
28
29
  import { EditorState, Selection, TextSelection } from '@atlaskit/editor-prosemirror/state';
29
30
  import { EditorView } from '@atlaskit/editor-prosemirror/view';
30
31
  import { EditorSSRRenderer } from '@atlaskit/editor-ssr-renderer';
@@ -56,6 +57,37 @@ import { useFireFullWidthEvent } from './ReactEditorView/useFireFullWidthEvent';
56
57
  var EDIT_AREA_ID = 'ak-editor-textarea';
57
58
  var SSR_TRACE_SEGMENT_NAME = 'reactEditorView';
58
59
  var bootStartTime = isPerformanceAPIAvailable() ? performance.now() : undefined;
60
+ // `markdown↔rich` toggles drop different node/mark sets, so the unique
61
+ // name set is enough to detect when a destructive rebuild is needed.
62
+ function sameNames(a, b) {
63
+ var setA = new Set(a);
64
+ var setB = new Set(b);
65
+ if (setA.size !== setB.size) {
66
+ return false;
67
+ }
68
+ var _iterator = _createForOfIteratorHelper(setA),
69
+ _step;
70
+ try {
71
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
72
+ var name = _step.value;
73
+ if (!setB.has(name)) {
74
+ return false;
75
+ }
76
+ }
77
+ } catch (err) {
78
+ _iterator.e(err);
79
+ } finally {
80
+ _iterator.f();
81
+ }
82
+ return true;
83
+ }
84
+ function schemaShapeChanged(current, next) {
85
+ return !sameNames(Object.keys(current.nodes), next.nodes.map(function (n) {
86
+ return n.name;
87
+ })) || !sameNames(Object.keys(current.marks), next.marks.map(function (m) {
88
+ return m.name;
89
+ }));
90
+ }
59
91
  export function ReactEditorView(props) {
60
92
  var _pluginInjectionAPI$c, _media, _linking, _document$querySelect, _props$render, _props$render2;
61
93
  // Should be always the first statement in the component
@@ -355,9 +387,10 @@ export function ReactEditorView(props) {
355
387
  _useState2 = _slicedToArray(_useState, 2),
356
388
  bumpConfigVersion = _useState2[1];
357
389
 
358
- // Preset reference last processed by the schema/API reconciliation below.
359
- // Used to skip that work when reconfigure is called with the same preset.
360
- var lastFilteredPresetRef = useRef(null);
390
+ // Preset reference last processed by reconfigureState. Used to skip the
391
+ // destructive work (plugin filter, schema rebuild) when reconfigure is
392
+ // called with the same preset.
393
+ var lastProcessedPresetRef = useRef(null);
361
394
  var reconfigureState = useCallback(function (props) {
362
395
  if (!viewRef.current) {
363
396
  return;
@@ -374,6 +407,35 @@ export function ReactEditorView(props) {
374
407
  var previousPluginNames = new Set(pluginInjectionAPI.current.getRegisteredPluginNames());
375
408
  var editorPlugins = createPluginsList(props.preset, 'allowBlockType' in props.editorProps ? props.editorProps : {}, pluginInjectionAPI.current);
376
409
 
410
+ // Capture once, before either downstream block updates the ref —
411
+ // both the filter and the schema rebuild are destructive and only
412
+ // want to run when the preset has actually changed.
413
+ var presetChanged = lastProcessedPresetRef.current !== props.preset;
414
+
415
+ // Build a candidate config from the *unfiltered* plugin list so we can
416
+ // decide whether the schema rebuild path will run. Both the rebuild
417
+ // decision and the drop-filter decision below depend on this answer,
418
+ // so it has to be computed up-front.
419
+ var buildConfig = function buildConfig(plugins) {
420
+ var c = processPluginsList(plugins);
421
+ if (expValEquals('platform_editor_appearance_shared_state', 'isEnabled', true)) {
422
+ var _c$pmPlugins;
423
+ (_c$pmPlugins = c.pmPlugins).push.apply(_c$pmPlugins, _toConsumableArray(pluginInjectionAPI.current.getInternalPMPlugins()));
424
+ }
425
+ return c;
426
+ };
427
+ var nextConfig = buildConfig(editorPlugins);
428
+
429
+ // `state.reconfigure` preserves the original schema, so a preset
430
+ // toggle that should change schema (markdown↔rich) needs a fresh
431
+ // `EditorState`. Resets all plugin state including undo history.
432
+ //
433
+ // Compare schema *shape* (node + mark name sets) rather than preset
434
+ // identity: consumers commonly recreate the preset object on every
435
+ // parent re-render, and a destructive rebuild on a no-op identity
436
+ // change tears down all plugin state (e.g. unmounts the AI palette).
437
+ var shouldRebuildSchema = presetChanged && schemaShapeChanged(viewRef.current.state.schema, nextConfig) && expValEqualsNoExposure('cc-markdown-mode', 'isEnabled', true);
438
+
377
439
  // `state.reconfigure` keeps the original schema, so switching presets
378
440
  // can leave the editor inconsistent in two ways:
379
441
  // 1. The new preset may add plugins that reference schema nodes or
@@ -382,17 +444,26 @@ export function ReactEditorView(props) {
382
444
  // injection API even when the new preset doesn't re-register
383
445
  // them, so listeners still fire against a state that no longer
384
446
  // has their pmPlugin.
385
- if (lastFilteredPresetRef.current !== props.preset && fg('platform_editor_reconfigure_filter_plugins')) {
386
- var _filterPluginsForReco = filterPluginsForReconfigure(editorPlugins, viewRef.current.state.schema, previousPluginNames),
387
- kept = _filterPluginsForReco.kept,
388
- dropped = _filterPluginsForReco.dropped;
389
- editorPlugins = kept;
390
-
391
- // Reconcile the injection API with the post-filter plugin set.
392
- // This evicts both the plugins we just dropped above (re-registered
393
- // by createPluginsList but no longer in editorPlugins) AND any
394
- // plugin from a previous preset that the new preset doesn't
395
- // re-register.
447
+ //
448
+ // When the schema is being rebuilt below, the new schema is built
449
+ // from the *unfiltered* plugin list — so dropping plugins whose
450
+ // nodes/marks the OLD schema lacks would wrongly remove the very
451
+ // plugins the rebuild is meant to admit. Skip the drop step in that
452
+ // case (purpose 1) but always reconcile the injection API
453
+ // (purpose 2). When NOT rebuilding, run both even under the
454
+ // `cc-markdown-mode` experiment, otherwise no-op preset identity
455
+ // changes would silently leave a broken plugin/schema mismatch.
456
+ if (presetChanged && fg('platform_editor_reconfigure_filter_plugins')) {
457
+ var dropped = [];
458
+ if (!shouldRebuildSchema) {
459
+ var _result = filterPluginsForReconfigure(editorPlugins, viewRef.current.state.schema, previousPluginNames);
460
+ if (_result.dropped.length > 0) {
461
+ editorPlugins = _result.kept;
462
+ // Plugin list changed — rebuild candidate config to match.
463
+ nextConfig = buildConfig(editorPlugins);
464
+ }
465
+ dropped = _result.dropped;
466
+ }
396
467
  var keptPluginNames = new Set(editorPlugins.map(function (p) {
397
468
  return p === null || p === void 0 ? void 0 : p.name;
398
469
  }).filter(function (n) {
@@ -406,33 +477,77 @@ export function ReactEditorView(props) {
406
477
  evictedFromApi: evictedFromApi
407
478
  });
408
479
  }
409
- lastFilteredPresetRef.current = props.preset;
410
- }
411
- config.current = processPluginsList(editorPlugins);
412
- if (expValEquals('platform_editor_appearance_shared_state', 'isEnabled', true)) {
413
- var _config$current$pmPlu2;
414
- (_config$current$pmPlu2 = config.current.pmPlugins).push.apply(_config$current$pmPlu2, _toConsumableArray(pluginInjectionAPI.current.getInternalPMPlugins()));
415
480
  }
481
+ config.current = nextConfig;
416
482
  var state = viewRef.current.state;
417
- var plugins = createPMPlugins({
418
- schema: state.schema,
419
- dispatch: dispatch,
420
- errorReporter: errorReporter,
421
- editorConfig: config.current,
422
- eventDispatcher: eventDispatcher,
423
- providerFactory: props.providerFactory,
424
- portalProviderAPI: props.portalProviderAPI,
425
- nodeViewPortalProviderAPI: props.nodeViewPortalProviderAPI,
426
- dispatchAnalyticsEvent: dispatchAnalyticsEvent,
427
- featureFlags: featureFlags,
428
- getIntl: function getIntl() {
429
- return props.intl;
430
- },
431
- onEditorStateUpdated: pluginInjectionAPI.current.onEditorViewUpdated
432
- });
433
- var newState = state.reconfigure({
434
- plugins: plugins
435
- });
483
+ var newState;
484
+ if (shouldRebuildSchema) {
485
+ var newSchema = createSchema(config.current);
486
+ var newDoc;
487
+ try {
488
+ newDoc = PMNode.fromJSON(newSchema, state.doc.toJSON());
489
+ } catch (e) {
490
+ // eslint-disable-next-line no-console
491
+ console.error('[reconfigureState] Failed to migrate doc to new schema; resetting to empty doc', e);
492
+ var empty = newSchema.topNodeType.createAndFill();
493
+ if (!empty) {
494
+ throw new Error('reconfigureState: doc migration failed and new schema cannot create an empty top node');
495
+ }
496
+ newDoc = empty;
497
+ }
498
+ var newSelection;
499
+ try {
500
+ newSelection = Selection.fromJSON(newDoc, state.selection.toJSON());
501
+ } catch (_unused) {
502
+ // Old selection's positions / node types may not map onto the new schema.
503
+ newSelection = Selection.atStart(newDoc);
504
+ }
505
+ var plugins = createPMPlugins({
506
+ schema: newSchema,
507
+ dispatch: dispatch,
508
+ errorReporter: errorReporter,
509
+ editorConfig: config.current,
510
+ eventDispatcher: eventDispatcher,
511
+ providerFactory: props.providerFactory,
512
+ portalProviderAPI: props.portalProviderAPI,
513
+ nodeViewPortalProviderAPI: props.nodeViewPortalProviderAPI,
514
+ dispatchAnalyticsEvent: dispatchAnalyticsEvent,
515
+ featureFlags: featureFlags,
516
+ getIntl: function getIntl() {
517
+ return props.intl;
518
+ },
519
+ onEditorStateUpdated: pluginInjectionAPI.current.onEditorViewUpdated
520
+ });
521
+ newState = EditorState.create({
522
+ schema: newSchema,
523
+ doc: newDoc,
524
+ selection: newSelection,
525
+ plugins: plugins
526
+ });
527
+ } else {
528
+ var _plugins = createPMPlugins({
529
+ schema: state.schema,
530
+ dispatch: dispatch,
531
+ errorReporter: errorReporter,
532
+ editorConfig: config.current,
533
+ eventDispatcher: eventDispatcher,
534
+ providerFactory: props.providerFactory,
535
+ portalProviderAPI: props.portalProviderAPI,
536
+ nodeViewPortalProviderAPI: props.nodeViewPortalProviderAPI,
537
+ dispatchAnalyticsEvent: dispatchAnalyticsEvent,
538
+ featureFlags: featureFlags,
539
+ getIntl: function getIntl() {
540
+ return props.intl;
541
+ },
542
+ onEditorStateUpdated: pluginInjectionAPI.current.onEditorViewUpdated
543
+ });
544
+ newState = state.reconfigure({
545
+ plugins: _plugins
546
+ });
547
+ }
548
+ if (presetChanged) {
549
+ lastProcessedPresetRef.current = props.preset;
550
+ }
436
551
 
437
552
  // need to update the state first so when the view builds the nodeviews it is
438
553
  // using the latest plugins
@@ -441,9 +556,32 @@ export function ReactEditorView(props) {
441
556
  state: newState
442
557
  }));
443
558
 
559
+ // The new collab-edit plugin instance starts with `isReady=false`.
560
+ // The rebind path in editor-plugin-collab-edit's initialize.ts is
561
+ // gated on `provider.getInitPayload`, which the Confluence NCS
562
+ // provider does not implement, so the placeholder spinner would
563
+ // never clear. Re-seeding here is safe: the prior state must have
564
+ // had `isReady=true` for the user to have triggered the toggle.
565
+ //
566
+ // Must run AFTER `view.update({ state: newState })`: that call resets
567
+ // the view's state to the captured `newState` reference, so a
568
+ // dispatch placed before it would advance `view.state` to a value
569
+ // that `update` then silently overwrites — discarding the meta and
570
+ // leaving `isReady=false`.
571
+ if (shouldRebuildSchema) {
572
+ // `state.collabEditPlugin$` is the property PM derives from the
573
+ // collab plugin's PluginKey; cast through `unknown` to read it.
574
+ var collabState = viewRef.current.state.collabEditPlugin$;
575
+ if (collabState && collabState.isReady !== true) {
576
+ viewRef.current.dispatch(viewRef.current.state.tr.setMeta('collabInitialised', true));
577
+ }
578
+ }
579
+
444
580
  // EDITOR-6702: gated until we have a broader gate; reconfigure is a
445
581
  // low-level path so use NoExposure.
446
582
  if (expValEqualsNoExposure('cc-markdown-mode', 'isEnabled', true)) {
583
+ // Force a render so PluginSlot picks up the new preset's content
584
+ // components against the new state.
447
585
  bumpConfigVersion(function (v) {
448
586
  return v + 1;
449
587
  });
@@ -686,19 +824,19 @@ export function ReactEditorView(props) {
686
824
  return function () {
687
825
  if (scrollElement.current) {
688
826
  // eslint-disable-next-line react-hooks/exhaustive-deps
689
- var _iterator = _createForOfIteratorHelper(possibleListeners.current),
690
- _step;
827
+ var _iterator2 = _createForOfIteratorHelper(possibleListeners.current),
828
+ _step2;
691
829
  try {
692
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
830
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
693
831
  var _scrollElement$curren;
694
- var possibleListener = _step.value;
832
+ var possibleListener = _step2.value;
695
833
  // eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
696
834
  (_scrollElement$curren = scrollElement.current) === null || _scrollElement$curren === void 0 || _scrollElement$curren.removeEventListener.apply(_scrollElement$curren, _toConsumableArray(possibleListener));
697
835
  }
698
836
  } catch (err) {
699
- _iterator.e(err);
837
+ _iterator2.e(err);
700
838
  } finally {
701
- _iterator.f();
839
+ _iterator2.f();
702
840
  }
703
841
  }
704
842
  scrollElement.current = null;
@@ -710,19 +848,19 @@ export function ReactEditorView(props) {
710
848
  scrollElement.current = document.querySelector('[data-editor-scroll-container]');
711
849
  var cleanupListeners = function cleanupListeners() {
712
850
  // eslint-disable-next-line react-hooks/exhaustive-deps
713
- var _iterator2 = _createForOfIteratorHelper(possibleListeners.current),
714
- _step2;
851
+ var _iterator3 = _createForOfIteratorHelper(possibleListeners.current),
852
+ _step3;
715
853
  try {
716
- for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
854
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
717
855
  var _scrollElement$curren2;
718
- var possibleListener = _step2.value;
856
+ var possibleListener = _step3.value;
719
857
  // eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
720
858
  (_scrollElement$curren2 = scrollElement.current) === null || _scrollElement$curren2 === void 0 || _scrollElement$curren2.removeEventListener.apply(_scrollElement$curren2, _toConsumableArray(possibleListener));
721
859
  }
722
860
  } catch (err) {
723
- _iterator2.e(err);
861
+ _iterator3.e(err);
724
862
  } finally {
725
- _iterator2.f();
863
+ _iterator3.f();
726
864
  }
727
865
  };
728
866
  if (scrollElement.current) {
@@ -10,6 +10,12 @@ import { getQuickInsertItemsFromModule, resolveImport } from '@atlaskit/editor-c
10
10
  import { combineProviders } from '@atlaskit/editor-common/provider-helpers';
11
11
  import { findInsertLocation } from '@atlaskit/editor-common/utils/analytics';
12
12
  import { fg } from '@atlaskit/platform-feature-flags';
13
+
14
+ // Structural shape of the markdown-mode plugin's slice of the injection API.
15
+ // Used to read `isMarkdownMode` without importing the `MarkdownModePlugin`
16
+ // type — that would pull editor-plugin-markdown-mode into editor-core's
17
+ // dependency graph and force every consuming product to rebuild.
18
+
13
19
  /**
14
20
  * Utils to send analytics event when a extension is inserted using quickInsert
15
21
  */
@@ -81,6 +87,15 @@ function _extensionProviderToQuickInsertProvider() {
81
87
  extensions = _context.sent;
82
88
  return _context.abrupt("return", {
83
89
  getItems: function getItems() {
90
+ var _apiRef$current;
91
+ // `extensionProvider` is supplied independently of the preset, so
92
+ // suppress its items in markdown mode where rich-only content cannot
93
+ // be inserted. See `MarkdownModeReader` above for why this is read
94
+ // via a structural cast rather than the typed plugin API.
95
+ var isMarkdownMode = (_apiRef$current = apiRef.current) === null || _apiRef$current === void 0 || (_apiRef$current = _apiRef$current.markdownMode) === null || _apiRef$current === void 0 || (_apiRef$current = _apiRef$current.sharedState.currentState()) === null || _apiRef$current === void 0 ? void 0 : _apiRef$current.isMarkdownMode;
96
+ if (isMarkdownMode) {
97
+ return Promise.resolve([]);
98
+ }
84
99
  var quickInsertItems = getQuickInsertItemsFromModule(extensions, function (item) {
85
100
  var Icon = Loadable({
86
101
  loader: item.icon,
@@ -112,8 +127,8 @@ function _extensionProviderToQuickInsertProvider() {
112
127
  isDisabledOffline: true,
113
128
  action: function action(insert, state, source) {
114
129
  if (typeof item.node === 'function') {
115
- var _apiRef$current;
116
- var extensionAPI = apiRef === null || apiRef === void 0 || (_apiRef$current = apiRef.current) === null || _apiRef$current === void 0 || (_apiRef$current = _apiRef$current.extension) === null || _apiRef$current === void 0 || (_apiRef$current = _apiRef$current.actions) === null || _apiRef$current === void 0 ? void 0 : _apiRef$current.api();
130
+ var _apiRef$current2;
131
+ var extensionAPI = apiRef === null || apiRef === void 0 || (_apiRef$current2 = apiRef.current) === null || _apiRef$current2 === void 0 || (_apiRef$current2 = _apiRef$current2.extension) === null || _apiRef$current2 === void 0 || (_apiRef$current2 = _apiRef$current2.actions) === null || _apiRef$current2 === void 0 ? void 0 : _apiRef$current2.api();
117
132
  // While this should only run when the extension some setups of editor
118
133
  // may not have the extension API
119
134
  if (extensionAPI) {
@@ -1,2 +1,2 @@
1
1
  export var name = "@atlaskit/editor-core";
2
- export var version = "219.7.1";
2
+ export var version = "219.7.2";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-core",
3
- "version": "219.7.2",
3
+ "version": "219.7.3",
4
4
  "description": "A package contains Atlassian editor core functionality",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org/"
@@ -55,7 +55,7 @@
55
55
  "@atlaskit/css": "^0.19.0",
56
56
  "@atlaskit/editor-json-transformer": "^8.31.0",
57
57
  "@atlaskit/editor-performance-metrics": "^2.1.0",
58
- "@atlaskit/editor-plugin-connectivity": "^10.0.0",
58
+ "@atlaskit/editor-plugin-connectivity": "^10.1.0",
59
59
  "@atlaskit/editor-plugin-quick-insert": "^10.4.0",
60
60
  "@atlaskit/editor-plugin-user-preferences": "^8.2.0",
61
61
  "@atlaskit/editor-plugins": "^13.1.0",
@@ -74,7 +74,7 @@
74
74
  "@atlaskit/platform-feature-flags-react": "^0.5.0",
75
75
  "@atlaskit/react-ufo": "^5.20.0",
76
76
  "@atlaskit/task-decision": "^20.1.0",
77
- "@atlaskit/tmp-editor-statsig": "^80.2.0",
77
+ "@atlaskit/tmp-editor-statsig": "^80.3.0",
78
78
  "@atlaskit/tokens": "^13.0.0",
79
79
  "@atlaskit/tooltip": "^22.2.0",
80
80
  "@atlaskit/width-detector": "^5.1.0",
@@ -92,7 +92,7 @@
92
92
  "uuid": "^3.1.0"
93
93
  },
94
94
  "peerDependencies": {
95
- "@atlaskit/editor-common": "^114.30.0",
95
+ "@atlaskit/editor-common": "^114.31.0",
96
96
  "@atlaskit/link-provider": "^4.4.0",
97
97
  "@atlaskit/media-core": "^37.0.0",
98
98
  "react": "^18.2.0",
@@ -105,9 +105,10 @@
105
105
  "@af/visual-regression": "workspace:^",
106
106
  "@atlaskit/adf-utils": "^19.29.0",
107
107
  "@atlaskit/analytics-listeners": "^10.1.0",
108
+ "@atlaskit/code": "^17.5.0",
108
109
  "@atlaskit/collab-provider": "^18.2.0",
109
110
  "@atlaskit/editor-plugin-annotation": "^10.3.0",
110
- "@atlaskit/editor-plugin-card": "^16.7.0",
111
+ "@atlaskit/editor-plugin-card": "^16.8.0",
111
112
  "@atlaskit/editor-plugin-list": "^12.1.0",
112
113
  "@atlaskit/editor-plugin-paste": "^11.2.0",
113
114
  "@atlaskit/editor-test-helpers": "workspace:^",
@@ -118,6 +119,8 @@
118
119
  "@atlaskit/media-integration-test-helpers": "workspace:^",
119
120
  "@atlaskit/media-test-helpers": "^41.0.0",
120
121
  "@atlaskit/modal-dialog": "^15.0.0",
122
+ "@atlaskit/popper": "^7.2.0",
123
+ "@atlaskit/portal": "^5.5.0",
121
124
  "@atlaskit/renderer": "^130.6.0",
122
125
  "@atlaskit/section-message": "^8.12.0",
123
126
  "@atlaskit/synchrony-test-helpers": "workspace:^",
@@ -132,6 +135,7 @@
132
135
  "@atlassian/search-client": "^1.7.0",
133
136
  "@atlassian/search-provider": "^11.1.0",
134
137
  "@atlassian/structured-docs-types": "workspace:^",
138
+ "@atlassian/user-profile-card": "^1.4.0",
135
139
  "@emotion/jest": "^11.8.0",
136
140
  "@testing-library/react": "^16.3.0",
137
141
  "@types/diff": "^5.0.2",
@@ -174,6 +178,10 @@
174
178
  "type": "boolean",
175
179
  "referenceOnly": "true"
176
180
  },
181
+ "people-teams_migrate-user-profile-card": {
182
+ "type": "boolean",
183
+ "referenceOnly": true
184
+ },
177
185
  "editor_inline_comments_on_inline_nodes": {
178
186
  "type": "boolean",
179
187
  "referenceOnly": "true"