@atlaskit/editor-plugin-annotation
Version:
Annotation plugin for @atlaskit/editor-core
258 lines (244 loc) • 11.8 kB
JavaScript
;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.shouldClearBookMarkCheck = exports.createPluginState = exports.createCommand = void 0;
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _utils = require("@atlaskit/editor-common/utils");
var _state = require("@atlaskit/editor-prosemirror/state");
var _view = require("@atlaskit/editor-prosemirror/view");
var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
var _reducer = _interopRequireDefault(require("./reducer"));
var _utils2 = require("./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; }
var handleDocChanged = function handleDocChanged(tr, prevPluginState) {
if (!tr.getMeta('replaceDocument')) {
return getSelectionChangedHandler(false)(tr, prevPluginState);
}
return _objectSpread(_objectSpread({}, prevPluginState), {}, {
dirtyAnnotations: true
});
};
/**
* Creates a handleDocChanged function with its own deleted annotations cache.
* This ensures each editor instance has its own cache, avoiding cross-contamination.
*/
var createHandleDocChanged = function createHandleDocChanged() {
// Cache for preserving deleted annotation resolved states through delete/undo cycles
// This lives in closure scope per editor instance to avoid serialization and reduce state size
var deletedAnnotationsCache = {};
return function (tr, prevPluginState) {
if (tr.getMeta('replaceDocument')) {
return _objectSpread(_objectSpread({}, prevPluginState), {}, {
dirtyAnnotations: true
});
}
var updatedState = getSelectionChangedHandler(false)(tr, prevPluginState);
// Collect annotation IDs currently in the document
var annotationIdsInDocument = new Set();
tr.doc.descendants(function (node) {
node.marks.forEach(function (mark) {
if (mark.type.name === 'annotation') {
annotationIdsInDocument.add(mark.attrs.id);
}
});
});
var annotationIdsInState = Object.keys(prevPluginState.annotations);
// Early return if annotations haven't changed
var annotationsHaveChanged = annotationIdsInDocument.size !== annotationIdsInState.length || annotationIdsInState.some(function (id) {
return !annotationIdsInDocument.has(id);
});
if (!annotationsHaveChanged) {
return updatedState;
}
// Cache deleted annotations to be able to restore their resolved states on undo
var updatedAnnotations = {};
annotationIdsInState.forEach(function (id) {
if (!annotationIdsInDocument.has(id)) {
deletedAnnotationsCache[id] = prevPluginState.annotations[id];
}
});
// Update annotations to match document state, preserving resolved states through delete/undo
// Only include annotations that have a known resolved state - don't default new annotations to false
// as this would cause them to briefly appear as unresolved before the provider sets their actual state
annotationIdsInDocument.forEach(function (id) {
var _prevPluginState$anno;
var knownState = (_prevPluginState$anno = prevPluginState.annotations[id]) !== null && _prevPluginState$anno !== void 0 ? _prevPluginState$anno : deletedAnnotationsCache[id];
if (knownState !== undefined) {
updatedAnnotations[id] = knownState;
}
});
return _objectSpread(_objectSpread({}, updatedState), {}, {
annotations: updatedAnnotations
});
};
};
/**
* We clear bookmark on the following conditions:
* 1. if current selection is an empty selection, or
* 2. if the current selection and bookmark selection are different
* @param tr
* @param editorState
* @param bookmark
* @example
*/
var shouldClearBookMarkCheck = exports.shouldClearBookMarkCheck = function shouldClearBookMarkCheck(tr, editorState, bookmark) {
if (editorState.selection.empty || !bookmark) {
return true;
} else if (editorState.selection instanceof _state.NodeSelection) {
var bookmarkSelection = bookmark === null || bookmark === void 0 ? void 0 : bookmark.resolve(tr.doc);
if (bookmarkSelection instanceof _state.NodeSelection) {
var selectionNode = editorState.selection.node;
var bookmarkNode = bookmarkSelection.node;
/**
* Currently, after updating the alt text of a mediaSingle node,
* the selection moves to the media node.
* (then will append a transaction to its parent node)
*/
if (selectionNode.type.name === 'media' && bookmarkNode.type.name === 'mediaSingle') {
var _bookmarkNode$firstCh;
return !((_bookmarkNode$firstCh = bookmarkNode.firstChild) !== null && _bookmarkNode$firstCh !== void 0 && _bookmarkNode$firstCh.eq(selectionNode));
} else {
return !bookmarkNode.eq(selectionNode);
}
}
}
// by default we discard bookmark
return true;
};
var getSelectionChangeHandlerOld = function getSelectionChangeHandlerOld(reopenCommentView) {
return function (tr, pluginState) {
if (pluginState.skipSelectionHandling) {
return _objectSpread(_objectSpread({}, pluginState), {}, {
skipSelectionHandling: false
}, reopenCommentView && {
isInlineCommentViewClosed: false
});
}
if (
// If pluginState.selectedAnnotations is annotations of block node, i.e. when a new comment is created,
// we keep it as it is so that we can show comment view component with the newly created comment
(0, _utils2.isBlockNodeAnnotationsSelected)(tr.selection, pluginState.selectedAnnotations)) {
return _objectSpread(_objectSpread({}, pluginState), reopenCommentView && {
isInlineCommentViewClosed: false
});
}
var selectedAnnotations = (0, _utils2.findAnnotationsInSelection)(tr.selection, tr.doc);
if (selectedAnnotations.length === 0) {
return _objectSpread(_objectSpread({}, pluginState), {}, {
selectedAnnotations: selectedAnnotations,
isInlineCommentViewClosed: true,
selectAnnotationMethod: undefined
});
}
if ((0, _utils2.isSelectedAnnotationsChanged)(selectedAnnotations, pluginState.selectedAnnotations)) {
return _objectSpread(_objectSpread({}, pluginState), {}, {
selectedAnnotations: selectedAnnotations,
selectAnnotationMethod: undefined
}, reopenCommentView && {
isInlineCommentViewClosed: false
});
}
return _objectSpread(_objectSpread(_objectSpread({}, pluginState), reopenCommentView && {
isInlineCommentViewClosed: false
}), {}, {
selectAnnotationMethod: undefined
});
};
};
var getSelectionChangeHandlerNew = function getSelectionChangeHandlerNew(reopenCommentView) {
return function (tr, pluginState) {
if (pluginState.skipSelectionHandling) {
return _objectSpread(_objectSpread({}, pluginState), {}, {
skipSelectionHandling: false
}, reopenCommentView && {
isInlineCommentViewClosed: false
});
}
var selectedAnnotations = (0, _utils2.findAnnotationsInSelection)(tr.selection, tr.doc);
// NOTE: I've left this commented code here as a reference that the previous old code would reset the selected annotations
// if the selection is empty. If this is no longer needed, we can remove this code.
// if (selectedAnnotations.length === 0) {
// return {
// ...pluginState,
// pendingSelectedAnnotations: selectedAnnotations,
// pendingSelectedAnnotationsUpdateCount:
// pluginState.pendingSelectedAnnotationsUpdateCount + 1,
// isInlineCommentViewClosed: true,
// selectAnnotationMethod: undefined,
// };
// }
if ((0, _utils2.isSelectedAnnotationsChanged)(selectedAnnotations, pluginState.pendingSelectedAnnotations)) {
return _objectSpread(_objectSpread({}, pluginState), {}, {
pendingSelectedAnnotations: selectedAnnotations,
pendingSelectedAnnotationsUpdateCount: pluginState.pendingSelectedAnnotationsUpdateCount + 1
}, reopenCommentView && {
isInlineCommentViewClosed: false
});
}
return _objectSpread(_objectSpread(_objectSpread({}, pluginState), reopenCommentView && {
isInlineCommentViewClosed: false
}), {}, {
selectAnnotationMethod: undefined
});
};
};
var getSelectionChangedHandler = function getSelectionChangedHandler(reopenCommentView) {
return function (tr, pluginState) {
return pluginState.isAnnotationManagerEnabled ?
// if platform_editor_comments_api_manager == true
getSelectionChangeHandlerNew(reopenCommentView)(tr, pluginState) :
// else if platform_editor_comments_api_manager == false
getSelectionChangeHandlerOld(reopenCommentView)(tr, pluginState);
};
};
// Create the handler with cache once at module level
var handleDocChangedWithSync = createHandleDocChanged();
var getDocChangedHandler = function getDocChangedHandler(tr, prevPluginState) {
// Check feature flag at runtime to support test variants
if ((0, _expValEquals.expValEquals)('platform_editor_annotations_sync_on_docchange', 'isEnabled', true)) {
return handleDocChangedWithSync(tr, prevPluginState);
}
return handleDocChanged(tr, prevPluginState);
};
var dest = (0, _utils.pluginFactory)(_utils2.inlineCommentPluginKey, _reducer.default, {
onSelectionChanged: getSelectionChangedHandler(true),
onDocChanged: getDocChangedHandler,
mapping: function mapping(tr, pluginState, editorState) {
var draftDecorationSet = pluginState.draftDecorationSet,
bookmark = pluginState.bookmark;
var mappedDecorationSet = _view.DecorationSet.empty,
mappedBookmark;
var hasMappedDecorations = false;
if (draftDecorationSet) {
mappedDecorationSet = draftDecorationSet.map(tr.mapping, tr.doc);
}
hasMappedDecorations = mappedDecorationSet.find(undefined, undefined, function (spec) {
return Object.values(_utils2.decorationKey).includes(spec.key);
}).length > 0;
// When changes to decoration target make decoration invalid (e.g. delete text, add mark to node),
// we need to reset bookmark to hide create component and to avoid invalid draft being published
// We only perform this change when document selection has changed.
if (!hasMappedDecorations && shouldClearBookMarkCheck(tr, editorState, bookmark)) {
return _objectSpread(_objectSpread({}, pluginState), {}, {
draftDecorationSet: mappedDecorationSet,
bookmark: undefined
});
}
if (bookmark) {
mappedBookmark = bookmark.map(tr.mapping);
}
// return same pluginState if mappings did not change
if (mappedBookmark === bookmark && mappedDecorationSet === draftDecorationSet) {
return pluginState;
}
return _objectSpread(_objectSpread({}, pluginState), {}, {
draftDecorationSet: mappedDecorationSet,
bookmark: mappedBookmark
});
}
});
var createPluginState = exports.createPluginState = dest.createPluginState;
var createCommand = exports.createCommand = dest.createCommand;