@atlaskit/renderer
Version:
Renderer component
350 lines (345 loc) • 13.5 kB
JavaScript
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
});
});