UNPKG

@atlaskit/renderer

Version:
350 lines (345 loc) • 13.5 kB
import React, { useCallback, useContext, useMemo, useEffect } from 'react'; // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead import uuid from 'uuid/v4'; import { AnnotationTypes, AnnotationMarkStates } from '@atlaskit/adf-schema'; import { fg } from '@atlaskit/platform-feature-flags'; import { updateWindowSelectionAroundDraft } from '../draft/dom'; import { FabricChannel } from '@atlaskit/analytics-listeners/types'; import { ACTION, ACTION_SUBJECT, EVENT_TYPE, ACTION_SUBJECT_ID } from '@atlaskit/editor-common/analytics'; import { getRendererRangeInlineNodeNames } from '../../../actions/get-renderer-range-inline-node-names'; import { RendererContext as ActionsContext } from '../../RendererActionsContext'; import { useAnnotationManagerDispatch, useAnnotationManagerState } from '../contexts/AnnotationManagerContext'; import { useAnnotationRangeDispatch, useAnnotationRangeState } from '../contexts/AnnotationRangeContext'; export var SelectionInlineCommentMounter = /*#__PURE__*/React.memo(function (props) { var Component = props.component, range = props.range, draftRange = props.draftRange, isAnnotationAllowed = props.isAnnotationAllowed, wrapperDOM = props.wrapperDOM, onCloseProps = props.onClose, documentPosition = props.documentPosition, applyAnnotation = props.applyAnnotation, createAnalyticsEvent = props.createAnalyticsEvent, generateIndexMatch = props.generateIndexMatch; var _useAnnotationRangeDi = useAnnotationRangeDispatch(), promoteSelectionToDraft = _useAnnotationRangeDi.promoteSelectionToDraft, clearSelectionDraft = _useAnnotationRangeDi.clearSelectionDraft; var _useAnnotationRangeSt = useAnnotationRangeState(), selectionDraftDocumentPosition = _useAnnotationRangeSt.selectionDraftDocumentPosition; var actions = useContext(ActionsContext); var _useAnnotationManager = useAnnotationManagerState(), isDrafting = _useAnnotationManager.isDrafting, draftId = _useAnnotationManager.draftId; var _useAnnotationManager2 = useAnnotationManagerDispatch(), annotationManager = _useAnnotationManager2.annotationManager, dispatch = _useAnnotationManager2.dispatch; var inlineNodeTypes = useMemo(function () { if (!actions.isRangeAnnotatable(range)) { return undefined; } return getRendererRangeInlineNodeNames({ pos: documentPosition, actions: actions }); }, [documentPosition, actions, range]); var onCreateCallback = useCallback(function (annotationId) { // We want to support creation on a documentPosition if the user is only using ranges // but we want to prioritize draft positions if they are being used by consumers // !!! at this point, the documentPosition can be the wrong position if the user select something else var positionToAnnotate = selectionDraftDocumentPosition || documentPosition; if (!positionToAnnotate || !applyAnnotation) { return false; } // Evaluate position validity when the user commits the position to be annotated var isCreateAllowedOnPosition = actions.isValidAnnotationPosition(positionToAnnotate); if (!isCreateAllowedOnPosition) { return false; } var annotation = { annotationId: annotationId, annotationType: AnnotationTypes.INLINE_COMMENT }; if (createAnalyticsEvent) { createAnalyticsEvent({ action: ACTION.INSERTED, actionSubject: ACTION_SUBJECT.ANNOTATION, actionSubjectId: ACTION_SUBJECT_ID.INLINE_COMMENT, attributes: { inlineNodeNames: inlineNodeTypes }, eventType: EVENT_TYPE.TRACK }).fire(FabricChannel.editor); } return applyAnnotation(positionToAnnotate, annotation); }, [actions, documentPosition, applyAnnotation, createAnalyticsEvent, inlineNodeTypes, selectionDraftDocumentPosition]); var createIndexCallback = useCallback(function () { if (!documentPosition || !generateIndexMatch) { return false; } var result = generateIndexMatch(documentPosition); if (!result) { return false; } return result; }, [documentPosition, generateIndexMatch]); var applyDraftModeCallback = useCallback(function (options) { if (!documentPosition || !isAnnotationAllowed) { if (createAnalyticsEvent) { createAnalyticsEvent({ action: ACTION.CREATE_NOT_ALLOWED, actionSubject: ACTION_SUBJECT.ANNOTATION, actionSubjectId: ACTION_SUBJECT_ID.INLINE_COMMENT, attributes: { inlineNodeNames: inlineNodeTypes, documentPosition: documentPosition, isAnnotationAllowed: isAnnotationAllowed }, eventType: EVENT_TYPE.TRACK }).fire(FabricChannel.editor); } return false; } promoteSelectionToDraft(documentPosition); if (createAnalyticsEvent) { var uniqueAnnotationsInRange = range ? actions.getAnnotationsByPosition(range) : []; createAnalyticsEvent({ action: ACTION.OPENED, actionSubject: ACTION_SUBJECT.ANNOTATION, actionSubjectId: ACTION_SUBJECT_ID.INLINE_COMMENT, eventType: EVENT_TYPE.TRACK, attributes: { overlap: uniqueAnnotationsInRange.length, inlineNodeNames: inlineNodeTypes } }).fire(FabricChannel.editor); } window.requestAnimationFrame(function () { if (options.keepNativeSelection) { updateWindowSelectionAroundDraft(documentPosition); } else { var sel = window.getSelection(); if (sel) { sel.removeAllRanges(); } } }); // at this point, the documentPosition is the position that the user has selected, // not the selectionDraftDocumentPosition // because the documentPosition is not promoted to selectionDraftDocumentPosition yet // use platform_editor_comments_api_manager here so we can clear the code path when the flag is removed var positionToAnnotate = fg('platform_editor_comments_api_manager') ? documentPosition : selectionDraftDocumentPosition || documentPosition; if (!positionToAnnotate || !applyAnnotation || !options.annotationId) { if (createAnalyticsEvent) { createAnalyticsEvent({ action: ACTION.CREATE_NOT_ALLOWED, actionSubject: ACTION_SUBJECT.ANNOTATION, actionSubjectId: ACTION_SUBJECT_ID.INLINE_COMMENT, attributes: { positionToAnnotate: positionToAnnotate, applyAnnotationMissing: !applyAnnotation, annotationId: options.annotationId }, eventType: EVENT_TYPE.TRACK }).fire(FabricChannel.editor); } return false; } var annotation = { annotationId: options.annotationId, annotationType: AnnotationTypes.INLINE_COMMENT }; return applyAnnotation(positionToAnnotate, annotation); }, [documentPosition, isAnnotationAllowed, createAnalyticsEvent, applyAnnotation, actions, range, inlineNodeTypes, promoteSelectionToDraft, selectionDraftDocumentPosition]); var removeDraftModeCallback = useCallback(function () { clearSelectionDraft(); var sel = window.getSelection(); if (sel) { sel.removeAllRanges(); } }, [clearSelectionDraft]); var onCloseCallback = useCallback(function () { if (createAnalyticsEvent) { createAnalyticsEvent({ action: ACTION.CLOSED, actionSubject: ACTION_SUBJECT.ANNOTATION, actionSubjectId: ACTION_SUBJECT_ID.INLINE_COMMENT, eventType: EVENT_TYPE.TRACK, attributes: { inlineNodeNames: inlineNodeTypes } }).fire(FabricChannel.editor); } removeDraftModeCallback(); onCloseProps(); }, [onCloseProps, removeDraftModeCallback, createAnalyticsEvent, inlineNodeTypes]); useEffect(function () { if (annotationManager) { var allowAnnotation = function allowAnnotation() { if (isDrafting) { return false; } return isAnnotationAllowed; }; annotationManager.hook('allowAnnotation', allowAnnotation); return function () { annotationManager.unhook('allowAnnotation', allowAnnotation); }; } }, [annotationManager, isAnnotationAllowed, isDrafting]); useEffect(function () { if (annotationManager) { var startDraft = function startDraft() { var _result$inlineNodeTyp; // if there is a draft in progress, we ignore it and start a new draft // this is because clearing the draft will remove the mark node from the DOM, which will cause the selection range to be invalid // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead var id = uuid(); var result = applyDraftModeCallback({ annotationId: id, keepNativeSelection: false }); if (!result) { return { success: false, reason: 'invalid-range' }; } dispatch({ type: 'setDrafting', data: { isDrafting: true, draftId: id, draftActionResult: result } }); dispatch({ type: 'resetSelectedAnnotation' }); return { success: true, // We cannot get a ref to the target element here // because the draft is not yet applied to the DOM targetElement: undefined, inlineNodeTypes: (_result$inlineNodeTyp = result.inlineNodeTypes) !== null && _result$inlineNodeTyp !== void 0 ? _result$inlineNodeTyp : [], actionResult: { step: result.step, doc: result.doc, inlineNodeTypes: result.inlineNodeTypes, targetNodeType: result.targetNodeType, originalSelection: result.originalSelection, numMatches: result.numMatches, matchIndex: result.matchIndex, pos: result.pos } }; }; annotationManager.hook('startDraft', startDraft); return function () { annotationManager.unhook('startDraft', startDraft); }; } }, [annotationManager, isDrafting, applyDraftModeCallback, actions, range, dispatch]); useEffect(function () { if (annotationManager) { var clearDraft = function clearDraft() { if (!isDrafting) { return { success: false, reason: 'draft-not-started' }; } dispatch({ type: 'setDrafting', data: { isDrafting: false, draftId: undefined, draftActionResult: undefined } }); onCloseCallback(); return { success: true }; }; annotationManager.hook('clearDraft', clearDraft); return function () { annotationManager.unhook('clearDraft', clearDraft); }; } }, [annotationManager, onCloseCallback, isDrafting, dispatch]); useEffect(function () { if (annotationManager) { var applyDraft = function applyDraft(id) { if (!isDrafting || !draftId) { return { success: false, reason: 'draft-not-started' }; } var result = onCreateCallback(id); if (!result) { return { success: false, reason: 'range-no-longer-exists' }; } onCloseCallback(); dispatch({ type: 'setDrafting', data: { isDrafting: false, draftId: undefined, draftActionResult: undefined } }); dispatch({ type: 'updateAnnotation', data: { id: id, selected: true, markState: AnnotationMarkStates.ACTIVE } }); return { success: true, targetElement: undefined, actionResult: id !== draftId ? { step: result.step, doc: result.doc, inlineNodeTypes: result.inlineNodeTypes, targetNodeType: result.targetNodeType, originalSelection: result.originalSelection, numMatches: result.numMatches, matchIndex: result.matchIndex, pos: result.pos } : undefined }; }; annotationManager.hook('applyDraft', applyDraft); return function () { annotationManager.unhook('applyDraft', applyDraft); }; } }, [annotationManager, onCreateCallback, onCloseCallback, isDrafting, draftId, dispatch]); // Please remove this NOP function when the flag platform_editor_comments_api_manager is removed. var nop = useMemo(function () { return function () { return false; }; }, []); return /*#__PURE__*/React.createElement(Component, { range: range, draftRange: draftRange // Ignored via go/ees005 // eslint-disable-next-line @atlaskit/editor/no-as-casting , wrapperDOM: wrapperDOM.current, isAnnotationAllowed: isAnnotationAllowed, onClose: annotationManager ? nop : onCloseCallback, onCreate: annotationManager ? nop : onCreateCallback, getAnnotationIndexMatch: createIndexCallback, applyDraftMode: annotationManager ? nop : applyDraftModeCallback, removeDraftMode: annotationManager ? nop : removeDraftModeCallback, inlineNodeTypes: inlineNodeTypes }); });