UNPKG

@atlaskit/editor-plugin-annotation

Version:

Annotation plugin for @atlaskit/editor-core

200 lines (198 loc) 9.01 kB
import React from 'react'; import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE, INPUT_METHOD, MODE } from '@atlaskit/editor-common/analytics'; import { ToolTipContent, addInlineComment } from '@atlaskit/editor-common/keymaps'; import { currentMediaNodeWithPos } from '@atlaskit/editor-common/media-single'; import { annotationMessages } from '@atlaskit/editor-common/messages'; import { calculateToolbarPositionAboveSelection, calculateToolbarPositionTrackHead, getRangeInlineNodeNames } from '@atlaskit/editor-common/utils'; import { isOfflineMode } from '@atlaskit/editor-plugin-connectivity'; import CommentIcon from '@atlaskit/icon/core/comment'; import { fg } from '@atlaskit/platform-feature-flags'; import { setInlineCommentDraftState } from '../editor-commands'; import { AnnotationSelectionType, AnnotationTestIds } from '../types'; import { getPluginState, isSelectionValid, resolveDraftBookmark } from './utils'; const getValidNodes = state => { const { schema } = state; const { annotation } = schema.marks; return Object.keys(schema.nodes).reduce((acc, current) => { const type = schema.nodes[current]; if (type.allowsMarkType(annotation)) { acc.push(type); } return acc; }, []); }; /** * Should suppress toolbars when the user is creating an inline comment * This only applies when the selection range exactly matches the bookmark range * which should be the case immediately after the comment button is clicked * if the user creates a different selection range, the floating toolbar should still be shown * @param root0 * @param root0.state * @param root0.bookmark * @example */ export const shouldSuppressFloatingToolbar = ({ state, bookmark }) => { if (!bookmark) { return false; } const { tr } = state; const resolvedBookmark = bookmark.resolve(tr.doc); const isSelectionMatchingBookmark = resolvedBookmark.to === tr.selection.to && resolvedBookmark.from === tr.selection.from; return isSelectionMatchingBookmark; }; export const buildSuppressedToolbar = state => { return { items: [], nodeType: getValidNodes(state), title: 'Annotation suppressed toolbar' }; }; export const buildToolbar = editorAnalyticsAPI => ({ state, intl, isToolbarAbove = false, _supportedNodes = [], api, createCommentExperience, annotationManager, onCommentButtonMount, getCanAddComments = () => true, contentType }) => { var _api$connectivity, _api$connectivity$sha, _api$connectivity$sha2; const selectionValid = isSelectionValid(state); const isMediaSelected = currentMediaNodeWithPos(state); // comments on media can only be added via media floating toolbar if (isMediaSelected || selectionValid === AnnotationSelectionType.INVALID) { return undefined; } const createCommentMessage = intl.formatMessage(annotationMessages.createComment); const commentDisabledMessage = intl.formatMessage(fg('editor_inline_comments_on_inline_nodes') ? annotationMessages.createCommentDisabled : annotationMessages.createCommentInvalid); const canAddComments = getCanAddComments(); const isCommentButtonDisabled = !canAddComments || selectionValid === AnnotationSelectionType.DISABLED; const disabledTooltipContent = !canAddComments ? intl.formatMessage(annotationMessages.noPermissionToAddComment, { contentType }) : commentDisabledMessage; const createComment = { type: 'button', showTitle: true, disabled: isCommentButtonDisabled || isOfflineMode(api === null || api === void 0 ? void 0 : (_api$connectivity = api.connectivity) === null || _api$connectivity === void 0 ? void 0 : (_api$connectivity$sha = _api$connectivity.sharedState) === null || _api$connectivity$sha === void 0 ? void 0 : (_api$connectivity$sha2 = _api$connectivity$sha.currentState()) === null || _api$connectivity$sha2 === void 0 ? void 0 : _api$connectivity$sha2.mode), testId: AnnotationTestIds.floatingToolbarCreateButton, interactionName: 'start-inline-comment-action', icon: CommentIcon, iconFallback: CommentIcon, tooltipContent: isCommentButtonDisabled ? disabledTooltipContent : /*#__PURE__*/React.createElement(ToolTipContent, { description: createCommentMessage, keymap: addInlineComment }), title: createCommentMessage, onMount: () => { var _getRangeInlineNodeNa; if (fg('confluence_frontend_preload_inline_comment_editor')) { onCommentButtonMount && onCommentButtonMount(); } // Check if the selection includes an non-text inline node const inlineCommentPluginState = getPluginState(state); const inlineNodeNames = (_getRangeInlineNodeNa = getRangeInlineNodeNames({ doc: state.doc, pos: resolveDraftBookmark(state, inlineCommentPluginState === null || inlineCommentPluginState === void 0 ? void 0 : inlineCommentPluginState.bookmark) })) !== null && _getRangeInlineNodeNa !== void 0 ? _getRangeInlineNodeNa : []; const isNonTextInlineNodeInludedInComment = inlineNodeNames.filter(nodeName => nodeName !== 'text').length > 0; if (editorAnalyticsAPI) { editorAnalyticsAPI.fireAnalyticsEvent({ action: ACTION.VIEWED, actionSubject: ACTION_SUBJECT.BUTTON, actionSubjectId: ACTION_SUBJECT_ID.INLINE_COMMENT, eventType: EVENT_TYPE.UI, attributes: { isNonTextInlineNodeInludedInComment, isDisabled: selectionValid === AnnotationSelectionType.DISABLED, inputMethod: INPUT_METHOD.FLOATING_TB, mode: MODE.EDITOR } }); } }, onClick: (state, dispatch) => { editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 ? void 0 : editorAnalyticsAPI.fireAnalyticsEvent({ action: ACTION.CLICKED, actionSubject: ACTION_SUBJECT.BUTTON, actionSubjectId: ACTION_SUBJECT_ID.CREATE_INLINE_COMMENT_FROM_HIGHLIGHT_ACTIONS_MENU, eventType: EVENT_TYPE.UI, attributes: { source: 'highlightActionsMenu', pageMode: 'edit' } }); if (annotationManager) { annotationManager.checkPreemptiveGate().then(canStartDraft => { if (canStartDraft) { createCommentExperience === null || createCommentExperience === void 0 ? void 0 : createCommentExperience.start({ attributes: { pageClass: 'editor', commentType: 'inline', entryPoint: 'highlightActions' } }); createCommentExperience === null || createCommentExperience === void 0 ? void 0 : createCommentExperience.initExperience.start(); const result = annotationManager.startDraft(); if (!result.success) { // Fire an analytics event to indicate that the user has clicked the button // but the action was not completed, the result should contain a reason. editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 ? void 0 : editorAnalyticsAPI.fireAnalyticsEvent({ action: ACTION.ERROR, actionSubject: ACTION_SUBJECT.ANNOTATION, actionSubjectId: ACTION_SUBJECT_ID.INLINE_COMMENT, eventType: EVENT_TYPE.OPERATIONAL, attributes: { errorReason: `toolbar-start-draft-failed/${result.reason}` } }); } } }).catch(() => { editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 ? void 0 : editorAnalyticsAPI.fireAnalyticsEvent({ action: ACTION.ERROR, actionSubject: ACTION_SUBJECT.ANNOTATION, actionSubjectId: ACTION_SUBJECT_ID.INLINE_COMMENT, eventType: EVENT_TYPE.OPERATIONAL, attributes: { errorReason: `toolbar-start-draft-preemptive-gate-error` } }); }); return true; } else { createCommentExperience === null || createCommentExperience === void 0 ? void 0 : createCommentExperience.start({ attributes: { pageClass: 'editor', commentType: 'inline', entryPoint: 'highlightActions' } }); createCommentExperience === null || createCommentExperience === void 0 ? void 0 : createCommentExperience.initExperience.start(); return setInlineCommentDraftState(editorAnalyticsAPI)(true)(state, dispatch); } }, supportsViewMode: true // TODO: MODES-3950 - Clean up this floating toolbar view mode logic, }; const toolbarTitle = intl.formatMessage(annotationMessages.toolbar); const calcToolbarPosition = isToolbarAbove ? calculateToolbarPositionAboveSelection : calculateToolbarPositionTrackHead; const onPositionCalculated = calcToolbarPosition(toolbarTitle); return { title: toolbarTitle, nodeType: getValidNodes(state), items: [createComment], onPositionCalculated, pluginName: 'annotation' }; };