@atlaskit/editor-core
Version:
A package contains Atlassian editor core functionality
83 lines (81 loc) • 3.69 kB
JavaScript
import { useCallback, useEffect, useRef } from 'react';
import { ACTION, ACTION_SUBJECT, EVENT_TYPE, getAnalyticsEventsFromTransaction } from '@atlaskit/editor-common/analytics';
import { isDirtyTransaction } from '@atlaskit/editor-common/collab';
import { getDocStructure } from '@atlaskit/editor-common/core-utils';
import { startMeasure, stopMeasure } from '@atlaskit/editor-common/performance-measures';
import { findChangedNodesFromTransaction } from '../../utils/findChangedNodesFromTransaction';
import { freezeUnsafeTransactionProperties } from '../../utils/performance/safer-transactions';
import { EVENT_NAME_ON_CHANGE } from '../../utils/performance/track-transactions';
import { validateNodes, validNode } from '../../utils/validateNodes';
export const useDispatchTransaction = ({
onChange,
dispatchAnalyticsEvent,
onEditorViewUpdated,
isRemoteReplaceDocumentTransaction
}) => {
// We need to have a ref to the latest `onChange` since the `dispatchTransaction` gets captured
const onChangeRef = useRef(onChange);
useEffect(() => {
onChangeRef.current = onChange;
}, [onChange]);
const dispatchTransaction = useCallback((view, unsafeTransaction) => {
if (!view) {
return;
}
const nodes = findChangedNodesFromTransaction(unsafeTransaction);
const changedNodesValid = validateNodes(nodes);
const transaction = new Proxy(unsafeTransaction, freezeUnsafeTransactionProperties({
dispatchAnalyticsEvent: dispatchAnalyticsEvent,
pluginKey: 'unknown-reacteditorview'
}));
// If the transaction is a remote replaceDocument transaction, we should skip validation.
// Remote replaceDocument transactions are fired when the document is replaced by initialization of editor-plugin-collab-edit
// If there is a discrepancy in the ProseMirror schema at initialization, it results in the editor being loaded with no content,
// giving the user the impression that content has been lost
const isRemoteReplace = isRemoteReplaceDocumentTransaction ? isRemoteReplaceDocumentTransaction(transaction) : false;
if (changedNodesValid || isRemoteReplace) {
const oldEditorState = view.state;
// go ahead and update the state now we know the transaction is good
const {
state: newEditorState,
transactions
} = view.state.applyTransaction(transaction);
if (newEditorState === oldEditorState) {
return;
}
view.updateState(newEditorState);
onEditorViewUpdated({
originalTransaction: transaction,
transactions,
oldEditorState,
newEditorState
});
if (onChangeRef.current && transaction.docChanged) {
const source = transaction.getMeta('isRemote') ? 'remote' : 'local';
const isDirtyChange = isDirtyTransaction(transaction);
startMeasure(EVENT_NAME_ON_CHANGE);
onChangeRef.current(view, {
source,
isDirtyChange
});
stopMeasure(EVENT_NAME_ON_CHANGE);
}
}
if (!changedNodesValid) {
const invalidNodes = nodes.filter(node => !validNode(node)).map(node => getDocStructure(node, {
compact: true
}));
dispatchAnalyticsEvent({
action: ACTION.DISPATCHED_INVALID_TRANSACTION,
actionSubject: ACTION_SUBJECT.EDITOR,
eventType: EVENT_TYPE.OPERATIONAL,
attributes: {
analyticsEventPayloads: getAnalyticsEventsFromTransaction(transaction),
invalidNodes,
isRemoteReplaceDocumentTransaction: isRemoteReplace
}
});
}
}, [dispatchAnalyticsEvent, onEditorViewUpdated, isRemoteReplaceDocumentTransaction]);
return dispatchTransaction;
};