@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.
@@ -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
- let intentToStartEditTime = (meta === null || meta === void 0 ? void 0 : meta.intentToStartEditTime) || pluginState.intentToStartEditTime;
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
- if (!intentToStartEditTime) {
49
- if (tr.docChanged && !tr.getMeta('replaceDocument')) {
50
- intentToStartEditTime = performance.now();
51
- } else {
52
- return pluginState;
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
- const canIgnoreTr = () => !tr.steps.every(e => e instanceof AnalyticsStep);
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
- activeSessionTime
84
+ previousTrType
72
85
  } = pluginState;
73
- // If previous and current action is text insertion, then don't increase total action count
74
- const isActionTextInput = isTextInput(tr);
75
- let newActiveSessionTime = activeSessionTime + (now - intentToStartEditTime);
76
- let newTextInputCount = isActionTextInput ? actionTypeCount.textInputCount + 1 : actionTypeCount.textInputCount;
77
- let newTotalActionCount = pluginState.totalActionCount + 1;
78
- if (pluginState.timeOfLastTextInput && isActionTextInput) {
79
- newActiveSessionTime = activeSessionTime + (now - (timeOfLastTextInput || 0));
80
- newTextInputCount = actionTypeCount.textInputCount;
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: newActiveSessionTime,
120
+ activeSessionTime: now - intentToStartEditTime,
86
121
  totalActionCount: newTotalActionCount,
87
- timeOfLastTextInput: isActionTextInput ? now : undefined,
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 newTr = view.state.tr;
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
- newTr.setMeta(metricsKey, {
146
- intentToStartEditTime: performance.now(),
147
- newSelection: newSelection
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
- stopActiveSession: function stopActiveSession() {
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)(newTr);
49
+ api === null || api === void 0 || api.analytics.actions.attachAnalyticsEvent(payloadToSend)(tr);
34
50
  }
35
- newTr.setMeta(metricsKey, {
51
+ tr.setMeta(metricsKey, {
36
52
  stopActiveSession: true
37
53
  });
38
- return newTr;
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
- var intentToStartEditTime = (meta === null || meta === void 0 ? void 0 : meta.intentToStartEditTime) || pluginState.intentToStartEditTime;
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
- if (!intentToStartEditTime) {
50
- if (tr.docChanged && !tr.getMeta('replaceDocument')) {
51
- intentToStartEditTime = performance.now();
52
- } else {
53
- return pluginState;
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
- var canIgnoreTr = function canIgnoreTr() {
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
- activeSessionTime = pluginState.activeSessionTime;
74
- // If previous and current action is text insertion, then don't increase total action count
75
- var isActionTextInput = isTextInput(tr);
76
- var newActiveSessionTime = activeSessionTime + (now - intentToStartEditTime);
77
- var newTextInputCount = isActionTextInput ? actionTypeCount.textInputCount + 1 : actionTypeCount.textInputCount;
78
- var newTotalActionCount = pluginState.totalActionCount + 1;
79
- if (pluginState.timeOfLastTextInput && isActionTextInput) {
80
- newActiveSessionTime = activeSessionTime + (now - (timeOfLastTextInput || 0));
81
- newTextInputCount = actionTypeCount.textInputCount;
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: newActiveSessionTime,
118
+ activeSessionTime: now - intentToStartEditTime,
86
119
  totalActionCount: newTotalActionCount,
87
- timeOfLastTextInput: isActionTextInput ? now : undefined,
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 newTr = view.state.tr;
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
- newTr.setMeta(metricsKey, {
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
  }