@atlaskit/editor-plugin-metrics 3.1.1 → 3.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/dist/cjs/metricsPlugin.js +48 -5
- package/dist/cjs/pm-plugins/main.js +93 -39
- package/dist/cjs/pm-plugins/utils/check-tr-action-type.js +159 -0
- package/dist/es2019/metricsPlugin.js +45 -4
- package/dist/es2019/pm-plugins/main.js +94 -36
- package/dist/es2019/pm-plugins/utils/check-tr-action-type.js +152 -0
- package/dist/esm/metricsPlugin.js +48 -5
- package/dist/esm/pm-plugins/main.js +93 -39
- package/dist/esm/pm-plugins/utils/check-tr-action-type.js +152 -0
- package/dist/types/metricsPluginType.d.ts +12 -2
- package/dist/types/pm-plugins/main.d.ts +3 -0
- package/dist/types/pm-plugins/utils/check-tr-action-type.d.ts +18 -0
- package/dist/types/pm-plugins/utils/get-node-changes.d.ts +1 -1
- package/dist/types/pm-plugins/utils/is-non-text-undo.d.ts +1 -1
- package/dist/types-ts4.5/metricsPluginType.d.ts +13 -2
- package/dist/types-ts4.5/pm-plugins/main.d.ts +3 -0
- package/dist/types-ts4.5/pm-plugins/utils/check-tr-action-type.d.ts +18 -0
- package/dist/types-ts4.5/pm-plugins/utils/get-node-changes.d.ts +1 -1
- package/dist/types-ts4.5/pm-plugins/utils/is-non-text-undo.d.ts +1 -1
- package/package.json +4 -3
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { bind } from 'bind-event-listener';
|
|
2
|
-
import { AnalyticsStep } from '@atlaskit/adf-schema/steps';
|
|
3
2
|
import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
|
|
4
|
-
import { isTextInput } from '@atlaskit/editor-common/utils';
|
|
5
3
|
import { PluginKey } from '@atlaskit/editor-prosemirror/state';
|
|
4
|
+
import { AddMarkStep, ReplaceAroundStep, ReplaceStep } from '@atlaskit/editor-prosemirror/transform';
|
|
6
5
|
import { ActiveSessionTimer } from './utils/active-session-timer';
|
|
7
6
|
import { getAnalyticsPayload } from './utils/analytics';
|
|
7
|
+
import { ActionType, checkTrActionType, shouldSkipTr } from './utils/check-tr-action-type';
|
|
8
8
|
import { isNonTextUndo } from './utils/is-non-text-undo';
|
|
9
9
|
export const metricsKey = new PluginKey('metricsPlugin');
|
|
10
10
|
export const initialPluginState = {
|
|
@@ -37,20 +37,35 @@ export const createPlugin = api => {
|
|
|
37
37
|
},
|
|
38
38
|
// eslint-disable-next-line @typescript-eslint/max-params
|
|
39
39
|
apply(tr, pluginState, oldState, newState) {
|
|
40
|
+
var _meta$shouldPersistAc, _tr$steps;
|
|
41
|
+
if (tr.getMeta('isRemote') || tr.getMeta('replaceDocument')) {
|
|
42
|
+
return pluginState;
|
|
43
|
+
}
|
|
40
44
|
const meta = tr.getMeta(metricsKey);
|
|
41
|
-
|
|
45
|
+
|
|
46
|
+
// If the active session is stopped, reset the plugin state, and set initialContent to new doc content
|
|
42
47
|
if (meta && meta.stopActiveSession) {
|
|
43
48
|
return {
|
|
44
49
|
...initialPluginState,
|
|
45
50
|
initialContent: newState.doc.content
|
|
46
51
|
};
|
|
47
52
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
53
|
+
const shouldPersistActiveSession = (_meta$shouldPersistAc = meta === null || meta === void 0 ? void 0 : meta.shouldPersistActiveSession) !== null && _meta$shouldPersistAc !== void 0 ? _meta$shouldPersistAc : pluginState.shouldPersistActiveSession;
|
|
54
|
+
const hasDocChanges = tr.steps.length > 0 && ((_tr$steps = tr.steps) === null || _tr$steps === void 0 ? void 0 : _tr$steps.some(step => step instanceof ReplaceStep || step instanceof ReplaceAroundStep || step instanceof AddMarkStep));
|
|
55
|
+
let intentToStartEditTime = (meta === null || meta === void 0 ? void 0 : meta.intentToStartEditTime) || pluginState.intentToStartEditTime;
|
|
56
|
+
const now = performance.now();
|
|
57
|
+
if (!intentToStartEditTime && !hasDocChanges && !tr.storedMarksSet) {
|
|
58
|
+
return pluginState;
|
|
59
|
+
}
|
|
60
|
+
if (!intentToStartEditTime && (hasDocChanges || tr.storedMarksSet)) {
|
|
61
|
+
intentToStartEditTime = now;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Start active session timer if intentToStartEditTime is set and shouldStartTimer is true
|
|
65
|
+
// shouldPersistActiveSession is set to true when dragging block controls and when insert menu is open
|
|
66
|
+
// Timer should start when menu closes or dragging stops
|
|
67
|
+
if (intentToStartEditTime && meta !== null && meta !== void 0 && meta.shouldStartTimer && !pluginState.shouldPersistActiveSession) {
|
|
68
|
+
timer.startTimer();
|
|
54
69
|
}
|
|
55
70
|
const undoCount = isNonTextUndo(tr) ? 1 : 0;
|
|
56
71
|
const newActionTypeCount = pluginState.actionTypeCount ? {
|
|
@@ -60,36 +75,59 @@ export const createPlugin = api => {
|
|
|
60
75
|
...initialPluginState.actionTypeCount,
|
|
61
76
|
undoCount
|
|
62
77
|
};
|
|
63
|
-
|
|
64
|
-
if (tr.docChanged && canIgnoreTr()) {
|
|
65
|
-
const now = performance.now();
|
|
78
|
+
if (hasDocChanges) {
|
|
66
79
|
timer.startTimer();
|
|
67
80
|
const {
|
|
68
81
|
actionTypeCount,
|
|
69
82
|
timeOfLastTextInput,
|
|
70
83
|
totalActionCount,
|
|
71
|
-
|
|
84
|
+
previousTrType
|
|
72
85
|
} = pluginState;
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
let
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
86
|
+
if (shouldSkipTr(tr)) {
|
|
87
|
+
return pluginState;
|
|
88
|
+
}
|
|
89
|
+
const trType = checkTrActionType(tr);
|
|
90
|
+
let shouldNotIncrementActionCount = false;
|
|
91
|
+
let shouldSetTimeOfLastTextInput = false;
|
|
92
|
+
let isTextInput = false;
|
|
93
|
+
if (trType) {
|
|
94
|
+
var _trType$extraData, _previousTrType$extra;
|
|
95
|
+
const isNotNewStatus = trType.type === ActionType.UPDATING_STATUS && (previousTrType === null || previousTrType === void 0 ? void 0 : previousTrType.type) === ActionType.UPDATING_STATUS && (trType === null || trType === void 0 ? void 0 : (_trType$extraData = trType.extraData) === null || _trType$extraData === void 0 ? void 0 : _trType$extraData.statusId) === (previousTrType === null || previousTrType === void 0 ? void 0 : (_previousTrType$extra = previousTrType.extraData) === null || _previousTrType$extra === void 0 ? void 0 : _previousTrType$extra.statusId);
|
|
96
|
+
const isAddingTextToListNode = trType.type === ActionType.TEXT_INPUT && !!previousTrType && [ActionType.UPDATING_NEW_LIST_TYPE_ITEM, ActionType.INSERTING_NEW_LIST_TYPE_NODE].includes(previousTrType.type);
|
|
97
|
+
const isAddingNewListItemAfterTextInput = !!previousTrType && previousTrType.type === ActionType.TEXT_INPUT && [ActionType.UPDATING_NEW_LIST_TYPE_ITEM].includes(trType.type);
|
|
98
|
+
|
|
99
|
+
// Check if tr is textInput and only increment textInputCount if previous action was not textInput
|
|
100
|
+
isTextInput = [ActionType.TEXT_INPUT, ActionType.EMPTY_LINE_ADDED_OR_DELETED].includes(trType.type);
|
|
101
|
+
|
|
102
|
+
// timeOfLastTextInput should be set if tr includes continuous text input on the same node
|
|
103
|
+
shouldSetTimeOfLastTextInput = [ActionType.TEXT_INPUT, ActionType.EMPTY_LINE_ADDED_OR_DELETED, ActionType.UPDATING_NEW_LIST_TYPE_ITEM, ActionType.INSERTING_NEW_LIST_TYPE_NODE, ActionType.UPDATING_STATUS].includes(trType.type) || isNotNewStatus;
|
|
104
|
+
|
|
105
|
+
// Should not increase action count if tr is text input,
|
|
106
|
+
// empty line added or deleted, updating new list item or is updating same status node
|
|
107
|
+
|
|
108
|
+
shouldNotIncrementActionCount = isTextInput || isNotNewStatus || isAddingTextToListNode || isAddingNewListItemAfterTextInput;
|
|
109
|
+
}
|
|
110
|
+
let newTextInputCount = isTextInput ? actionTypeCount.textInputCount + 1 : actionTypeCount.textInputCount;
|
|
111
|
+
let newTotalActionCount = totalActionCount + 1;
|
|
112
|
+
if (timeOfLastTextInput && shouldNotIncrementActionCount) {
|
|
81
113
|
newTotalActionCount = totalActionCount;
|
|
82
114
|
}
|
|
115
|
+
if (timeOfLastTextInput && isTextInput && previousTrType && [ActionType.TEXT_INPUT, ActionType.EMPTY_LINE_ADDED_OR_DELETED, ActionType.UPDATING_NEW_LIST_TYPE_ITEM, ActionType.INSERTING_NEW_LIST_TYPE_NODE].includes(previousTrType.type)) {
|
|
116
|
+
newTextInputCount = actionTypeCount.textInputCount;
|
|
117
|
+
}
|
|
83
118
|
const newPluginState = {
|
|
84
119
|
...pluginState,
|
|
85
|
-
activeSessionTime:
|
|
120
|
+
activeSessionTime: now - intentToStartEditTime,
|
|
86
121
|
totalActionCount: newTotalActionCount,
|
|
87
|
-
timeOfLastTextInput:
|
|
122
|
+
timeOfLastTextInput: shouldSetTimeOfLastTextInput ? now : undefined,
|
|
88
123
|
contentSizeChanged: pluginState.contentSizeChanged + (newState.doc.content.size - oldState.doc.content.size),
|
|
89
124
|
actionTypeCount: {
|
|
90
125
|
...newActionTypeCount,
|
|
91
126
|
textInputCount: newTextInputCount
|
|
92
|
-
}
|
|
127
|
+
},
|
|
128
|
+
intentToStartEditTime,
|
|
129
|
+
shouldPersistActiveSession,
|
|
130
|
+
previousTrType: trType
|
|
93
131
|
};
|
|
94
132
|
return newPluginState;
|
|
95
133
|
}
|
|
@@ -97,11 +135,37 @@ export const createPlugin = api => {
|
|
|
97
135
|
...pluginState,
|
|
98
136
|
lastSelection: (meta === null || meta === void 0 ? void 0 : meta.newSelection) || pluginState.lastSelection,
|
|
99
137
|
intentToStartEditTime,
|
|
100
|
-
actionTypeCount: newActionTypeCount
|
|
138
|
+
actionTypeCount: newActionTypeCount,
|
|
139
|
+
shouldPersistActiveSession
|
|
101
140
|
};
|
|
102
141
|
}
|
|
103
142
|
},
|
|
104
143
|
view(view) {
|
|
144
|
+
var _api$blockControls;
|
|
145
|
+
const handleIsDraggingChanged = api === null || api === void 0 ? void 0 : (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 ? void 0 : _api$blockControls.sharedState.onChange(({
|
|
146
|
+
nextSharedState,
|
|
147
|
+
prevSharedState
|
|
148
|
+
}) => {
|
|
149
|
+
if (nextSharedState) {
|
|
150
|
+
api === null || api === void 0 ? void 0 : api.core.actions.execute(({
|
|
151
|
+
tr
|
|
152
|
+
}) => {
|
|
153
|
+
if (!(prevSharedState !== null && prevSharedState !== void 0 && prevSharedState.isDragging) && nextSharedState.isDragging) {
|
|
154
|
+
api === null || api === void 0 ? void 0 : api.metrics.commands.handleIntentToStartEdit({
|
|
155
|
+
shouldStartTimer: false,
|
|
156
|
+
shouldPersistActiveSession: true
|
|
157
|
+
})({
|
|
158
|
+
tr
|
|
159
|
+
});
|
|
160
|
+
} else if (prevSharedState !== null && prevSharedState !== void 0 && prevSharedState.isDragging && !nextSharedState.isDragging) {
|
|
161
|
+
api === null || api === void 0 ? void 0 : api.metrics.commands.startActiveSessionTimer()({
|
|
162
|
+
tr
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
return tr;
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
});
|
|
105
169
|
const fireAnalyticsEvent = () => {
|
|
106
170
|
const pluginState = metricsKey.getState(view.state);
|
|
107
171
|
if (!pluginState) {
|
|
@@ -128,6 +192,7 @@ export const createPlugin = api => {
|
|
|
128
192
|
fireAnalyticsEvent();
|
|
129
193
|
timer.cleanupTimer();
|
|
130
194
|
unbindBeforeUnload();
|
|
195
|
+
handleIsDraggingChanged === null || handleIsDraggingChanged === void 0 ? void 0 : handleIsDraggingChanged();
|
|
131
196
|
}
|
|
132
197
|
};
|
|
133
198
|
},
|
|
@@ -135,19 +200,12 @@ export const createPlugin = api => {
|
|
|
135
200
|
handleDOMEvents: {
|
|
136
201
|
click: view => {
|
|
137
202
|
var _pluginState$lastSele, _pluginState$lastSele2;
|
|
138
|
-
const pluginState = api === null || api === void 0 ? void 0 : api.metrics.sharedState.currentState();
|
|
139
|
-
if (!pluginState || pluginState.intentToStartEditTime) {
|
|
140
|
-
return false;
|
|
141
|
-
}
|
|
142
203
|
const newSelection = view.state.tr.selection;
|
|
143
|
-
const
|
|
204
|
+
const pluginState = api === null || api === void 0 ? void 0 : api.metrics.sharedState.currentState();
|
|
144
205
|
if ((pluginState === null || pluginState === void 0 ? void 0 : (_pluginState$lastSele = pluginState.lastSelection) === null || _pluginState$lastSele === void 0 ? void 0 : _pluginState$lastSele.from) !== newSelection.from && (pluginState === null || pluginState === void 0 ? void 0 : (_pluginState$lastSele2 = pluginState.lastSelection) === null || _pluginState$lastSele2 === void 0 ? void 0 : _pluginState$lastSele2.to) !== newSelection.to) {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
});
|
|
149
|
-
view.dispatch(newTr);
|
|
150
|
-
timer.startTimer();
|
|
206
|
+
api === null || api === void 0 ? void 0 : api.core.actions.execute(api === null || api === void 0 ? void 0 : api.metrics.commands.handleIntentToStartEdit({
|
|
207
|
+
newSelection
|
|
208
|
+
}));
|
|
151
209
|
}
|
|
152
210
|
return false;
|
|
153
211
|
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { InsertTypeAheadStep, LinkMetaStep } from '@atlaskit/adf-schema/steps';
|
|
2
|
+
import { ReplaceAroundStep, ReplaceStep } from '@atlaskit/editor-prosemirror/transform';
|
|
3
|
+
export let ActionType = /*#__PURE__*/function (ActionType) {
|
|
4
|
+
ActionType["TEXT_INPUT"] = "textInput";
|
|
5
|
+
ActionType["EMPTY_LINE_ADDED_OR_DELETED"] = "emptyLineAddedOrDeleted";
|
|
6
|
+
ActionType["INSERTED_FROM_TYPE_AHEAD"] = "insertedFromTypeAhead";
|
|
7
|
+
ActionType["INSERTING_NEW_LIST_TYPE_NODE"] = "insertingNewListTypeNode";
|
|
8
|
+
ActionType["UPDATING_NEW_LIST_TYPE_ITEM"] = "updatingNewListItem";
|
|
9
|
+
ActionType["ADDING_LINK"] = "addingLink";
|
|
10
|
+
ActionType["UPDATING_STATUS"] = "updatingStatus";
|
|
11
|
+
return ActionType;
|
|
12
|
+
}({});
|
|
13
|
+
const isTextInput = step => {
|
|
14
|
+
const {
|
|
15
|
+
slice: {
|
|
16
|
+
content
|
|
17
|
+
},
|
|
18
|
+
from,
|
|
19
|
+
to
|
|
20
|
+
} = step;
|
|
21
|
+
const node = content.firstChild;
|
|
22
|
+
const isAddingCharacter = from === to && content.childCount === 1 && !!node && !!node.text && node.text.length === 1;
|
|
23
|
+
const isDeletingCharacter = to - from === 1 && content.childCount === 0;
|
|
24
|
+
return isAddingCharacter || isDeletingCharacter;
|
|
25
|
+
};
|
|
26
|
+
const isEmptyLineAddedOrDeleted = step => {
|
|
27
|
+
const {
|
|
28
|
+
slice: {
|
|
29
|
+
content
|
|
30
|
+
},
|
|
31
|
+
from,
|
|
32
|
+
to
|
|
33
|
+
} = step;
|
|
34
|
+
const isEmptyLineDeleted = to - from === 2 && content.size === 0;
|
|
35
|
+
if (isEmptyLineDeleted) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
let isEmptyLineAdded = false;
|
|
39
|
+
content.forEach(node => {
|
|
40
|
+
isEmptyLineAdded = node.type.name === 'paragraph' && node.content.size === 0;
|
|
41
|
+
});
|
|
42
|
+
return isEmptyLineAdded;
|
|
43
|
+
};
|
|
44
|
+
const isUpdatingListTypeNode = step => {
|
|
45
|
+
const {
|
|
46
|
+
slice
|
|
47
|
+
} = step;
|
|
48
|
+
const childCount = slice.content.childCount;
|
|
49
|
+
if (childCount < 1) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
let isListTypeNode = false;
|
|
53
|
+
slice.content.forEach(node => {
|
|
54
|
+
isListTypeNode = ['decisionList', 'decisionItem', 'bulletList', 'listItem', 'orderedList', 'taskList', 'taskItem'].includes(node.type.name);
|
|
55
|
+
});
|
|
56
|
+
if (!isListTypeNode) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
return childCount === 1 ? ActionType.INSERTING_NEW_LIST_TYPE_NODE : ActionType.UPDATING_NEW_LIST_TYPE_ITEM;
|
|
60
|
+
};
|
|
61
|
+
const isUpdatingStatus = step => {
|
|
62
|
+
const {
|
|
63
|
+
slice
|
|
64
|
+
} = step;
|
|
65
|
+
const firstChild = slice.content.firstChild;
|
|
66
|
+
if (!firstChild) {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
return firstChild.type.name === 'status' && firstChild.attrs.localId;
|
|
70
|
+
};
|
|
71
|
+
const isAddingLink = tr => {
|
|
72
|
+
const hasLinkStep = tr.steps.some(step => step instanceof LinkMetaStep);
|
|
73
|
+
const hasReplaceStep = tr.steps.some(step => step instanceof ReplaceStep);
|
|
74
|
+
return hasLinkStep && !hasReplaceStep;
|
|
75
|
+
};
|
|
76
|
+
const isTypeAheadRelatedTr = tr => {
|
|
77
|
+
var _tr$getMeta;
|
|
78
|
+
if (((_tr$getMeta = tr.getMeta('typeAheadPlugin$')) === null || _tr$getMeta === void 0 ? void 0 : _tr$getMeta.action) === 'INSERT_RAW_QUERY') {
|
|
79
|
+
return 'INSERT_RAW_QUERY';
|
|
80
|
+
}
|
|
81
|
+
if (!tr.getMeta('appendedTransaction')) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
const insertTypeAheadStep = tr.steps.find(step => step instanceof InsertTypeAheadStep);
|
|
85
|
+
const replaceStep = tr.steps.find(step => step instanceof ReplaceStep);
|
|
86
|
+
if (!insertTypeAheadStep || !replaceStep) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
return insertTypeAheadStep instanceof InsertTypeAheadStep && insertTypeAheadStep.stage;
|
|
90
|
+
};
|
|
91
|
+
export const shouldSkipTr = tr => {
|
|
92
|
+
const resolvingLink = isAddingLink(tr);
|
|
93
|
+
if (resolvingLink) {
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
const typeAheadRelatedTr = isTypeAheadRelatedTr(tr);
|
|
97
|
+
if (typeAheadRelatedTr) {
|
|
98
|
+
return typeAheadRelatedTr !== 'INSERTING_ITEM';
|
|
99
|
+
} else {
|
|
100
|
+
return tr.getMeta('appendedTransaction');
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
export const checkTrActionType = tr => {
|
|
104
|
+
if (tr.getMeta('input_rule_plugin_transaction')) {
|
|
105
|
+
return {
|
|
106
|
+
type: ActionType.INSERTING_NEW_LIST_TYPE_NODE
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
const stepsLength = tr.steps.length;
|
|
110
|
+
if (stepsLength <= 0) {
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
if (isAddingLink(tr)) {
|
|
114
|
+
return {
|
|
115
|
+
type: ActionType.ADDING_LINK
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
const [firstStep] = tr.steps;
|
|
119
|
+
const isReplaceStep = firstStep instanceof ReplaceStep;
|
|
120
|
+
const isReplaceAroundStep = firstStep instanceof ReplaceAroundStep;
|
|
121
|
+
if (!isReplaceStep && !isReplaceAroundStep) {
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
if (isReplaceStep) {
|
|
125
|
+
if (isTextInput(firstStep)) {
|
|
126
|
+
return {
|
|
127
|
+
type: ActionType.TEXT_INPUT
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
if (isEmptyLineAddedOrDeleted(firstStep)) {
|
|
131
|
+
return {
|
|
132
|
+
type: ActionType.EMPTY_LINE_ADDED_OR_DELETED
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
const statusId = isUpdatingStatus(firstStep);
|
|
136
|
+
if (statusId) {
|
|
137
|
+
return {
|
|
138
|
+
type: ActionType.UPDATING_STATUS,
|
|
139
|
+
extraData: {
|
|
140
|
+
statusId
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
const updatingListTypeNode = isUpdatingListTypeNode(firstStep);
|
|
146
|
+
if (updatingListTypeNode) {
|
|
147
|
+
return {
|
|
148
|
+
type: updatingListTypeNode
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
return undefined;
|
|
152
|
+
};
|
|
@@ -4,6 +4,7 @@ import { getAnalyticsPayload } from './pm-plugins/utils/analytics';
|
|
|
4
4
|
* Metrics plugin to be added to an `EditorPresetBuilder` and used with `ComposableEditor`
|
|
5
5
|
* from `@atlaskit/editor-core`.
|
|
6
6
|
*/
|
|
7
|
+
|
|
7
8
|
export var metricsPlugin = function metricsPlugin(_ref) {
|
|
8
9
|
var api = _ref.api;
|
|
9
10
|
return {
|
|
@@ -17,25 +18,67 @@ export var metricsPlugin = function metricsPlugin(_ref) {
|
|
|
17
18
|
}];
|
|
18
19
|
},
|
|
19
20
|
commands: {
|
|
20
|
-
|
|
21
|
+
startActiveSessionTimer: function startActiveSessionTimer() {
|
|
21
22
|
return function (_ref2) {
|
|
22
23
|
var tr = _ref2.tr;
|
|
24
|
+
var pluginState = api === null || api === void 0 ? void 0 : api.metrics.sharedState.currentState();
|
|
25
|
+
if (!(pluginState !== null && pluginState !== void 0 && pluginState.intentToStartEditTime)) {
|
|
26
|
+
return tr;
|
|
27
|
+
}
|
|
28
|
+
return tr.setMeta(metricsKey, {
|
|
29
|
+
shouldStartTimer: true,
|
|
30
|
+
shouldPersistActiveSession: false
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
},
|
|
34
|
+
stopActiveSession: function stopActiveSession() {
|
|
35
|
+
return function (_ref3) {
|
|
36
|
+
var tr = _ref3.tr;
|
|
23
37
|
if (!api) {
|
|
24
38
|
return tr;
|
|
25
39
|
}
|
|
26
|
-
var newTr = tr;
|
|
27
40
|
var pluginState = api === null || api === void 0 ? void 0 : api.metrics.sharedState.currentState();
|
|
41
|
+
if (pluginState !== null && pluginState !== void 0 && pluginState.shouldPersistActiveSession) {
|
|
42
|
+
return tr;
|
|
43
|
+
}
|
|
28
44
|
if (pluginState && pluginState.totalActionCount > 0 && pluginState.activeSessionTime > 0) {
|
|
29
45
|
var payloadToSend = getAnalyticsPayload({
|
|
30
46
|
currentContent: tr.doc.content,
|
|
31
47
|
pluginState: pluginState
|
|
32
48
|
});
|
|
33
|
-
api === null || api === void 0 || api.analytics.actions.attachAnalyticsEvent(payloadToSend)(
|
|
49
|
+
api === null || api === void 0 || api.analytics.actions.attachAnalyticsEvent(payloadToSend)(tr);
|
|
34
50
|
}
|
|
35
|
-
|
|
51
|
+
tr.setMeta(metricsKey, {
|
|
36
52
|
stopActiveSession: true
|
|
37
53
|
});
|
|
38
|
-
return
|
|
54
|
+
return tr;
|
|
55
|
+
};
|
|
56
|
+
},
|
|
57
|
+
handleIntentToStartEdit: function handleIntentToStartEdit(_ref4) {
|
|
58
|
+
var newSelection = _ref4.newSelection,
|
|
59
|
+
_ref4$shouldStartTime = _ref4.shouldStartTimer,
|
|
60
|
+
shouldStartTimer = _ref4$shouldStartTime === void 0 ? true : _ref4$shouldStartTime,
|
|
61
|
+
shouldPersistActiveSession = _ref4.shouldPersistActiveSession;
|
|
62
|
+
return function (_ref5) {
|
|
63
|
+
var tr = _ref5.tr;
|
|
64
|
+
if (!api) {
|
|
65
|
+
return tr;
|
|
66
|
+
}
|
|
67
|
+
var pluginState = api === null || api === void 0 ? void 0 : api.metrics.sharedState.currentState();
|
|
68
|
+
if (!pluginState || pluginState.intentToStartEditTime) {
|
|
69
|
+
if (shouldPersistActiveSession !== (pluginState === null || pluginState === void 0 ? void 0 : pluginState.shouldPersistActiveSession)) {
|
|
70
|
+
return tr.setMeta(metricsKey, {
|
|
71
|
+
shouldPersistActiveSession: shouldPersistActiveSession
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
tr.setMeta(metricsKey, {
|
|
76
|
+
intentToStartEditTime: performance.now(),
|
|
77
|
+
shouldStartTimer: shouldStartTimer,
|
|
78
|
+
newSelection: newSelection,
|
|
79
|
+
shouldPersistActiveSession: shouldPersistActiveSession
|
|
80
|
+
});
|
|
81
|
+
return tr;
|
|
39
82
|
};
|
|
40
83
|
}
|
|
41
84
|
},
|
|
@@ -2,12 +2,12 @@ import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
|
2
2
|
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
3
3
|
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
4
4
|
import { bind } from 'bind-event-listener';
|
|
5
|
-
import { AnalyticsStep } from '@atlaskit/adf-schema/steps';
|
|
6
5
|
import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
|
|
7
|
-
import { isTextInput } from '@atlaskit/editor-common/utils';
|
|
8
6
|
import { PluginKey } from '@atlaskit/editor-prosemirror/state';
|
|
7
|
+
import { AddMarkStep, ReplaceAroundStep, ReplaceStep } from '@atlaskit/editor-prosemirror/transform';
|
|
9
8
|
import { ActiveSessionTimer } from './utils/active-session-timer';
|
|
10
9
|
import { getAnalyticsPayload } from './utils/analytics';
|
|
10
|
+
import { ActionType, checkTrActionType, shouldSkipTr } from './utils/check-tr-action-type';
|
|
11
11
|
import { isNonTextUndo } from './utils/is-non-text-undo';
|
|
12
12
|
export var metricsKey = new PluginKey('metricsPlugin');
|
|
13
13
|
export var initialPluginState = {
|
|
@@ -39,19 +39,36 @@ export var createPlugin = function createPlugin(api) {
|
|
|
39
39
|
},
|
|
40
40
|
// eslint-disable-next-line @typescript-eslint/max-params
|
|
41
41
|
apply: function apply(tr, pluginState, oldState, newState) {
|
|
42
|
+
var _meta$shouldPersistAc, _tr$steps;
|
|
43
|
+
if (tr.getMeta('isRemote') || tr.getMeta('replaceDocument')) {
|
|
44
|
+
return pluginState;
|
|
45
|
+
}
|
|
42
46
|
var meta = tr.getMeta(metricsKey);
|
|
43
|
-
|
|
47
|
+
|
|
48
|
+
// If the active session is stopped, reset the plugin state, and set initialContent to new doc content
|
|
44
49
|
if (meta && meta.stopActiveSession) {
|
|
45
50
|
return _objectSpread(_objectSpread({}, initialPluginState), {}, {
|
|
46
51
|
initialContent: newState.doc.content
|
|
47
52
|
});
|
|
48
53
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
54
|
+
var shouldPersistActiveSession = (_meta$shouldPersistAc = meta === null || meta === void 0 ? void 0 : meta.shouldPersistActiveSession) !== null && _meta$shouldPersistAc !== void 0 ? _meta$shouldPersistAc : pluginState.shouldPersistActiveSession;
|
|
55
|
+
var hasDocChanges = tr.steps.length > 0 && ((_tr$steps = tr.steps) === null || _tr$steps === void 0 ? void 0 : _tr$steps.some(function (step) {
|
|
56
|
+
return step instanceof ReplaceStep || step instanceof ReplaceAroundStep || step instanceof AddMarkStep;
|
|
57
|
+
}));
|
|
58
|
+
var intentToStartEditTime = (meta === null || meta === void 0 ? void 0 : meta.intentToStartEditTime) || pluginState.intentToStartEditTime;
|
|
59
|
+
var now = performance.now();
|
|
60
|
+
if (!intentToStartEditTime && !hasDocChanges && !tr.storedMarksSet) {
|
|
61
|
+
return pluginState;
|
|
62
|
+
}
|
|
63
|
+
if (!intentToStartEditTime && (hasDocChanges || tr.storedMarksSet)) {
|
|
64
|
+
intentToStartEditTime = now;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Start active session timer if intentToStartEditTime is set and shouldStartTimer is true
|
|
68
|
+
// shouldPersistActiveSession is set to true when dragging block controls and when insert menu is open
|
|
69
|
+
// Timer should start when menu closes or dragging stops
|
|
70
|
+
if (intentToStartEditTime && meta !== null && meta !== void 0 && meta.shouldStartTimer && !pluginState.shouldPersistActiveSession) {
|
|
71
|
+
timer.startTimer();
|
|
55
72
|
}
|
|
56
73
|
var undoCount = isNonTextUndo(tr) ? 1 : 0;
|
|
57
74
|
var newActionTypeCount = pluginState.actionTypeCount ? _objectSpread(_objectSpread({}, pluginState.actionTypeCount), {}, {
|
|
@@ -59,47 +76,90 @@ export var createPlugin = function createPlugin(api) {
|
|
|
59
76
|
}) : _objectSpread(_objectSpread({}, initialPluginState.actionTypeCount), {}, {
|
|
60
77
|
undoCount: undoCount
|
|
61
78
|
});
|
|
62
|
-
|
|
63
|
-
return !tr.steps.every(function (e) {
|
|
64
|
-
return e instanceof AnalyticsStep;
|
|
65
|
-
});
|
|
66
|
-
};
|
|
67
|
-
if (tr.docChanged && canIgnoreTr()) {
|
|
68
|
-
var now = performance.now();
|
|
79
|
+
if (hasDocChanges) {
|
|
69
80
|
timer.startTimer();
|
|
70
81
|
var actionTypeCount = pluginState.actionTypeCount,
|
|
71
82
|
timeOfLastTextInput = pluginState.timeOfLastTextInput,
|
|
72
83
|
totalActionCount = pluginState.totalActionCount,
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
var
|
|
78
|
-
var
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
84
|
+
previousTrType = pluginState.previousTrType;
|
|
85
|
+
if (shouldSkipTr(tr)) {
|
|
86
|
+
return pluginState;
|
|
87
|
+
}
|
|
88
|
+
var trType = checkTrActionType(tr);
|
|
89
|
+
var shouldNotIncrementActionCount = false;
|
|
90
|
+
var shouldSetTimeOfLastTextInput = false;
|
|
91
|
+
var isTextInput = false;
|
|
92
|
+
if (trType) {
|
|
93
|
+
var _trType$extraData, _previousTrType$extra;
|
|
94
|
+
var isNotNewStatus = trType.type === ActionType.UPDATING_STATUS && (previousTrType === null || previousTrType === void 0 ? void 0 : previousTrType.type) === ActionType.UPDATING_STATUS && (trType === null || trType === void 0 || (_trType$extraData = trType.extraData) === null || _trType$extraData === void 0 ? void 0 : _trType$extraData.statusId) === (previousTrType === null || previousTrType === void 0 || (_previousTrType$extra = previousTrType.extraData) === null || _previousTrType$extra === void 0 ? void 0 : _previousTrType$extra.statusId);
|
|
95
|
+
var isAddingTextToListNode = trType.type === ActionType.TEXT_INPUT && !!previousTrType && [ActionType.UPDATING_NEW_LIST_TYPE_ITEM, ActionType.INSERTING_NEW_LIST_TYPE_NODE].includes(previousTrType.type);
|
|
96
|
+
var isAddingNewListItemAfterTextInput = !!previousTrType && previousTrType.type === ActionType.TEXT_INPUT && [ActionType.UPDATING_NEW_LIST_TYPE_ITEM].includes(trType.type);
|
|
97
|
+
|
|
98
|
+
// Check if tr is textInput and only increment textInputCount if previous action was not textInput
|
|
99
|
+
isTextInput = [ActionType.TEXT_INPUT, ActionType.EMPTY_LINE_ADDED_OR_DELETED].includes(trType.type);
|
|
100
|
+
|
|
101
|
+
// timeOfLastTextInput should be set if tr includes continuous text input on the same node
|
|
102
|
+
shouldSetTimeOfLastTextInput = [ActionType.TEXT_INPUT, ActionType.EMPTY_LINE_ADDED_OR_DELETED, ActionType.UPDATING_NEW_LIST_TYPE_ITEM, ActionType.INSERTING_NEW_LIST_TYPE_NODE, ActionType.UPDATING_STATUS].includes(trType.type) || isNotNewStatus;
|
|
103
|
+
|
|
104
|
+
// Should not increase action count if tr is text input,
|
|
105
|
+
// empty line added or deleted, updating new list item or is updating same status node
|
|
106
|
+
|
|
107
|
+
shouldNotIncrementActionCount = isTextInput || isNotNewStatus || isAddingTextToListNode || isAddingNewListItemAfterTextInput;
|
|
108
|
+
}
|
|
109
|
+
var newTextInputCount = isTextInput ? actionTypeCount.textInputCount + 1 : actionTypeCount.textInputCount;
|
|
110
|
+
var newTotalActionCount = totalActionCount + 1;
|
|
111
|
+
if (timeOfLastTextInput && shouldNotIncrementActionCount) {
|
|
82
112
|
newTotalActionCount = totalActionCount;
|
|
83
113
|
}
|
|
114
|
+
if (timeOfLastTextInput && isTextInput && previousTrType && [ActionType.TEXT_INPUT, ActionType.EMPTY_LINE_ADDED_OR_DELETED, ActionType.UPDATING_NEW_LIST_TYPE_ITEM, ActionType.INSERTING_NEW_LIST_TYPE_NODE].includes(previousTrType.type)) {
|
|
115
|
+
newTextInputCount = actionTypeCount.textInputCount;
|
|
116
|
+
}
|
|
84
117
|
var newPluginState = _objectSpread(_objectSpread({}, pluginState), {}, {
|
|
85
|
-
activeSessionTime:
|
|
118
|
+
activeSessionTime: now - intentToStartEditTime,
|
|
86
119
|
totalActionCount: newTotalActionCount,
|
|
87
|
-
timeOfLastTextInput:
|
|
120
|
+
timeOfLastTextInput: shouldSetTimeOfLastTextInput ? now : undefined,
|
|
88
121
|
contentSizeChanged: pluginState.contentSizeChanged + (newState.doc.content.size - oldState.doc.content.size),
|
|
89
122
|
actionTypeCount: _objectSpread(_objectSpread({}, newActionTypeCount), {}, {
|
|
90
123
|
textInputCount: newTextInputCount
|
|
91
|
-
})
|
|
124
|
+
}),
|
|
125
|
+
intentToStartEditTime: intentToStartEditTime,
|
|
126
|
+
shouldPersistActiveSession: shouldPersistActiveSession,
|
|
127
|
+
previousTrType: trType
|
|
92
128
|
});
|
|
93
129
|
return newPluginState;
|
|
94
130
|
}
|
|
95
131
|
return _objectSpread(_objectSpread({}, pluginState), {}, {
|
|
96
132
|
lastSelection: (meta === null || meta === void 0 ? void 0 : meta.newSelection) || pluginState.lastSelection,
|
|
97
133
|
intentToStartEditTime: intentToStartEditTime,
|
|
98
|
-
actionTypeCount: newActionTypeCount
|
|
134
|
+
actionTypeCount: newActionTypeCount,
|
|
135
|
+
shouldPersistActiveSession: shouldPersistActiveSession
|
|
99
136
|
});
|
|
100
137
|
}
|
|
101
138
|
},
|
|
102
139
|
view: function view(_view) {
|
|
140
|
+
var _api$blockControls;
|
|
141
|
+
var handleIsDraggingChanged = api === null || api === void 0 || (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 ? void 0 : _api$blockControls.sharedState.onChange(function (_ref) {
|
|
142
|
+
var nextSharedState = _ref.nextSharedState,
|
|
143
|
+
prevSharedState = _ref.prevSharedState;
|
|
144
|
+
if (nextSharedState) {
|
|
145
|
+
api === null || api === void 0 || api.core.actions.execute(function (_ref2) {
|
|
146
|
+
var tr = _ref2.tr;
|
|
147
|
+
if (!(prevSharedState !== null && prevSharedState !== void 0 && prevSharedState.isDragging) && nextSharedState.isDragging) {
|
|
148
|
+
api === null || api === void 0 || api.metrics.commands.handleIntentToStartEdit({
|
|
149
|
+
shouldStartTimer: false,
|
|
150
|
+
shouldPersistActiveSession: true
|
|
151
|
+
})({
|
|
152
|
+
tr: tr
|
|
153
|
+
});
|
|
154
|
+
} else if (prevSharedState !== null && prevSharedState !== void 0 && prevSharedState.isDragging && !nextSharedState.isDragging) {
|
|
155
|
+
api === null || api === void 0 || api.metrics.commands.startActiveSessionTimer()({
|
|
156
|
+
tr: tr
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
return tr;
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
});
|
|
103
163
|
var fireAnalyticsEvent = function fireAnalyticsEvent() {
|
|
104
164
|
var pluginState = metricsKey.getState(_view.state);
|
|
105
165
|
if (!pluginState) {
|
|
@@ -126,6 +186,7 @@ export var createPlugin = function createPlugin(api) {
|
|
|
126
186
|
fireAnalyticsEvent();
|
|
127
187
|
timer.cleanupTimer();
|
|
128
188
|
unbindBeforeUnload();
|
|
189
|
+
handleIsDraggingChanged === null || handleIsDraggingChanged === void 0 || handleIsDraggingChanged();
|
|
129
190
|
}
|
|
130
191
|
};
|
|
131
192
|
},
|
|
@@ -133,19 +194,12 @@ export var createPlugin = function createPlugin(api) {
|
|
|
133
194
|
handleDOMEvents: {
|
|
134
195
|
click: function click(view) {
|
|
135
196
|
var _pluginState$lastSele, _pluginState$lastSele2;
|
|
136
|
-
var pluginState = api === null || api === void 0 ? void 0 : api.metrics.sharedState.currentState();
|
|
137
|
-
if (!pluginState || pluginState.intentToStartEditTime) {
|
|
138
|
-
return false;
|
|
139
|
-
}
|
|
140
197
|
var newSelection = view.state.tr.selection;
|
|
141
|
-
var
|
|
198
|
+
var pluginState = api === null || api === void 0 ? void 0 : api.metrics.sharedState.currentState();
|
|
142
199
|
if ((pluginState === null || pluginState === void 0 || (_pluginState$lastSele = pluginState.lastSelection) === null || _pluginState$lastSele === void 0 ? void 0 : _pluginState$lastSele.from) !== newSelection.from && (pluginState === null || pluginState === void 0 || (_pluginState$lastSele2 = pluginState.lastSelection) === null || _pluginState$lastSele2 === void 0 ? void 0 : _pluginState$lastSele2.to) !== newSelection.to) {
|
|
143
|
-
|
|
144
|
-
intentToStartEditTime: performance.now(),
|
|
200
|
+
api === null || api === void 0 || api.core.actions.execute(api === null || api === void 0 ? void 0 : api.metrics.commands.handleIntentToStartEdit({
|
|
145
201
|
newSelection: newSelection
|
|
146
|
-
});
|
|
147
|
-
view.dispatch(newTr);
|
|
148
|
-
timer.startTimer();
|
|
202
|
+
}));
|
|
149
203
|
}
|
|
150
204
|
return false;
|
|
151
205
|
}
|