@atlaskit/editor-plugin-collab-edit 0.1.0

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.
Files changed (69) hide show
  1. package/CHANGELOG.md +1 -0
  2. package/LICENSE.md +13 -0
  3. package/README.md +30 -0
  4. package/dist/cjs/actions.js +110 -0
  5. package/dist/cjs/analytics.js +47 -0
  6. package/dist/cjs/events/handlers.js +88 -0
  7. package/dist/cjs/events/initialize.js +58 -0
  8. package/dist/cjs/events/send-transaction.js +48 -0
  9. package/dist/cjs/index.js +128 -0
  10. package/dist/cjs/native-collab-provider-plugin.js +37 -0
  11. package/dist/cjs/participants.js +95 -0
  12. package/dist/cjs/plugin-key.js +8 -0
  13. package/dist/cjs/plugin-state.js +241 -0
  14. package/dist/cjs/plugin.js +102 -0
  15. package/dist/cjs/types.js +5 -0
  16. package/dist/cjs/utils.js +150 -0
  17. package/dist/es2019/actions.js +119 -0
  18. package/dist/es2019/analytics.js +41 -0
  19. package/dist/es2019/events/handlers.js +72 -0
  20. package/dist/es2019/events/initialize.js +44 -0
  21. package/dist/es2019/events/send-transaction.js +42 -0
  22. package/dist/es2019/index.js +86 -0
  23. package/dist/es2019/native-collab-provider-plugin.js +32 -0
  24. package/dist/es2019/participants.js +57 -0
  25. package/dist/es2019/plugin-key.js +2 -0
  26. package/dist/es2019/plugin-state.js +219 -0
  27. package/dist/es2019/plugin.js +83 -0
  28. package/dist/es2019/types.js +1 -0
  29. package/dist/es2019/utils.js +133 -0
  30. package/dist/esm/actions.js +103 -0
  31. package/dist/esm/analytics.js +41 -0
  32. package/dist/esm/events/handlers.js +82 -0
  33. package/dist/esm/events/initialize.js +51 -0
  34. package/dist/esm/events/send-transaction.js +42 -0
  35. package/dist/esm/index.js +116 -0
  36. package/dist/esm/native-collab-provider-plugin.js +31 -0
  37. package/dist/esm/participants.js +88 -0
  38. package/dist/esm/plugin-key.js +2 -0
  39. package/dist/esm/plugin-state.js +229 -0
  40. package/dist/esm/plugin.js +85 -0
  41. package/dist/esm/types.js +1 -0
  42. package/dist/esm/utils.js +136 -0
  43. package/dist/types/actions.d.ts +11 -0
  44. package/dist/types/analytics.d.ts +6 -0
  45. package/dist/types/events/handlers.d.ts +24 -0
  46. package/dist/types/events/initialize.d.ts +16 -0
  47. package/dist/types/events/send-transaction.d.ts +11 -0
  48. package/dist/types/index.d.ts +29 -0
  49. package/dist/types/native-collab-provider-plugin.d.ts +7 -0
  50. package/dist/types/participants.d.ts +18 -0
  51. package/dist/types/plugin-key.d.ts +3 -0
  52. package/dist/types/plugin-state.d.ts +25 -0
  53. package/dist/types/plugin.d.ts +11 -0
  54. package/dist/types/types.d.ts +8 -0
  55. package/dist/types/utils.d.ts +16 -0
  56. package/dist/types-ts4.5/actions.d.ts +11 -0
  57. package/dist/types-ts4.5/analytics.d.ts +6 -0
  58. package/dist/types-ts4.5/events/handlers.d.ts +24 -0
  59. package/dist/types-ts4.5/events/initialize.d.ts +16 -0
  60. package/dist/types-ts4.5/events/send-transaction.d.ts +11 -0
  61. package/dist/types-ts4.5/index.d.ts +29 -0
  62. package/dist/types-ts4.5/native-collab-provider-plugin.d.ts +7 -0
  63. package/dist/types-ts4.5/participants.d.ts +18 -0
  64. package/dist/types-ts4.5/plugin-key.d.ts +3 -0
  65. package/dist/types-ts4.5/plugin-state.d.ts +25 -0
  66. package/dist/types-ts4.5/plugin.d.ts +11 -0
  67. package/dist/types-ts4.5/types.d.ts +8 -0
  68. package/dist/types-ts4.5/utils.d.ts +16 -0
  69. package/package.json +104 -0
@@ -0,0 +1,72 @@
1
+ import { applyRemoteData, handleConnection, handleInit, handlePresence, handleTelePointer } from '../actions';
2
+ import { addSynchronyEntityAnalytics, addSynchronyErrorAnalytics } from '../analytics';
3
+ const effect = (fn, eq) => {
4
+ let previousDeps;
5
+ let cleanup;
6
+ return (...currentDeps) => {
7
+ if (cleanup && eq(previousDeps, currentDeps)) {
8
+ return cleanup;
9
+ }
10
+ cleanup = fn(...currentDeps);
11
+ previousDeps = currentDeps;
12
+ return cleanup;
13
+ };
14
+ };
15
+ export const subscribe = effect((view, provider, options, featureFlags, _providerFactory, editorAnalyticsApi) => {
16
+ let entityRef;
17
+ const entityHandlers = {
18
+ disconnectedHandler: () => {
19
+ addSynchronyEntityAnalytics(view.state, view.state.tr)('disconnected', editorAnalyticsApi);
20
+ },
21
+ errorHandler: () => {
22
+ addSynchronyEntityAnalytics(view.state, view.state.tr)('error', editorAnalyticsApi);
23
+ }
24
+ };
25
+ const unsubscribeSynchronyEntity = () => {
26
+ if (entityRef) {
27
+ entityRef.off('disconnected', entityHandlers.disconnectedHandler);
28
+ entityRef.off('error', entityHandlers.errorHandler);
29
+ }
30
+ };
31
+ const handlers = {
32
+ initHandler: data => {
33
+ view.dispatch(view.state.tr.setMeta('collabInitialised', true));
34
+ handleInit(data, view, options);
35
+ },
36
+ connectedHandler: data => handleConnection(data, view),
37
+ dataHandler: data => applyRemoteData(data, view, options),
38
+ presenceHandler: data => handlePresence(data, view),
39
+ telepointerHandler: data => handleTelePointer(data, view),
40
+ localStepsHandler: data => {
41
+ const {
42
+ steps
43
+ } = data;
44
+ const {
45
+ state
46
+ } = view;
47
+ const {
48
+ tr
49
+ } = state;
50
+ steps.forEach(step => tr.step(step));
51
+ view.dispatch(tr);
52
+ },
53
+ errorHandler: error => {
54
+ addSynchronyErrorAnalytics(view.state, view.state.tr, featureFlags, editorAnalyticsApi)(error);
55
+ },
56
+ entityHandler: ({
57
+ entity
58
+ }) => {
59
+ unsubscribeSynchronyEntity();
60
+ if (options.EXPERIMENTAL_allowInternalErrorAnalytics) {
61
+ entity.on('disconnected', entityHandlers.disconnectedHandler);
62
+ entity.on('error', entityHandlers.errorHandler);
63
+ entityRef = entity;
64
+ }
65
+ }
66
+ };
67
+ provider.on('init', handlers.initHandler).on('connected', handlers.connectedHandler).on('data', handlers.dataHandler).on('presence', handlers.presenceHandler).on('telepointer', handlers.telepointerHandler).on('local-steps', handlers.localStepsHandler).on('error', handlers.errorHandler).on('entity', handlers.entityHandler);
68
+ return () => {
69
+ unsubscribeSynchronyEntity();
70
+ provider.off('init', handlers.initHandler).off('connected', handlers.connectedHandler).off('data', handlers.dataHandler).off('presence', handlers.presenceHandler).off('telepointer', handlers.telepointerHandler).off('local-steps', handlers.localStepsHandler).off('error', handlers.errorHandler).off('entity', handlers.entityHandler);
71
+ };
72
+ }, (previousDeps, currentDeps) => currentDeps && currentDeps.every((dep, i) => dep === previousDeps[i]));
@@ -0,0 +1,44 @@
1
+ import memoizeOne from 'memoize-one';
2
+ import { Step } from '@atlaskit/editor-prosemirror/transform';
3
+ import { pluginKey } from '../plugin-key';
4
+ import { subscribe } from './handlers';
5
+ const initCollab = (collabEditProvider, view) => {
6
+ if (collabEditProvider.initialize) {
7
+ collabEditProvider.initialize(() => view.state, json => Step.fromJSON(view.state.schema, json));
8
+ }
9
+ };
10
+ const initNewCollab = (collabEditProvider, view, onSyncUpError) => {
11
+ collabEditProvider.setup({
12
+ getState: () => view.state,
13
+ onSyncUpError
14
+ });
15
+ };
16
+ const initCollabMemo = memoizeOne(initCollab);
17
+ export const initialize = ({
18
+ options,
19
+ providerFactory,
20
+ view,
21
+ featureFlags,
22
+ editorAnalyticsApi
23
+ }) => provider => {
24
+ let cleanup;
25
+ const pluginState = pluginKey.getState(view.state);
26
+ if (pluginState !== null && pluginState !== void 0 && pluginState.isReady && cleanup) {
27
+ cleanup();
28
+ }
29
+ cleanup = subscribe(view, provider, options, featureFlags, providerFactory, editorAnalyticsApi);
30
+
31
+ // Initialize provider
32
+ if (options.useNativePlugin) {
33
+ // ED-13912 For NCS we don't want to use memoizeOne because it causes
34
+ // infinite text while changing page-width
35
+ initNewCollab(provider, view, options.onSyncUpError);
36
+ } else {
37
+ /**
38
+ * We only want to initialise once, if we reload/reconfigure this plugin
39
+ * We dont want to re-init collab, it would break existing sessions
40
+ */
41
+ initCollabMemo(provider, view);
42
+ }
43
+ return cleanup;
44
+ };
@@ -0,0 +1,42 @@
1
+ import { getSendableSelection } from '../actions';
2
+ import { pluginKey } from '../plugin-key';
3
+ export const sendTransaction = ({
4
+ originalTransaction,
5
+ transactions,
6
+ oldEditorState,
7
+ newEditorState,
8
+ useNativePlugin
9
+ }) => provider => {
10
+ const docChangedTransaction = transactions.find(tr => tr.docChanged);
11
+ const currentPluginState = pluginKey.getState(newEditorState);
12
+ if (!(currentPluginState !== null && currentPluginState !== void 0 && currentPluginState.isReady)) {
13
+ return;
14
+ }
15
+ const shouldSendStepForSynchronyCollabProvider = !originalTransaction.getMeta('isRemote') &&
16
+ // TODO: ED-8995
17
+ // We need to do this check to reduce the number of race conditions when working with tables.
18
+ // This metadata is coming from the scaleTable command in table-resizing plugin
19
+ !originalTransaction.getMeta('scaleTable') && docChangedTransaction;
20
+ if (useNativePlugin || shouldSendStepForSynchronyCollabProvider) {
21
+ provider.send(docChangedTransaction, oldEditorState, newEditorState);
22
+ }
23
+ const prevPluginState = pluginKey.getState(oldEditorState);
24
+ const {
25
+ activeParticipants: prevActiveParticipants
26
+ } = prevPluginState || {};
27
+ const {
28
+ activeParticipants,
29
+ sessionId
30
+ } = currentPluginState;
31
+ const selectionChanged = !oldEditorState.selection.eq(newEditorState.selection);
32
+ const participantsChanged = prevActiveParticipants && !prevActiveParticipants.eq(activeParticipants);
33
+ if (sessionId && selectionChanged && !docChangedTransaction || sessionId && participantsChanged) {
34
+ const selection = getSendableSelection(newEditorState.selection);
35
+ const message = {
36
+ type: 'telepointer',
37
+ selection,
38
+ sessionId
39
+ };
40
+ provider.sendMessage(message);
41
+ }
42
+ };
@@ -0,0 +1,86 @@
1
+ import { collab } from 'prosemirror-collab';
2
+ import { addSynchronyErrorAnalytics } from './analytics';
3
+ import { sendTransaction } from './events/send-transaction';
4
+ import { nativeCollabProviderPlugin } from './native-collab-provider-plugin';
5
+ import { createPlugin, pluginKey } from './plugin';
6
+ export { pluginKey };
7
+ import { getAvatarColor } from './utils';
8
+ const providerBuilder = collabEditProviderPromise => async (codeToExecute, onError) => {
9
+ try {
10
+ const provider = await collabEditProviderPromise;
11
+ if (provider) {
12
+ return codeToExecute(provider);
13
+ }
14
+ } catch (err) {
15
+ if (onError) {
16
+ onError(err);
17
+ } else {
18
+ // eslint-disable-next-line no-console
19
+ console.error(err);
20
+ }
21
+ }
22
+ };
23
+ export const collabEditPlugin = ({
24
+ config: options,
25
+ api
26
+ }) => {
27
+ var _api$featureFlags;
28
+ const featureFlags = (api === null || api === void 0 ? void 0 : (_api$featureFlags = api.featureFlags) === null || _api$featureFlags === void 0 ? void 0 : _api$featureFlags.sharedState.currentState()) || {};
29
+ let providerResolver = () => {};
30
+ const collabEditProviderPromise = new Promise(_providerResolver => {
31
+ providerResolver = _providerResolver;
32
+ });
33
+ const executeProviderCode = providerBuilder(collabEditProviderPromise);
34
+ return {
35
+ name: 'collabEdit',
36
+ getSharedState(state) {
37
+ if (!state) {
38
+ return undefined;
39
+ }
40
+ const collabPluginState = pluginKey.getState(state);
41
+ return {
42
+ activeParticipants: collabPluginState === null || collabPluginState === void 0 ? void 0 : collabPluginState.activeParticipants,
43
+ sessionId: collabPluginState === null || collabPluginState === void 0 ? void 0 : collabPluginState.sessionId
44
+ };
45
+ },
46
+ actions: {
47
+ getAvatarColor
48
+ },
49
+ pmPlugins() {
50
+ const {
51
+ useNativePlugin = false,
52
+ userId
53
+ } = options || {};
54
+ return [...(useNativePlugin ? [{
55
+ name: 'pmCollab',
56
+ plugin: () => collab({
57
+ clientID: userId
58
+ })
59
+ }, {
60
+ name: 'nativeCollabProviderPlugin',
61
+ plugin: () => nativeCollabProviderPlugin({
62
+ providerPromise: collabEditProviderPromise
63
+ })
64
+ }] : []), {
65
+ name: 'collab',
66
+ plugin: ({
67
+ dispatch,
68
+ providerFactory
69
+ }) => {
70
+ return createPlugin(dispatch, providerFactory, providerResolver, executeProviderCode, options, featureFlags, api);
71
+ }
72
+ }];
73
+ },
74
+ onEditorViewStateUpdated(props) {
75
+ var _api$analytics, _options$useNativePlu;
76
+ const addErrorAnalytics = addSynchronyErrorAnalytics(props.newEditorState, props.newEditorState.tr, featureFlags, api === null || api === void 0 ? void 0 : (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions);
77
+ executeProviderCode(sendTransaction({
78
+ originalTransaction: props.originalTransaction,
79
+ transactions: props.transactions,
80
+ oldEditorState: props.oldEditorState,
81
+ newEditorState: props.newEditorState,
82
+ useNativePlugin: (_options$useNativePlu = options === null || options === void 0 ? void 0 : options.useNativePlugin) !== null && _options$useNativePlu !== void 0 ? _options$useNativePlu : false
83
+ }), addErrorAnalytics);
84
+ }
85
+ };
86
+ };
@@ -0,0 +1,32 @@
1
+ import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
2
+ import { PluginKey } from '@atlaskit/editor-prosemirror/state';
3
+ const nativeCollabProviderPluginKey = new PluginKey('nativeCollabProviderPlugin');
4
+ export const nativeCollabProviderPlugin = ({
5
+ providerPromise
6
+ }) => {
7
+ return new SafePlugin({
8
+ key: nativeCollabProviderPluginKey,
9
+ state: {
10
+ init: () => null,
11
+ apply: (tr, currentPluginState) => {
12
+ const provider = tr.getMeta(nativeCollabProviderPluginKey);
13
+ return provider ? provider : currentPluginState;
14
+ }
15
+ },
16
+ view: editorView => {
17
+ providerPromise.then(provider => {
18
+ const {
19
+ dispatch,
20
+ state
21
+ } = editorView;
22
+ const tr = state.tr;
23
+ tr.setMeta(nativeCollabProviderPluginKey, provider);
24
+ dispatch(tr);
25
+ });
26
+ return {};
27
+ }
28
+ });
29
+ };
30
+ export const getCollabProvider = editorState => {
31
+ return nativeCollabProviderPluginKey.getState(editorState);
32
+ };
@@ -0,0 +1,57 @@
1
+ export class Participants {
2
+ constructor(participants = new Map()) {
3
+ this.participants = participants;
4
+ }
5
+ add(data) {
6
+ const newSet = new Map(this.participants);
7
+ data.forEach(participant => {
8
+ newSet.set(participant.sessionId, participant);
9
+ });
10
+ return new Participants(newSet);
11
+ }
12
+ remove(sessionIds) {
13
+ const newSet = new Map(this.participants);
14
+ sessionIds.forEach(sessionId => {
15
+ newSet.delete(sessionId);
16
+ });
17
+ return new Participants(newSet);
18
+ }
19
+ update(sessionId, lastActive) {
20
+ const newSet = new Map(this.participants);
21
+ const data = newSet.get(sessionId);
22
+ if (!data) {
23
+ return this;
24
+ }
25
+ newSet.set(sessionId, {
26
+ ...data,
27
+ lastActive
28
+ });
29
+ return new Participants(newSet);
30
+ }
31
+ updateCursorPos(sessionId, cursorPos) {
32
+ const newSet = new Map(this.participants);
33
+ const data = newSet.get(sessionId);
34
+ if (!data) {
35
+ return this;
36
+ }
37
+ newSet.set(sessionId, {
38
+ ...data,
39
+ cursorPos
40
+ });
41
+ return new Participants(newSet);
42
+ }
43
+ toArray() {
44
+ return Array.from(this.participants.values());
45
+ }
46
+ get(sessionId) {
47
+ return this.participants.get(sessionId);
48
+ }
49
+ size() {
50
+ return this.participants.size;
51
+ }
52
+ eq(other) {
53
+ const left = this.toArray().map(p => p.sessionId).sort((a, b) => a > b ? -1 : 1).join('');
54
+ const right = other.toArray().map(p => p.sessionId).sort((a, b) => a > b ? -1 : 1).join('');
55
+ return left === right;
56
+ }
57
+ }
@@ -0,0 +1,2 @@
1
+ import { PluginKey } from '@atlaskit/editor-prosemirror/state';
2
+ export const pluginKey = new PluginKey('collabEditPlugin');
@@ -0,0 +1,219 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
+ import { TELEPOINTER_DIM_CLASS } from '@atlaskit/editor-common/collab';
3
+ import { browser } from '@atlaskit/editor-common/utils';
4
+ import { Selection } from '@atlaskit/editor-prosemirror/state';
5
+ import { ReplaceStep } from '@atlaskit/editor-prosemirror/transform';
6
+ import { DecorationSet } from '@atlaskit/editor-prosemirror/view';
7
+ import { Participants } from './participants';
8
+ import { createTelepointers, findPointers, getPositionOfTelepointer } from './utils';
9
+ const isReplaceStep = step => step instanceof ReplaceStep;
10
+ export { TELEPOINTER_DIM_CLASS };
11
+
12
+ /**
13
+ * Returns position where it's possible to place a decoration.
14
+ */
15
+ export const getValidPos = (tr, pos) => {
16
+ const endOfDocPos = tr.doc.nodeSize - 2;
17
+ if (pos <= endOfDocPos) {
18
+ const resolvedPos = tr.doc.resolve(pos);
19
+ const backwardSelection = Selection.findFrom(resolvedPos, -1, true);
20
+ // if there's no correct cursor position before the `pos`, we try to find it after the `pos`
21
+ const forwardSelection = Selection.findFrom(resolvedPos, 1, true);
22
+ return backwardSelection ? backwardSelection.from : forwardSelection ? forwardSelection.from : pos;
23
+ }
24
+ return endOfDocPos;
25
+ };
26
+ export class PluginState {
27
+ get decorations() {
28
+ return this.decorationSet;
29
+ }
30
+ get activeParticipants() {
31
+ return this.participants;
32
+ }
33
+ get sessionId() {
34
+ return this.sid;
35
+ }
36
+ constructor(decorations, participants, sessionId, collabInitalised = false, onError) {
37
+ // eslint-disable-next-line no-console
38
+ _defineProperty(this, "onError", error => console.error(error));
39
+ this.decorationSet = decorations;
40
+ this.participants = participants;
41
+ this.sid = sessionId;
42
+ this.isReady = collabInitalised;
43
+ this.onError = onError || this.onError;
44
+ }
45
+ getInitial(sessionId) {
46
+ const participant = this.participants.get(sessionId);
47
+ return participant ? participant.name.substring(0, 1).toUpperCase() : 'X';
48
+ }
49
+ apply(tr) {
50
+ let {
51
+ participants,
52
+ sid,
53
+ isReady
54
+ } = this;
55
+ const presenceData = tr.getMeta('presence');
56
+ const telepointerData = tr.getMeta('telepointer');
57
+ const sessionIdData = tr.getMeta('sessionId');
58
+ let collabInitialised = tr.getMeta('collabInitialised');
59
+ if (typeof collabInitialised !== 'boolean') {
60
+ collabInitialised = isReady;
61
+ }
62
+ if (sessionIdData) {
63
+ sid = sessionIdData.sid;
64
+ }
65
+ let add = [];
66
+ let remove = [];
67
+ if (presenceData) {
68
+ const {
69
+ joined = [],
70
+ left = []
71
+ } = presenceData;
72
+ participants = participants.remove(left.map(i => i.sessionId));
73
+ participants = participants.add(joined);
74
+
75
+ // Remove telepointers for users that left
76
+ left.forEach(i => {
77
+ const pointers = findPointers(i.sessionId, this.decorationSet);
78
+ if (pointers) {
79
+ remove = remove.concat(pointers);
80
+ }
81
+ });
82
+ }
83
+ if (telepointerData) {
84
+ const {
85
+ sessionId
86
+ } = telepointerData;
87
+ if (participants.get(sessionId) && sessionId !== sid) {
88
+ const oldPointers = findPointers(telepointerData.sessionId, this.decorationSet);
89
+ if (oldPointers) {
90
+ remove = remove.concat(oldPointers);
91
+ }
92
+ const endOfDocPos = tr.doc.nodeSize - 2;
93
+ const anchor = telepointerData.selection.anchor;
94
+ const head = telepointerData.selection.head;
95
+ let rawFrom = anchor < head ? anchor : head;
96
+ let rawTo = anchor >= head ? anchor : head;
97
+ if (rawFrom > endOfDocPos) {
98
+ rawFrom = endOfDocPos;
99
+ }
100
+ if (rawTo > endOfDocPos) {
101
+ rawTo = endOfDocPos;
102
+ }
103
+ const isSelection = rawTo - rawFrom > 0;
104
+ let from = 1;
105
+ let to = 1;
106
+ try {
107
+ from = getValidPos(tr, isSelection ? Math.max(rawFrom - 1, 0) : rawFrom);
108
+ to = isSelection ? getValidPos(tr, rawTo) : from;
109
+ } catch (err) {
110
+ this.onError(err);
111
+ }
112
+ add = add.concat(createTelepointers(from, to, sessionId, isSelection, this.getInitial(sessionId)));
113
+ }
114
+ }
115
+ if (tr.docChanged) {
116
+ // Adjust decoration positions to changes made by the transaction
117
+ try {
118
+ this.decorationSet = this.decorationSet.map(tr.mapping, tr.doc, {
119
+ // Reapplies decorators those got removed by the state change
120
+ onRemove: spec => {
121
+ if (spec.pointer && spec.pointer.sessionId && spec.key === `telepointer-${spec.pointer.sessionId}`) {
122
+ const step = tr.steps.filter(isReplaceStep)[0];
123
+ if (step) {
124
+ const {
125
+ sessionId
126
+ } = spec.pointer;
127
+ const {
128
+ slice: {
129
+ content: {
130
+ size
131
+ }
132
+ },
133
+ from
134
+ } = step;
135
+ const pos = getValidPos(tr, size ? Math.min(from + size, tr.doc.nodeSize - 3) : Math.max(from, 1));
136
+ add = add.concat(createTelepointers(pos, pos, sessionId, false, this.getInitial(sessionId)));
137
+ }
138
+ }
139
+ }
140
+ });
141
+ } catch (err) {
142
+ this.onError(err);
143
+ }
144
+
145
+ // Remove any selection decoration within the change range,
146
+ // takes care of the issue when after pasting we end up with a dead selection
147
+ tr.steps.filter(isReplaceStep).forEach(s => {
148
+ const {
149
+ from,
150
+ to
151
+ } = s;
152
+ this.decorationSet.find(from, to).forEach(deco => {
153
+ // `type` is private, `from` and `to` are public in latest version
154
+ // `from` != `to` means it's a selection
155
+ if (deco.from !== deco.to) {
156
+ remove.push(deco);
157
+ }
158
+ });
159
+ });
160
+ }
161
+ const {
162
+ selection
163
+ } = tr;
164
+ this.decorationSet.find().forEach(deco => {
165
+ if (deco.type.toDOM) {
166
+ const hasTelepointerDimClass = deco.type.toDOM.classList.contains(TELEPOINTER_DIM_CLASS);
167
+ if (deco.from === selection.from && deco.to === selection.to) {
168
+ if (!hasTelepointerDimClass) {
169
+ deco.type.toDOM.classList.add(TELEPOINTER_DIM_CLASS);
170
+ }
171
+
172
+ // Browser condition here to fix ED-14722 where telepointer
173
+ // decorations with side -1 in Firefox causes backspace issues.
174
+ // This is likely caused by contenteditable quirks in Firefox
175
+ if (!browser.gecko) {
176
+ deco.type.side = -1;
177
+ }
178
+ } else {
179
+ if (hasTelepointerDimClass) {
180
+ deco.type.toDOM.classList.remove(TELEPOINTER_DIM_CLASS);
181
+ }
182
+ deco.type.side = 0;
183
+ }
184
+ }
185
+ });
186
+ if (remove.length) {
187
+ this.decorationSet = this.decorationSet.remove(remove);
188
+ }
189
+ if (add.length) {
190
+ this.decorationSet = this.decorationSet.add(tr.doc, add);
191
+ }
192
+
193
+ // This piece needs to be after the decorationSet adjustments,
194
+ // otherwise it's always one step behind where the cursor is
195
+ if (telepointerData) {
196
+ const {
197
+ sessionId
198
+ } = telepointerData;
199
+ if (participants.get(sessionId)) {
200
+ const positionForScroll = getPositionOfTelepointer(sessionId, this.decorationSet);
201
+ if (positionForScroll) {
202
+ participants = participants.updateCursorPos(sessionId, positionForScroll);
203
+ }
204
+ }
205
+ }
206
+ const nextState = new PluginState(this.decorationSet, participants, sid, collabInitialised);
207
+ return PluginState.eq(nextState, this) ? this : nextState;
208
+ }
209
+ static eq(a, b) {
210
+ return a.participants === b.participants && a.sessionId === b.sessionId && a.isReady === b.isReady;
211
+ }
212
+ static init(config) {
213
+ const {
214
+ doc,
215
+ onError
216
+ } = config;
217
+ return new PluginState(DecorationSet.create(doc, []), new Participants(), undefined, undefined, onError);
218
+ }
219
+ }
@@ -0,0 +1,83 @@
1
+ import { ACTION, ACTION_SUBJECT, EVENT_TYPE, fireAnalyticsEvent } from '@atlaskit/editor-common/analytics';
2
+ import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
3
+ import { addSynchronyErrorAnalytics } from './analytics';
4
+ import { initialize } from './events/initialize';
5
+ import { pluginKey } from './plugin-key';
6
+ import { PluginState } from './plugin-state';
7
+ export { PluginState, pluginKey };
8
+ export const createPlugin = (dispatch, providerFactory, providerResolver, collabProviderCallback, options, featureFlags, pluginInjectionApi) => {
9
+ return new SafePlugin({
10
+ key: pluginKey,
11
+ state: {
12
+ init(config) {
13
+ return PluginState.init(config);
14
+ },
15
+ apply(transaction, prevPluginState, _oldEditorState, _newEditorState) {
16
+ const pluginState = prevPluginState.apply(transaction);
17
+ dispatch(pluginKey, pluginState);
18
+ return pluginState;
19
+ }
20
+ },
21
+ props: {
22
+ decorations(state) {
23
+ var _pluginKey$getState;
24
+ return (_pluginKey$getState = pluginKey.getState(state)) === null || _pluginKey$getState === void 0 ? void 0 : _pluginKey$getState.decorations;
25
+ }
26
+ },
27
+ filterTransaction(tr, state) {
28
+ const pluginState = pluginKey.getState(state);
29
+ const collabInitialiseTr = tr.getMeta('collabInitialised');
30
+
31
+ // Don't allow transactions that modifies the document before
32
+ // collab-plugin is ready.
33
+ if (collabInitialiseTr) {
34
+ return true;
35
+ }
36
+ if (!(pluginState !== null && pluginState !== void 0 && pluginState.isReady) && tr.docChanged) {
37
+ return false;
38
+ }
39
+ return true;
40
+ },
41
+ view(view) {
42
+ var _pluginInjectionApi$a, _pluginInjectionApi$a5;
43
+ const addErrorAnalytics = addSynchronyErrorAnalytics(view.state, view.state.tr, featureFlags, pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$a = pluginInjectionApi.analytics) === null || _pluginInjectionApi$a === void 0 ? void 0 : _pluginInjectionApi$a.actions);
44
+ const onSyncUpError = attributes => {
45
+ var _pluginInjectionApi$a2, _pluginInjectionApi$a3, _pluginInjectionApi$a4;
46
+ const fireAnalyticsCallback = fireAnalyticsEvent((_pluginInjectionApi$a2 = pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$a3 = pluginInjectionApi.analytics) === null || _pluginInjectionApi$a3 === void 0 ? void 0 : (_pluginInjectionApi$a4 = _pluginInjectionApi$a3.sharedState.currentState()) === null || _pluginInjectionApi$a4 === void 0 ? void 0 : _pluginInjectionApi$a4.createAnalyticsEvent) !== null && _pluginInjectionApi$a2 !== void 0 ? _pluginInjectionApi$a2 : undefined);
47
+ fireAnalyticsCallback({
48
+ payload: {
49
+ action: ACTION.NEW_COLLAB_SYNC_UP_ERROR_NO_STEPS,
50
+ actionSubject: ACTION_SUBJECT.EDITOR,
51
+ eventType: EVENT_TYPE.OPERATIONAL,
52
+ attributes
53
+ }
54
+ });
55
+ };
56
+ options.onSyncUpError = onSyncUpError;
57
+ const cleanup = collabProviderCallback(initialize({
58
+ view,
59
+ options,
60
+ providerFactory,
61
+ featureFlags,
62
+ editorAnalyticsApi: pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$a5 = pluginInjectionApi.analytics) === null || _pluginInjectionApi$a5 === void 0 ? void 0 : _pluginInjectionApi$a5.actions
63
+ }), addErrorAnalytics);
64
+ providerFactory && providerFactory.subscribe('collabEditProvider', (_name, providerPromise) => {
65
+ if (providerPromise) {
66
+ providerPromise.then(provider => providerResolver(provider));
67
+ }
68
+ });
69
+ return {
70
+ destroy() {
71
+ providerFactory.unsubscribeAll('collabEditProvider');
72
+ if (cleanup) {
73
+ cleanup.then(unsubscribe => {
74
+ if (unsubscribe) {
75
+ unsubscribe();
76
+ }
77
+ });
78
+ }
79
+ }
80
+ };
81
+ }
82
+ });
83
+ };
@@ -0,0 +1 @@
1
+ export {};