@atlaskit/editor-plugin-interactivity
Version:
Interactivity plugin for @atlaskit/editor-core
91 lines (89 loc) • 5.3 kB
JavaScript
import { useEffect, useRef } from 'react';
import { logException } from '@atlaskit/editor-common/monitoring';
import { fireInteractivityEvent } from './analytics/fire-interactivity-event';
import { InteractivityCollector } from './collector/interactivity-collector';
/**
* Reports session-to-date interaction latency distributions for full page editor sessions
* as the `editor interactivity` operational event.
*
* The session runs for as long as the plugin's hook stays mounted, which is one editor
* mount: `MountPluginHooks` in editor-core keys hook fibers by plugin name, so a preset
* reconfigure — which destroys and recreates ProseMirror plugin views — leaves this hook,
* and the session, in place.
*/
export const interactivityPlugin = ({
api
}) => ({
name: 'interactivity',
usePluginHook({
editorView,
wrapperElement
}) {
const collectorRef = useRef(undefined);
useEffect(() => {
try {
var _api$contextIdentifie4, _api$editorViewMode2;
const collector = new InteractivityCollector({
emit: snapshot => {
var _api$analytics;
return fireInteractivityEvent(api === null || api === void 0 ? void 0 : (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions, snapshot);
},
getObjectId: () => {
var _api$contextIdentifie, _api$contextIdentifie2, _api$contextIdentifie3;
return api === null || api === void 0 ? void 0 : (_api$contextIdentifie = api.contextIdentifier) === null || _api$contextIdentifie === void 0 ? void 0 : (_api$contextIdentifie2 = _api$contextIdentifie.sharedState.currentState()) === null || _api$contextIdentifie2 === void 0 ? void 0 : (_api$contextIdentifie3 = _api$contextIdentifie2.contextIdentifierProvider) === null || _api$contextIdentifie3 === void 0 ? void 0 : _api$contextIdentifie3.objectId;
},
getSessionMode: () => {
var _api$editorViewMode, _api$editorViewMode$s;
const mode = api === null || api === void 0 ? void 0 : (_api$editorViewMode = api.editorViewMode) === null || _api$editorViewMode === void 0 ? void 0 : (_api$editorViewMode$s = _api$editorViewMode.sharedState.currentState()) === null || _api$editorViewMode$s === void 0 ? void 0 : _api$editorViewMode$s.mode;
if (mode === undefined) {
return undefined;
}
return mode === 'view' ? 'reading' : 'editing';
},
// A destroyed view still answers, with the document it was destroyed with, so
// both report nothing rather than a size the editor no longer has.
getNodeSize: () => editorView.isDestroyed ? undefined : editorView.state.doc.nodeSize,
// `getElementsByTagName` counts descendants in the browser engine, so this
// cannot overflow the stack on very large documents.
getEditorDomSize: () => editorView.isDestroyed ? undefined : editorView.dom.getElementsByTagName('*').length
});
// Effects run in declaration order, so the one below always finds it.
collectorRef.current = collector;
if (!collector.start()) {
return;
}
// Confluence live pages navigate and switch between reading and editing without
// remounting the editor. A session covers one document in one mode, so either
// change ends it and starts the next.
const unsubscribeFromObjectId = api === null || api === void 0 ? void 0 : (_api$contextIdentifie4 = api.contextIdentifier) === null || _api$contextIdentifie4 === void 0 ? void 0 : _api$contextIdentifie4.sharedState.onChange(() => collector.onObjectIdChanged());
const unsubscribeFromViewMode = api === null || api === void 0 ? void 0 : (_api$editorViewMode2 = api.editorViewMode) === null || _api$editorViewMode2 === void 0 ? void 0 : _api$editorViewMode2.sharedState.onChange(() => collector.onViewModeChanged());
return () => {
unsubscribeFromObjectId === null || unsubscribeFromObjectId === void 0 ? void 0 : unsubscribeFromObjectId();
unsubscribeFromViewMode === null || unsubscribeFromViewMode === void 0 ? void 0 : unsubscribeFromViewMode();
collector.stop();
};
} catch (error) {
// Instrumentation must not fail the editor around it, but a failure that hits every
// browser of one engine would be invisible without this.
void logException(error, {
location: 'editor-plugin-interactivity/start'
});
return;
}
// `api` is deliberately not a dependency: a preset reconfigure hands out a new
// proxy object, but the one captured here keeps resolving plugins from the same
// live registry, and restarting the session on a reconfigure would split one
// editor session in two.
}, [editorView]);
useEffect(() => {
try {
var _collectorRef$current;
(_collectorRef$current = collectorRef.current) === null || _collectorRef$current === void 0 ? void 0 : _collectorRef$current.setEditorRoot(wrapperElement);
} catch (error) {
void logException(error, {
location: 'editor-plugin-interactivity/observe'
});
}
}, [wrapperElement]);
}
});