UNPKG

@atlaskit/editor-plugin-synced-block

Version:

SyncedBlock plugin for @atlaskit/editor-core

685 lines (674 loc) 36.2 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.syncedBlockPluginKey = exports.createPlugin = void 0; var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass")); var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray")); var _analytics = require("@atlaskit/editor-common/analytics"); var _collab = require("@atlaskit/editor-common/collab"); var _safePlugin = require("@atlaskit/editor-common/safe-plugin"); var _selection = require("@atlaskit/editor-common/selection"); var _syncBlock = require("@atlaskit/editor-common/sync-block"); var _utils = require("@atlaskit/editor-common/utils"); var _editorPluginConnectivity = require("@atlaskit/editor-plugin-connectivity"); var _state = require("@atlaskit/editor-prosemirror/state"); var _transform = require("@atlaskit/editor-prosemirror/transform"); var _view = require("@atlaskit/editor-prosemirror/view"); var _editorSyncedBlockProvider = require("@atlaskit/editor-synced-block-provider"); var _platformFeatureFlags = require("@atlaskit/platform-feature-flags"); var _experiments = require("@atlaskit/tmp-editor-statsig/experiments"); var _bodiedSyncedBlock = require("../nodeviews/bodiedSyncedBlock"); var _syncedBlock = require("../nodeviews/syncedBlock"); var _types = require("../types"); var _handleBodiedSyncBlockCreation = require("./utils/handle-bodied-sync-block-creation"); var _handleBodiedSyncBlockRemoval = require("./utils/handle-bodied-sync-block-removal"); var _ignoreDomEvent = require("./utils/ignore-dom-event"); var _selectionDecorations = require("./utils/selection-decorations"); var _trackSyncBlocks7 = require("./utils/track-sync-blocks"); var _utils2 = require("./utils/utils"); 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; } 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; } 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; } } }; } 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; } } 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; } var syncedBlockPluginKey = exports.syncedBlockPluginKey = new _state.PluginKey('syncedBlockPlugin'); var mapRetryCreationPosMap = function mapRetryCreationPosMap(oldMap, newRetryCreationPos, mapPos) { var resourceId = newRetryCreationPos === null || newRetryCreationPos === void 0 ? void 0 : newRetryCreationPos.resourceId; var newMap = new Map(oldMap); if (resourceId) { var pos = newRetryCreationPos.pos; if (!pos) { newMap.delete(resourceId); } else { newMap.set(resourceId, pos); } } if (newMap.size === 0) { return newMap; } var _iterator = _createForOfIteratorHelper(newMap.entries()), _step; try { for (_iterator.s(); !(_step = _iterator.n()).done;) { var _step$value = (0, _slicedToArray2.default)(_step.value, 2), id = _step$value[0], _pos = _step$value[1]; newMap.set(id, { from: mapPos(_pos.from), to: mapPos(_pos.to) }); } } catch (err) { _iterator.e(err); } finally { _iterator.f(); } return newMap; }; var showCopiedFlag = function showCopiedFlag(api) { (0, _utils2.deferDispatch)(function () { api === null || api === void 0 || api.core.actions.execute(function (_ref) { var tr = _ref.tr; return tr.setMeta(syncedBlockPluginKey, { activeFlag: { id: _types.FLAG_ID.SYNC_BLOCK_COPIED } }); }); }); }; var showExtensionInSyncBlockWarningIfNeeded = function showExtensionInSyncBlockWarningIfNeeded(tr, state, api, extensionFlagShown) { var _api$connectivity; if (!tr.docChanged || tr.getMeta('isRemote') || Boolean(tr.getMeta(_utils.pmHistoryPluginKey)) || (0, _editorPluginConnectivity.isOfflineMode)(api === null || api === void 0 || (_api$connectivity = api.connectivity) === null || _api$connectivity === void 0 || (_api$connectivity = _api$connectivity.sharedState.currentState()) === null || _api$connectivity === void 0 ? void 0 : _api$connectivity.mode)) { return; } var resourceId = (0, _utils2.wasExtensionInsertedInBodiedSyncBlock)(tr, state); // Only show the flag on the first instance per sync block (same as UNPUBLISHED_SYNC_BLOCK_PASTED) if (resourceId && !extensionFlagShown.has(resourceId)) { extensionFlagShown.add(resourceId); (0, _utils2.deferDispatch)(function () { api === null || api === void 0 || api.core.actions.execute(function (_ref2) { var tr = _ref2.tr; return tr.setMeta(syncedBlockPluginKey, { activeFlag: { id: (0, _experiments.editorExperiment)('platform_synced_block_patch_6', true, { exposure: true }) ? _types.FLAG_ID.EXTENSION_IN_SYNC_BLOCK : _types.FLAG_ID.INLINE_EXTENSION_IN_SYNC_BLOCK } }); }); }); } }; var getDeleteReason = function getDeleteReason(tr) { var reason = tr.getMeta('deletionReason'); if (!reason) { return 'source-block-deleted'; } return reason; }; var filterTransactionOnline = function filterTransactionOnline(_ref3) { var tr = _ref3.tr, state = _ref3.state, syncBlockStore = _ref3.syncBlockStore, api = _ref3.api, confirmationTransactionRef = _ref3.confirmationTransactionRef, bodiedSyncBlockRemoved = _ref3.bodiedSyncBlockRemoved, bodiedSyncBlockAdded = _ref3.bodiedSyncBlockAdded, extensionFlagShown = _ref3.extensionFlagShown; var _trackSyncBlocks = (0, _trackSyncBlocks7.trackSyncBlocks)(syncBlockStore.referenceManager.isReferenceBlock, tr, state), syncBlockRemoved = _trackSyncBlocks.removed, syncBlockAdded = _trackSyncBlocks.added; syncBlockRemoved.forEach(function (syncBlock) { var _api$analytics; api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 || (_api$analytics = _api$analytics.actions) === null || _api$analytics === void 0 || _api$analytics.fireAnalyticsEvent({ action: _analytics.ACTION.DELETED, actionSubject: _analytics.ACTION_SUBJECT.SYNCED_BLOCK, actionSubjectId: _analytics.ACTION_SUBJECT_ID.REFERENCE_SYNCED_BLOCK_DELETE, attributes: { resourceId: syncBlock.attrs.resourceId, blockInstanceId: syncBlock.attrs.localId }, eventType: _analytics.EVENT_TYPE.OPERATIONAL }); }); syncBlockAdded.forEach(function (syncBlock) { var _api$analytics2; api === null || api === void 0 || (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 || (_api$analytics2 = _api$analytics2.actions) === null || _api$analytics2 === void 0 || _api$analytics2.fireAnalyticsEvent({ action: _analytics.ACTION.INSERTED, actionSubject: _analytics.ACTION_SUBJECT.DOCUMENT, actionSubjectId: _analytics.ACTION_SUBJECT_ID.SYNCED_BLOCK, attributes: { resourceId: syncBlock.attrs.resourceId, blockInstanceId: syncBlock.attrs.localId }, eventType: _analytics.EVENT_TYPE.TRACK }); }); if (bodiedSyncBlockRemoved.length > 0) { // eslint-disable-next-line no-param-reassign confirmationTransactionRef.current = tr; return (0, _handleBodiedSyncBlockRemoval.handleBodiedSyncBlockRemoval)(bodiedSyncBlockRemoved, syncBlockStore, api, confirmationTransactionRef, getDeleteReason(tr)); } if (bodiedSyncBlockAdded.length > 0) { if (tr.getMeta(_utils.pmHistoryPluginKey)) { // We don't allow bodiedSyncBlock creation via redo, however, we need to return true here to let transaction through so history can be updated properly. // If we simply returns false, creation from redo is blocked as desired, but this results in editor showing redo as possible even though it's not. // After true is returned here and the node is created, we delete the node in the filterTransaction immediately, which cancels out the creation return true; } (0, _handleBodiedSyncBlockCreation.handleBodiedSyncBlockCreation)(bodiedSyncBlockAdded, state, api); return true; } showExtensionInSyncBlockWarningIfNeeded(tr, state, api, extensionFlagShown); return true; }; var filterTransactionOffline = function filterTransactionOffline(_ref4) { var tr = _ref4.tr, state = _ref4.state, syncBlockStore = _ref4.syncBlockStore, api = _ref4.api, isConfirmedSyncBlockDeletion = _ref4.isConfirmedSyncBlockDeletion, bodiedSyncBlockRemoved = _ref4.bodiedSyncBlockRemoved, bodiedSyncBlockAdded = _ref4.bodiedSyncBlockAdded; var _trackSyncBlocks2 = (0, _trackSyncBlocks7.trackSyncBlocks)(syncBlockStore.referenceManager.isReferenceBlock, tr, state), syncBlockRemoved = _trackSyncBlocks2.removed, syncBlockAdded = _trackSyncBlocks2.added; var errorFlag = false; if (isConfirmedSyncBlockDeletion || bodiedSyncBlockRemoved.length > 0 || syncBlockRemoved.length > 0) { errorFlag = _types.FLAG_ID.CANNOT_DELETE_WHEN_OFFLINE; } else if (bodiedSyncBlockAdded.length > 0 || syncBlockAdded.length > 0) { errorFlag = _types.FLAG_ID.CANNOT_CREATE_WHEN_OFFLINE; } else if ((0, _trackSyncBlocks7.hasEditInSyncBlock)(tr, state)) { errorFlag = _types.FLAG_ID.CANNOT_EDIT_WHEN_OFFLINE; } if (errorFlag) { (0, _utils2.deferDispatch)(function () { api === null || api === void 0 || api.core.actions.execute(function (_ref5) { var tr = _ref5.tr; return tr.setMeta(syncedBlockPluginKey, { activeFlag: { id: errorFlag } }); }); }); return false; } return true; }; /** * Encapsulates mutable state that persists across transactions in the * synced block plugin. Replaces module-level closure variables so state * is explicitly scoped to a single plugin instance. */ var SyncedBlockPluginContext = /*#__PURE__*/function () { function SyncedBlockPluginContext() { (0, _classCallCheck2.default)(this, SyncedBlockPluginContext); (0, _defineProperty2.default)(this, "confirmationTransactionRef", { current: undefined }); (0, _defineProperty2.default)(this, "_isCopyEvent", false); (0, _defineProperty2.default)(this, "unpublishedFlagShown", new Set()); (0, _defineProperty2.default)(this, "extensionFlagShown", new Set()); } return (0, _createClass2.default)(SyncedBlockPluginContext, [{ key: "isCopyEvent", get: function get() { return this._isCopyEvent; } }, { key: "markCopyEvent", value: function markCopyEvent() { this._isCopyEvent = true; } }, { key: "consumeCopyEvent", value: function consumeCopyEvent() { var was = this._isCopyEvent; this._isCopyEvent = false; return was; } }]); }(); var createPlugin = exports.createPlugin = function createPlugin(options, pmPluginFactoryParams, syncBlockStore, api) { var _ref6 = options || {}, _ref6$useLongPressSel = _ref6.useLongPressSelection, useLongPressSelection = _ref6$useLongPressSel === void 0 ? false : _ref6$useLongPressSel; var ctx = new SyncedBlockPluginContext(); var confirmationTransactionRef = ctx.confirmationTransactionRef; var unpublishedFlagShown = ctx.unpublishedFlagShown; var extensionFlagShown = ctx.extensionFlagShown; // Update plugin state post-flush to sync hasUnsavedBodiedSyncBlockChanges. // It prevents false "Changes may not be saved" warnings when publishing // Classic pages with sync blocks. syncBlockStore.sourceManager.registerFlushCompletionCallback(function () { (0, _utils2.deferDispatch)(function () { api === null || api === void 0 || api.core.actions.execute(function (_ref7) { var tr = _ref7.tr; return tr; }); }); }); // Set up callback to detect unpublished sync blocks when they're fetched syncBlockStore.referenceManager.setOnUnpublishedSyncBlockDetected(function (resourceId) { // Only show the flag once per sync block if (!unpublishedFlagShown.has(resourceId)) { unpublishedFlagShown.add(resourceId); (0, _utils2.deferDispatch)(function () { api === null || api === void 0 || api.core.actions.execute(function (_ref8) { var tr = _ref8.tr; return tr.setMeta(syncedBlockPluginKey, { activeFlag: { id: _types.FLAG_ID.UNPUBLISHED_SYNC_BLOCK_PASTED } }); }); }); } }); return new _safePlugin.SafePlugin({ key: syncedBlockPluginKey, state: { init: function init(_, instance) { var syncBlockNodes = instance.doc.children.filter(syncBlockStore.referenceManager.isReferenceBlock); syncBlockStore.referenceManager.fetchSyncBlocksData((0, _editorSyncedBlockProvider.convertPMNodesToSyncBlockNodes)(syncBlockNodes)); // Populate source sync block cache from initial document // When fg is ON, this replaces the constructor call in the nodeview if ((0, _platformFeatureFlags.fg)('platform_synced_block_update_refactor')) { instance.doc.forEach(function (node) { if (syncBlockStore.sourceManager.isSourceBlock(node)) { syncBlockStore.sourceManager.updateSyncBlockData(node, false); } }); } return { selectionDecorationSet: (0, _selectionDecorations.calculateDecorations)(instance.doc, instance.selection, instance.schema), activeFlag: false, syncBlockStore: syncBlockStore, retryCreationPosMap: new Map(), hasUnsavedBodiedSyncBlockChanges: syncBlockStore.sourceManager.hasUnsavedChanges() }; }, apply: function apply(tr, currentPluginState, oldEditorState) { var _meta$activeFlag, _meta$bodiedSyncBlock; var meta = tr.getMeta(syncedBlockPluginKey); var activeFlag = currentPluginState.activeFlag, selectionDecorationSet = currentPluginState.selectionDecorationSet, bodiedSyncBlockDeletionStatus = currentPluginState.bodiedSyncBlockDeletionStatus, retryCreationPosMap = currentPluginState.retryCreationPosMap; var newDecorationSet = tr.docChanged ? selectionDecorationSet.map(tr.mapping, tr.doc) // only map if document changed : selectionDecorationSet; if (!tr.selection.eq(oldEditorState.selection)) { newDecorationSet = (0, _selectionDecorations.calculateDecorations)(tr.doc, tr.selection, tr.doc.type.schema); } else if (tr.docChanged) { var existingDecorationsLength = selectionDecorationSet.find().length; var newDecorationsLength = newDecorationSet.find().length; // Edge case: When document nodes are replaced, the mapping can lose decorations // We rebuild decorations when the document changes but the selection hasn't. // We can do this check because we only expect 1 decoration for the selection if (existingDecorationsLength !== newDecorationsLength) { newDecorationSet = (0, _selectionDecorations.calculateDecorations)(tr.doc, tr.selection, tr.doc.type.schema); } } var newPosEntry = meta === null || meta === void 0 ? void 0 : meta.retryCreationPos; var newRetryCreationPosMap = mapRetryCreationPosMap(retryCreationPosMap, newPosEntry, tr.mapping.map.bind(tr.mapping)); return { activeFlag: (_meta$activeFlag = meta === null || meta === void 0 ? void 0 : meta.activeFlag) !== null && _meta$activeFlag !== void 0 ? _meta$activeFlag : activeFlag, selectionDecorationSet: newDecorationSet, syncBlockStore: syncBlockStore, retryCreationPosMap: newRetryCreationPosMap, bodiedSyncBlockDeletionStatus: (_meta$bodiedSyncBlock = meta === null || meta === void 0 ? void 0 : meta.bodiedSyncBlockDeletionStatus) !== null && _meta$bodiedSyncBlock !== void 0 ? _meta$bodiedSyncBlock : bodiedSyncBlockDeletionStatus, hasUnsavedBodiedSyncBlockChanges: syncBlockStore.sourceManager.hasUnsavedChanges() }; } }, props: { nodeViews: { syncBlock: function syncBlock(node, view, getPos, _decorations) { return ( // To support SSR, pass `syncBlockStore` here // and do not use lazy loading. // We cannot start rendering and then load `syncBlockStore` asynchronously, // because obtaining it is asynchronous (sharedPluginState.currentState() is delayed). new _syncedBlock.SyncBlock({ api: api, options: options, node: node, view: view, getPos: getPos, portalProviderAPI: pmPluginFactoryParams.portalProviderAPI, eventDispatcher: pmPluginFactoryParams.eventDispatcher, syncBlockStore: syncBlockStore }).init() ); }, bodiedSyncBlock: (0, _experiments.editorExperiment)('platform_synced_block_use_new_source_nodeview', true, { exposure: true }) ? (0, _bodiedSyncedBlock.bodiedSyncBlockNodeView)({ pluginOptions: options, pmPluginFactoryParams: pmPluginFactoryParams, api: api, syncBlockStore: syncBlockStore }) : (0, _bodiedSyncedBlock.bodiedSyncBlockNodeViewOld)({ pluginOptions: options, pmPluginFactoryParams: pmPluginFactoryParams, api: api, syncBlockStore: syncBlockStore }) }, decorations: function decorations(state) { var _currentPluginState$s, _api$connectivity2, _api$editorViewMode, _api$userIntent, _api$focus; var currentPluginState = syncedBlockPluginKey.getState(state); var selectionDecorationSet = (_currentPluginState$s = currentPluginState === null || currentPluginState === void 0 ? void 0 : currentPluginState.selectionDecorationSet) !== null && _currentPluginState$s !== void 0 ? _currentPluginState$s : _view.DecorationSet.empty; var syncBlockStore = currentPluginState === null || currentPluginState === void 0 ? void 0 : currentPluginState.syncBlockStore; var doc = state.doc; var isOffline = (0, _editorPluginConnectivity.isOfflineMode)(api === null || api === void 0 || (_api$connectivity2 = api.connectivity) === null || _api$connectivity2 === void 0 || (_api$connectivity2 = _api$connectivity2.sharedState.currentState()) === null || _api$connectivity2 === void 0 ? void 0 : _api$connectivity2.mode); var isViewMode = (api === null || api === void 0 || (_api$editorViewMode = api.editorViewMode) === null || _api$editorViewMode === void 0 || (_api$editorViewMode = _api$editorViewMode.sharedState.currentState()) === null || _api$editorViewMode === void 0 ? void 0 : _api$editorViewMode.mode) === 'view'; var isDragging = (api === null || api === void 0 || (_api$userIntent = api.userIntent) === null || _api$userIntent === void 0 || (_api$userIntent = _api$userIntent.sharedState.currentState()) === null || _api$userIntent === void 0 ? void 0 : _api$userIntent.currentUserIntent) === 'dragging'; var offlineDecorations = []; var viewModeDecorations = []; var loadingDecorations = []; var dragDecorations = []; state.doc.descendants(function (node, pos) { if (node.type.name === 'bodiedSyncBlock' && isOffline) { offlineDecorations.push(_view.Decoration.node(pos, pos + node.nodeSize, { class: _syncBlock.SyncBlockStateCssClassName.disabledClassName })); } if (syncBlockStore.isSyncBlock(node) && isViewMode) { viewModeDecorations.push(_view.Decoration.node(pos, pos + node.nodeSize, { class: _syncBlock.SyncBlockStateCssClassName.viewModeClassName })); } if (node.type.name === 'bodiedSyncBlock' && syncBlockStore.sourceManager.isPendingCreation(node.attrs.resourceId)) { loadingDecorations.push(_view.Decoration.node(pos, pos + node.nodeSize, { class: _syncBlock.SyncBlockStateCssClassName.creationLoadingClassName })); } // Show sync block border while the user is dragging if (isDragging && syncBlockStore.isSyncBlock(node)) { dragDecorations.push(_view.Decoration.node(pos, pos + node.nodeSize, { class: _syncBlock.SyncBlockStateCssClassName.draggingClassName })); } }); if (api !== null && api !== void 0 && (_api$focus = api.focus) !== null && _api$focus !== void 0 && (_api$focus = _api$focus.sharedState) !== null && _api$focus !== void 0 && (_api$focus = _api$focus.currentState()) !== null && _api$focus !== void 0 && _api$focus.hasFocus || !(0, _experiments.editorExperiment)('platform_synced_block_patch_6', true, { exposure: true })) { // Don't show decorations if the editor is not focused return selectionDecorationSet.add(doc, offlineDecorations).add(doc, viewModeDecorations).add(doc, loadingDecorations).add(doc, dragDecorations); } else { return _view.DecorationSet.empty.add(doc, offlineDecorations).add(doc, viewModeDecorations).add(doc, loadingDecorations).add(doc, dragDecorations); } }, handleClickOn: (0, _selection.createSelectionClickHandler)(['bodiedSyncBlock'], function (target) { return !!target.closest(".".concat(_syncBlock.BodiedSyncBlockSharedCssClassName.prefix)); }, { useLongPressSelection: useLongPressSelection }), handleDOMEvents: { mouseover: function mouseover(view, event) { return (0, _ignoreDomEvent.shouldIgnoreDomEvent)(view, event, api); }, mousedown: function mousedown(view, event) { return (0, _ignoreDomEvent.shouldIgnoreDomEvent)(view, event, api); }, copy: function copy() { ctx.markCopyEvent(); return false; } }, transformCopied: function transformCopied(slice, _ref9) { var state = _ref9.state; var pluginState = syncedBlockPluginKey.getState(state); var syncBlockStore = pluginState === null || pluginState === void 0 ? void 0 : pluginState.syncBlockStore; var schema = state.schema; var isCopy = ctx.consumeCopyEvent(); if (!syncBlockStore || !isCopy) { return slice; } return (0, _utils.mapSlice)(slice, function (node) { if (syncBlockStore.referenceManager.isReferenceBlock(node)) { showCopiedFlag(api); return node; } if (node.type.name === 'bodiedSyncBlock' && node.attrs.resourceId) { // if we only selected part of the bodied sync block content, // remove the sync block node and only keep the content if (!(0, _utils2.sliceFullyContainsNode)(slice, node)) { return node.content; } showCopiedFlag(api); var newResourceId = syncBlockStore.referenceManager.generateResourceIdForReference(node.attrs.resourceId); // Convert bodiedSyncBlock to syncBlock // The paste transformation will regenrate the localId var newAttrs = _objectSpread(_objectSpread({}, node.attrs), {}, { resourceId: newResourceId }); var newMarks = schema.nodes.syncBlock.markSet ? node.marks.filter(function (mark) { var _schema$nodes$syncBlo; return (_schema$nodes$syncBlo = schema.nodes.syncBlock.markSet) === null || _schema$nodes$syncBlo === void 0 ? void 0 : _schema$nodes$syncBlo.includes(mark.type); }) : node.marks; return schema.nodes.syncBlock.create(newAttrs, null, newMarks); } return node; }); } }, filterTransaction: function filterTransaction(tr, state) { var _api$editorViewMode2, _api$connectivity3; var viewMode = api === null || api === void 0 || (_api$editorViewMode2 = api.editorViewMode) === null || _api$editorViewMode2 === void 0 || (_api$editorViewMode2 = _api$editorViewMode2.sharedState.currentState()) === null || _api$editorViewMode2 === void 0 ? void 0 : _api$editorViewMode2.mode; if (viewMode === 'view' && (0, _platformFeatureFlags.fg)('platform_synced_block_patch_8')) { return true; } var isOffline = (0, _editorPluginConnectivity.isOfflineMode)(api === null || api === void 0 || (_api$connectivity3 = api.connectivity) === null || _api$connectivity3 === void 0 || (_api$connectivity3 = _api$connectivity3.sharedState.currentState()) === null || _api$connectivity3 === void 0 ? void 0 : _api$connectivity3.mode); var isConfirmedSyncBlockDeletion = Boolean(tr.getMeta('isConfirmedSyncBlockDeletion')); // Track newly added reference sync blocks before processing the transaction if (tr.docChanged && !tr.getMeta('isRemote')) { var _trackSyncBlocks3 = (0, _trackSyncBlocks7.trackSyncBlocks)(syncBlockStore.referenceManager.isReferenceBlock, tr, state), added = _trackSyncBlocks3.added; added.forEach(function (nodeInfo) { var _nodeInfo$attrs; if ((_nodeInfo$attrs = nodeInfo.attrs) !== null && _nodeInfo$attrs !== void 0 && _nodeInfo$attrs.resourceId) { syncBlockStore.referenceManager.markAsNewlyAdded(nodeInfo.attrs.resourceId); } }); } if ((0, _platformFeatureFlags.fg)('platform_synced_block_update_refactor')) { // if doc changed and it's a remote transaction, check if any synced block were added, // and if so, for source synced blocks, ensure we update the cache with them // and for reference synced blocks, ensure we fetch the data from the server if (tr.docChanged && tr.getMeta('isRemote')) { var _trackSyncBlocks4 = (0, _trackSyncBlocks7.trackSyncBlocks)(function (node) { return syncBlockStore.isSyncBlock(node); }, tr, state), _added = _trackSyncBlocks4.added; var sourceSyncBlockNodes = _added.filter(function (nodeInfo) { return nodeInfo.node && syncBlockStore.sourceManager.isSourceBlock(nodeInfo.node); }); var referenceSyncBlockNodes = _added.filter(function (nodeInfo) { return nodeInfo.node && syncBlockStore.referenceManager.isReferenceBlock(nodeInfo.node); }); sourceSyncBlockNodes.forEach(function (nodeInfo) { var _nodeInfo$attrs2; if ((_nodeInfo$attrs2 = nodeInfo.attrs) !== null && _nodeInfo$attrs2 !== void 0 && _nodeInfo$attrs2.resourceId && nodeInfo.node) { syncBlockStore.sourceManager.updateSyncBlockData(nodeInfo.node, tr.getMeta('isRemote')); } }); var syncBlockNodes = referenceSyncBlockNodes.map(function (nodeInfo) { return nodeInfo.node; }).filter(function (node) { return node !== undefined; }); syncBlockStore.referenceManager.fetchSyncBlocksData((0, _editorSyncedBlockProvider.convertPMNodesToSyncBlockNodes)(syncBlockNodes)); } } if (!tr.docChanged || Boolean(tr.getMeta('isRemote')) || !isOffline && isConfirmedSyncBlockDeletion) { return true; } var _trackSyncBlocks5 = (0, _trackSyncBlocks7.trackSyncBlocks)(syncBlockStore.sourceManager.isSourceBlock, tr, state), bodiedSyncBlockRemoved = _trackSyncBlocks5.removed, bodiedSyncBlockAdded = _trackSyncBlocks5.added; return isOffline ? filterTransactionOffline({ tr: tr, state: state, syncBlockStore: syncBlockStore, api: api, isConfirmedSyncBlockDeletion: isConfirmedSyncBlockDeletion, bodiedSyncBlockRemoved: bodiedSyncBlockRemoved, bodiedSyncBlockAdded: bodiedSyncBlockAdded }) : filterTransactionOnline({ tr: tr, state: state, syncBlockStore: syncBlockStore, api: api, confirmationTransactionRef: confirmationTransactionRef, bodiedSyncBlockRemoved: bodiedSyncBlockRemoved, bodiedSyncBlockAdded: bodiedSyncBlockAdded, extensionFlagShown: extensionFlagShown }); }, appendTransaction: function appendTransaction(trs, oldState, newState) { var _api$editorViewMode3; var viewMode = api === null || api === void 0 || (_api$editorViewMode3 = api.editorViewMode) === null || _api$editorViewMode3 === void 0 || (_api$editorViewMode3 = _api$editorViewMode3.sharedState.currentState()) === null || _api$editorViewMode3 === void 0 ? void 0 : _api$editorViewMode3.mode; if (viewMode === 'view' && (0, _platformFeatureFlags.fg)('platform_synced_block_patch_8')) { return null; } // Update source sync block cache for user-initiated changes only // When fg is ON, cache updates are handled here instead of in the nodeview update() if ((0, _platformFeatureFlags.fg)('platform_synced_block_update_refactor')) { var isUserChange = function isUserChange(tr) { return tr.docChanged && !(0, _collab.isDirtyTransaction)(tr) && !tr.getMeta('isRemote'); }; var hasSourceBlockEdit = trs.some(function (tr) { return isUserChange(tr) && (0, _trackSyncBlocks7.hasEditInSyncBlock)(tr, oldState); }); if (hasSourceBlockEdit) { newState.doc.forEach(function (node) { if (syncBlockStore.sourceManager.isSourceBlock(node)) { syncBlockStore.sourceManager.updateSyncBlockData(node, false); } }); } } trs.filter(function (tr) { return tr.docChanged; }).forEach(function (tr) { if (confirmationTransactionRef.current) { confirmationTransactionRef.current = (0, _editorSyncedBlockProvider.rebaseTransaction)(confirmationTransactionRef.current, tr, newState); } }); var _iterator2 = _createForOfIteratorHelper(trs), _step2; try { var _loop = function _loop() { var tr = _step2.value; if (tr.getMeta(_utils.pmHistoryPluginKey)) { var _trackSyncBlocks6 = (0, _trackSyncBlocks7.trackSyncBlocks)(syncBlockStore.sourceManager.isSourceBlock, tr, oldState), added = _trackSyncBlocks6.added; if (added.length > 0) { // Delete bodiedSyncBlock if it's originated from history, i.e. redo creation // See filterTransaction above for more details var _tr = newState.tr; added.forEach(function (node) { if (node.from !== undefined && node.to !== undefined) { _tr.delete(node.from, node.to); } }); return { v: _tr }; } } }, _ret; for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) { _ret = _loop(); if (_ret) return _ret.v; } // Detect and remove duplicate bodiedSyncBlock resourceIds. // When a block template containing a source sync block is inserted into the // same document, it creates a duplicate with the same resourceId. We keep the // first occurrence and delete subsequent duplicates entirely (including their // contents), since a document must not contain two source sync blocks with the // same resourceId. } catch (err) { _iterator2.e(err); } finally { _iterator2.f(); } if (trs.some(function (tr) { return tr.docChanged && !tr.getMeta('isRemote'); }) && (0, _platformFeatureFlags.fg)('platform_synced_block_patch_8')) { // Quick check: only walk the full document when at least one // transaction inserted a source synced block. This avoids an // expensive descendants() traversal on every local edit. var hasInsertedSourceBlock = trs.some(function (tr) { if (!tr.docChanged || tr.getMeta('isRemote')) { return false; } return tr.steps.some(function (step) { if (!(step instanceof _transform.ReplaceStep || step instanceof _transform.ReplaceAroundStep) || !('slice' in step)) { return false; } var _ref0 = step, slice = _ref0.slice; var found = false; slice.content.descendants(function (node) { if (syncBlockStore.sourceManager.isSourceBlock(node) && node.attrs.resourceId) { found = true; } return false; }); return found; }); }); if (!hasInsertedSourceBlock) { return null; } var seenResourceIds = new Set(); var duplicates = []; newState.doc.descendants(function (node, pos) { if (syncBlockStore.sourceManager.isSourceBlock(node) && node.attrs.resourceId) { if (seenResourceIds.has(node.attrs.resourceId)) { duplicates.push({ pos: pos, nodeSize: node.nodeSize }); } else { seenResourceIds.add(node.attrs.resourceId); } return false; } }); if (duplicates.length > 0) { var tr = newState.tr; // Delete in reverse document order so positions remain valid for (var i = duplicates.length - 1; i >= 0; i--) { var dup = duplicates[i]; tr.delete(dup.pos, dup.pos + dup.nodeSize); } tr.setMeta('addToHistory', false); (0, _utils2.deferDispatch)(function () { var _api$core; api === null || api === void 0 || (_api$core = api.core) === null || _api$core === void 0 || _api$core.actions.execute(function (_ref1) { var tr = _ref1.tr; return tr.setMeta(syncedBlockPluginKey, { activeFlag: { id: _types.FLAG_ID.DUPLICATE_SOURCE_SYNC_BLOCK } }); }); }); return tr; } } return null; } }); };