@atlaskit/editor-plugin-synced-block
Version:
SyncedBlock plugin for @atlaskit/editor-core
678 lines (668 loc) • 34.7 kB
JavaScript
import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
import _createClass from "@babel/runtime/helpers/createClass";
import _defineProperty from "@babel/runtime/helpers/defineProperty";
import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
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) { _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; }
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; }
import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE } from '@atlaskit/editor-common/analytics';
import { isDirtyTransaction } from '@atlaskit/editor-common/collab';
import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
import { createSelectionClickHandler } from '@atlaskit/editor-common/selection';
import { BodiedSyncBlockSharedCssClassName, SyncBlockStateCssClassName } from '@atlaskit/editor-common/sync-block';
import { mapSlice, pmHistoryPluginKey } from '@atlaskit/editor-common/utils';
import { isOfflineMode } from '@atlaskit/editor-plugin-connectivity';
import { PluginKey } from '@atlaskit/editor-prosemirror/state';
import { ReplaceAroundStep, ReplaceStep } from '@atlaskit/editor-prosemirror/transform';
import { DecorationSet, Decoration } from '@atlaskit/editor-prosemirror/view';
import { convertPMNodesToSyncBlockNodes, rebaseTransaction } from '@atlaskit/editor-synced-block-provider';
import { fg } from '@atlaskit/platform-feature-flags';
import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
import { bodiedSyncBlockNodeView, bodiedSyncBlockNodeViewOld } from '../nodeviews/bodiedSyncedBlock';
import { SyncBlock as SyncBlockView } from '../nodeviews/syncedBlock';
import { FLAG_ID } from '../types';
import { handleBodiedSyncBlockCreation } from './utils/handle-bodied-sync-block-creation';
import { handleBodiedSyncBlockRemoval } from './utils/handle-bodied-sync-block-removal';
import { shouldIgnoreDomEvent } from './utils/ignore-dom-event';
import { calculateDecorations } from './utils/selection-decorations';
import { hasEditInSyncBlock, trackSyncBlocks } from './utils/track-sync-blocks';
import { deferDispatch, wasExtensionInsertedInBodiedSyncBlock, sliceFullyContainsNode } from './utils/utils';
export var syncedBlockPluginKey = new 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 = _slicedToArray(_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) {
deferDispatch(function () {
api === null || api === void 0 || api.core.actions.execute(function (_ref) {
var tr = _ref.tr;
return tr.setMeta(syncedBlockPluginKey, {
activeFlag: {
id: 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(pmHistoryPluginKey)) || 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 = 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);
deferDispatch(function () {
api === null || api === void 0 || api.core.actions.execute(function (_ref2) {
var tr = _ref2.tr;
return tr.setMeta(syncedBlockPluginKey, {
activeFlag: {
id: editorExperiment('platform_synced_block_patch_6', true, {
exposure: true
}) ? FLAG_ID.EXTENSION_IN_SYNC_BLOCK : 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 = 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: ACTION.DELETED,
actionSubject: ACTION_SUBJECT.SYNCED_BLOCK,
actionSubjectId: ACTION_SUBJECT_ID.REFERENCE_SYNCED_BLOCK_DELETE,
attributes: {
resourceId: syncBlock.attrs.resourceId,
blockInstanceId: syncBlock.attrs.localId
},
eventType: 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: ACTION.INSERTED,
actionSubject: ACTION_SUBJECT.DOCUMENT,
actionSubjectId: ACTION_SUBJECT_ID.SYNCED_BLOCK,
attributes: {
resourceId: syncBlock.attrs.resourceId,
blockInstanceId: syncBlock.attrs.localId
},
eventType: EVENT_TYPE.TRACK
});
});
if (bodiedSyncBlockRemoved.length > 0) {
// eslint-disable-next-line no-param-reassign
confirmationTransactionRef.current = tr;
return handleBodiedSyncBlockRemoval(bodiedSyncBlockRemoved, syncBlockStore, api, confirmationTransactionRef, getDeleteReason(tr));
}
if (bodiedSyncBlockAdded.length > 0) {
if (tr.getMeta(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;
}
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 = trackSyncBlocks(syncBlockStore.referenceManager.isReferenceBlock, tr, state),
syncBlockRemoved = _trackSyncBlocks2.removed,
syncBlockAdded = _trackSyncBlocks2.added;
var errorFlag = false;
if (isConfirmedSyncBlockDeletion || bodiedSyncBlockRemoved.length > 0 || syncBlockRemoved.length > 0) {
errorFlag = FLAG_ID.CANNOT_DELETE_WHEN_OFFLINE;
} else if (bodiedSyncBlockAdded.length > 0 || syncBlockAdded.length > 0) {
errorFlag = FLAG_ID.CANNOT_CREATE_WHEN_OFFLINE;
} else if (hasEditInSyncBlock(tr, state)) {
errorFlag = FLAG_ID.CANNOT_EDIT_WHEN_OFFLINE;
}
if (errorFlag) {
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() {
_classCallCheck(this, SyncedBlockPluginContext);
_defineProperty(this, "confirmationTransactionRef", {
current: undefined
});
_defineProperty(this, "_isCopyEvent", false);
_defineProperty(this, "unpublishedFlagShown", new Set());
_defineProperty(this, "extensionFlagShown", new Set());
}
return _createClass(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;
}
}]);
}();
export var 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 () {
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);
deferDispatch(function () {
api === null || api === void 0 || api.core.actions.execute(function (_ref8) {
var tr = _ref8.tr;
return tr.setMeta(syncedBlockPluginKey, {
activeFlag: {
id: FLAG_ID.UNPUBLISHED_SYNC_BLOCK_PASTED
}
});
});
});
}
});
return new SafePlugin({
key: syncedBlockPluginKey,
state: {
init: function init(_, instance) {
var syncBlockNodes = instance.doc.children.filter(syncBlockStore.referenceManager.isReferenceBlock);
syncBlockStore.referenceManager.fetchSyncBlocksData(convertPMNodesToSyncBlockNodes(syncBlockNodes));
// Populate source sync block cache from initial document
// When fg is ON, this replaces the constructor call in the nodeview
if (fg('platform_synced_block_update_refactor')) {
instance.doc.forEach(function (node) {
if (syncBlockStore.sourceManager.isSourceBlock(node)) {
syncBlockStore.sourceManager.updateSyncBlockData(node, false);
}
});
}
return {
selectionDecorationSet: 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 = 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 = 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 SyncBlockView({
api: api,
options: options,
node: node,
view: view,
getPos: getPos,
portalProviderAPI: pmPluginFactoryParams.portalProviderAPI,
eventDispatcher: pmPluginFactoryParams.eventDispatcher,
syncBlockStore: syncBlockStore
}).init()
);
},
bodiedSyncBlock: editorExperiment('platform_synced_block_use_new_source_nodeview', true, {
exposure: true
}) ? bodiedSyncBlockNodeView({
pluginOptions: options,
pmPluginFactoryParams: pmPluginFactoryParams,
api: api,
syncBlockStore: syncBlockStore
}) : 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 : DecorationSet.empty;
var syncBlockStore = currentPluginState === null || currentPluginState === void 0 ? void 0 : currentPluginState.syncBlockStore;
var doc = state.doc;
var isOffline = 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(Decoration.node(pos, pos + node.nodeSize, {
class: SyncBlockStateCssClassName.disabledClassName
}));
}
if (syncBlockStore.isSyncBlock(node) && isViewMode) {
viewModeDecorations.push(Decoration.node(pos, pos + node.nodeSize, {
class: SyncBlockStateCssClassName.viewModeClassName
}));
}
if (node.type.name === 'bodiedSyncBlock' && syncBlockStore.sourceManager.isPendingCreation(node.attrs.resourceId)) {
loadingDecorations.push(Decoration.node(pos, pos + node.nodeSize, {
class: SyncBlockStateCssClassName.creationLoadingClassName
}));
}
// Show sync block border while the user is dragging
if (isDragging && syncBlockStore.isSyncBlock(node)) {
dragDecorations.push(Decoration.node(pos, pos + node.nodeSize, {
class: 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 || !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 DecorationSet.empty.add(doc, offlineDecorations).add(doc, viewModeDecorations).add(doc, loadingDecorations).add(doc, dragDecorations);
}
},
handleClickOn: createSelectionClickHandler(['bodiedSyncBlock'], function (target) {
return !!target.closest(".".concat(BodiedSyncBlockSharedCssClassName.prefix));
}, {
useLongPressSelection: useLongPressSelection
}),
handleDOMEvents: {
mouseover: function mouseover(view, event) {
return shouldIgnoreDomEvent(view, event, api);
},
mousedown: function mousedown(view, event) {
return 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 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 (!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' && fg('platform_synced_block_patch_8')) {
return true;
}
var isOffline = 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 = 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 (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 = 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(convertPMNodesToSyncBlockNodes(syncBlockNodes));
}
}
if (!tr.docChanged || Boolean(tr.getMeta('isRemote')) || !isOffline && isConfirmedSyncBlockDeletion) {
return true;
}
var _trackSyncBlocks5 = 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' && 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 (fg('platform_synced_block_update_refactor')) {
var isUserChange = function isUserChange(tr) {
return tr.docChanged && !isDirtyTransaction(tr) && !tr.getMeta('isRemote');
};
var hasSourceBlockEdit = trs.some(function (tr) {
return isUserChange(tr) && 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 = rebaseTransaction(confirmationTransactionRef.current, tr, newState);
}
});
var _iterator2 = _createForOfIteratorHelper(trs),
_step2;
try {
var _loop = function _loop() {
var tr = _step2.value;
if (tr.getMeta(pmHistoryPluginKey)) {
var _trackSyncBlocks6 = 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');
}) && 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 ReplaceStep || step instanceof 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);
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: FLAG_ID.DUPLICATE_SOURCE_SYNC_BLOCK
}
});
});
});
return tr;
}
}
return null;
}
});
};