UNPKG

@atlaskit/editor-plugin-annotation

Version:

Annotation plugin for @atlaskit/editor-core

258 lines (244 loc) 11.8 kB
"use strict"; 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;