@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.
- package/CHANGELOG.md +1 -0
- package/LICENSE.md +13 -0
- package/README.md +30 -0
- package/dist/cjs/actions.js +110 -0
- package/dist/cjs/analytics.js +47 -0
- package/dist/cjs/events/handlers.js +88 -0
- package/dist/cjs/events/initialize.js +58 -0
- package/dist/cjs/events/send-transaction.js +48 -0
- package/dist/cjs/index.js +128 -0
- package/dist/cjs/native-collab-provider-plugin.js +37 -0
- package/dist/cjs/participants.js +95 -0
- package/dist/cjs/plugin-key.js +8 -0
- package/dist/cjs/plugin-state.js +241 -0
- package/dist/cjs/plugin.js +102 -0
- package/dist/cjs/types.js +5 -0
- package/dist/cjs/utils.js +150 -0
- package/dist/es2019/actions.js +119 -0
- package/dist/es2019/analytics.js +41 -0
- package/dist/es2019/events/handlers.js +72 -0
- package/dist/es2019/events/initialize.js +44 -0
- package/dist/es2019/events/send-transaction.js +42 -0
- package/dist/es2019/index.js +86 -0
- package/dist/es2019/native-collab-provider-plugin.js +32 -0
- package/dist/es2019/participants.js +57 -0
- package/dist/es2019/plugin-key.js +2 -0
- package/dist/es2019/plugin-state.js +219 -0
- package/dist/es2019/plugin.js +83 -0
- package/dist/es2019/types.js +1 -0
- package/dist/es2019/utils.js +133 -0
- package/dist/esm/actions.js +103 -0
- package/dist/esm/analytics.js +41 -0
- package/dist/esm/events/handlers.js +82 -0
- package/dist/esm/events/initialize.js +51 -0
- package/dist/esm/events/send-transaction.js +42 -0
- package/dist/esm/index.js +116 -0
- package/dist/esm/native-collab-provider-plugin.js +31 -0
- package/dist/esm/participants.js +88 -0
- package/dist/esm/plugin-key.js +2 -0
- package/dist/esm/plugin-state.js +229 -0
- package/dist/esm/plugin.js +85 -0
- package/dist/esm/types.js +1 -0
- package/dist/esm/utils.js +136 -0
- package/dist/types/actions.d.ts +11 -0
- package/dist/types/analytics.d.ts +6 -0
- package/dist/types/events/handlers.d.ts +24 -0
- package/dist/types/events/initialize.d.ts +16 -0
- package/dist/types/events/send-transaction.d.ts +11 -0
- package/dist/types/index.d.ts +29 -0
- package/dist/types/native-collab-provider-plugin.d.ts +7 -0
- package/dist/types/participants.d.ts +18 -0
- package/dist/types/plugin-key.d.ts +3 -0
- package/dist/types/plugin-state.d.ts +25 -0
- package/dist/types/plugin.d.ts +11 -0
- package/dist/types/types.d.ts +8 -0
- package/dist/types/utils.d.ts +16 -0
- package/dist/types-ts4.5/actions.d.ts +11 -0
- package/dist/types-ts4.5/analytics.d.ts +6 -0
- package/dist/types-ts4.5/events/handlers.d.ts +24 -0
- package/dist/types-ts4.5/events/initialize.d.ts +16 -0
- package/dist/types-ts4.5/events/send-transaction.d.ts +11 -0
- package/dist/types-ts4.5/index.d.ts +29 -0
- package/dist/types-ts4.5/native-collab-provider-plugin.d.ts +7 -0
- package/dist/types-ts4.5/participants.d.ts +18 -0
- package/dist/types-ts4.5/plugin-key.d.ts +3 -0
- package/dist/types-ts4.5/plugin-state.d.ts +25 -0
- package/dist/types-ts4.5/plugin.d.ts +11 -0
- package/dist/types-ts4.5/types.d.ts +8 -0
- package/dist/types-ts4.5/utils.d.ts +16 -0
- package/package.json +104 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { ACTION, ACTION_SUBJECT, EVENT_TYPE } from '@atlaskit/editor-common/analytics';
|
|
2
|
+
import { colors } from '@atlaskit/editor-common/collab';
|
|
3
|
+
import { ZERO_WIDTH_JOINER } from '@atlaskit/editor-common/utils';
|
|
4
|
+
import { Selection, TextSelection } from '@atlaskit/editor-prosemirror/state';
|
|
5
|
+
import { Decoration } from '@atlaskit/editor-prosemirror/view';
|
|
6
|
+
export { colors };
|
|
7
|
+
export const findPointers = (id, decorations) => decorations.find().reduce((arr, deco) => deco.spec.pointer.sessionId === id ? arr.concat(deco) : arr, []);
|
|
8
|
+
function style(options) {
|
|
9
|
+
const color = options && options.color || 'black';
|
|
10
|
+
return `border-left: 1px solid ${color}; border-right: 1px solid ${color}; margin-right: -2px;`;
|
|
11
|
+
}
|
|
12
|
+
export function getAvatarColor(str) {
|
|
13
|
+
let hash = 0;
|
|
14
|
+
for (let i = 0; i < str.length; i++) {
|
|
15
|
+
/* eslint-disable no-bitwise */
|
|
16
|
+
hash = (hash << 5) - hash + str.charCodeAt(i);
|
|
17
|
+
hash = hash & hash;
|
|
18
|
+
/* eslint-enable no-bitwise */
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const index = Math.abs(hash) % colors.length;
|
|
22
|
+
return {
|
|
23
|
+
index,
|
|
24
|
+
color: colors[index]
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export const createTelepointers = (from, to, sessionId, isSelection, initial) => {
|
|
28
|
+
let decorations = [];
|
|
29
|
+
const avatarColor = getAvatarColor(sessionId);
|
|
30
|
+
const color = avatarColor.index.toString();
|
|
31
|
+
if (isSelection) {
|
|
32
|
+
const className = `telepointer color-${color} telepointer-selection`;
|
|
33
|
+
decorations.push(Decoration.inline(from, to, {
|
|
34
|
+
class: className,
|
|
35
|
+
'data-initial': initial
|
|
36
|
+
}, {
|
|
37
|
+
pointer: {
|
|
38
|
+
sessionId
|
|
39
|
+
}
|
|
40
|
+
}));
|
|
41
|
+
}
|
|
42
|
+
const spaceJoinerBefore = document.createElement('span');
|
|
43
|
+
spaceJoinerBefore.textContent = ZERO_WIDTH_JOINER;
|
|
44
|
+
const spaceJoinerAfter = document.createElement('span');
|
|
45
|
+
spaceJoinerAfter.textContent = ZERO_WIDTH_JOINER;
|
|
46
|
+
const cursor = document.createElement('span');
|
|
47
|
+
cursor.textContent = ZERO_WIDTH_JOINER;
|
|
48
|
+
cursor.className = `telepointer color-${color} telepointer-selection-badge`;
|
|
49
|
+
cursor.style.cssText = `${style({
|
|
50
|
+
color: avatarColor.color.solid
|
|
51
|
+
})};`;
|
|
52
|
+
cursor.setAttribute('data-initial', initial);
|
|
53
|
+
return decorations.concat(Decoration.widget(to, spaceJoinerAfter, {
|
|
54
|
+
pointer: {
|
|
55
|
+
sessionId
|
|
56
|
+
},
|
|
57
|
+
key: `telepointer-${sessionId}-zero`
|
|
58
|
+
})).concat(Decoration.widget(to, cursor, {
|
|
59
|
+
pointer: {
|
|
60
|
+
sessionId
|
|
61
|
+
},
|
|
62
|
+
key: `telepointer-${sessionId}`
|
|
63
|
+
})).concat(Decoration.widget(to, spaceJoinerBefore, {
|
|
64
|
+
pointer: {
|
|
65
|
+
sessionId
|
|
66
|
+
},
|
|
67
|
+
key: `telepointer-${sessionId}-zero`
|
|
68
|
+
}));
|
|
69
|
+
};
|
|
70
|
+
export const replaceDocument = (doc, state, version, options, reserveCursor) => {
|
|
71
|
+
const {
|
|
72
|
+
schema,
|
|
73
|
+
tr
|
|
74
|
+
} = state;
|
|
75
|
+
let content = (doc.content || []).map(child => schema.nodeFromJSON(child));
|
|
76
|
+
let hasContent = !!content.length;
|
|
77
|
+
if (hasContent) {
|
|
78
|
+
tr.setMeta('addToHistory', false);
|
|
79
|
+
tr.replaceWith(0, state.doc.nodeSize - 2, content);
|
|
80
|
+
const selection = state.selection;
|
|
81
|
+
if (reserveCursor) {
|
|
82
|
+
// If the cursor is still in the range of the new document,
|
|
83
|
+
// keep where it was.
|
|
84
|
+
if (selection.to < tr.doc.content.size - 2) {
|
|
85
|
+
const $from = tr.doc.resolve(selection.from);
|
|
86
|
+
const $to = tr.doc.resolve(selection.to);
|
|
87
|
+
const newselection = new TextSelection($from, $to);
|
|
88
|
+
tr.setSelection(newselection);
|
|
89
|
+
}
|
|
90
|
+
} else {
|
|
91
|
+
tr.setSelection(Selection.atStart(tr.doc));
|
|
92
|
+
}
|
|
93
|
+
tr.setMeta('replaceDocument', true);
|
|
94
|
+
if (typeof version !== undefined && options && options.useNativePlugin) {
|
|
95
|
+
const collabState = {
|
|
96
|
+
version,
|
|
97
|
+
unconfirmed: []
|
|
98
|
+
};
|
|
99
|
+
tr.setMeta('collab$', collabState);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return tr;
|
|
103
|
+
};
|
|
104
|
+
export const scrollToCollabCursor = (editorView, participants, sessionId, index, editorAnalyticsAPI) => {
|
|
105
|
+
const selectedUser = participants[index];
|
|
106
|
+
if (selectedUser && selectedUser.cursorPos !== undefined && selectedUser.sessionId !== sessionId) {
|
|
107
|
+
const {
|
|
108
|
+
state
|
|
109
|
+
} = editorView;
|
|
110
|
+
let tr = state.tr;
|
|
111
|
+
const analyticsPayload = {
|
|
112
|
+
action: ACTION.MATCHED,
|
|
113
|
+
actionSubject: ACTION_SUBJECT.SELECTION,
|
|
114
|
+
eventType: EVENT_TYPE.TRACK
|
|
115
|
+
};
|
|
116
|
+
tr.setSelection(Selection.near(tr.doc.resolve(selectedUser.cursorPos)));
|
|
117
|
+
editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 ? void 0 : editorAnalyticsAPI.attachAnalyticsEvent(analyticsPayload)(tr);
|
|
118
|
+
tr.scrollIntoView();
|
|
119
|
+
editorView.dispatch(tr);
|
|
120
|
+
if (!editorView.hasFocus()) {
|
|
121
|
+
editorView.focus();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
export const getPositionOfTelepointer = (sessionId, decorationSet) => {
|
|
126
|
+
let scrollPosition;
|
|
127
|
+
decorationSet.find().forEach(deco => {
|
|
128
|
+
if (deco.type.spec.pointer.sessionId === sessionId) {
|
|
129
|
+
scrollPosition = deco.from;
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
return scrollPosition;
|
|
133
|
+
};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
|
|
2
|
+
import { receiveTransaction } from 'prosemirror-collab';
|
|
3
|
+
import { AllSelection, NodeSelection } from '@atlaskit/editor-prosemirror/state';
|
|
4
|
+
import { Step } from '@atlaskit/editor-prosemirror/transform';
|
|
5
|
+
import { replaceDocument } from './utils';
|
|
6
|
+
export var handleInit = function handleInit(initData, view, options) {
|
|
7
|
+
var doc = initData.doc,
|
|
8
|
+
json = initData.json,
|
|
9
|
+
version = initData.version,
|
|
10
|
+
reserveCursor = initData.reserveCursor;
|
|
11
|
+
if (doc) {
|
|
12
|
+
var state = view.state;
|
|
13
|
+
var tr = replaceDocument(doc, state, version, options, reserveCursor);
|
|
14
|
+
tr.setMeta('isRemote', true);
|
|
15
|
+
view.dispatch(tr);
|
|
16
|
+
} else if (json) {
|
|
17
|
+
applyRemoteSteps(json, view);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
export var handleConnection = function handleConnection(connectionData, view) {
|
|
21
|
+
var tr = view.state.tr;
|
|
22
|
+
view.dispatch(tr.setMeta('sessionId', connectionData));
|
|
23
|
+
};
|
|
24
|
+
export var handlePresence = function handlePresence(presenceData, view) {
|
|
25
|
+
var tr = view.state.tr;
|
|
26
|
+
view.dispatch(tr.setMeta('presence', presenceData));
|
|
27
|
+
};
|
|
28
|
+
export var applyRemoteData = function applyRemoteData(remoteData, view, options) {
|
|
29
|
+
var json = remoteData.json,
|
|
30
|
+
_remoteData$userIds = remoteData.userIds,
|
|
31
|
+
userIds = _remoteData$userIds === void 0 ? [] : _remoteData$userIds;
|
|
32
|
+
if (json) {
|
|
33
|
+
applyRemoteSteps(json, view, userIds, options);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
export var applyRemoteSteps = function applyRemoteSteps(json, view, userIds, options) {
|
|
37
|
+
if (!json || !json.length) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
var state = view.state,
|
|
41
|
+
schema = view.state.schema;
|
|
42
|
+
var steps = json.map(function (step) {
|
|
43
|
+
return Step.fromJSON(schema, step);
|
|
44
|
+
});
|
|
45
|
+
var tr;
|
|
46
|
+
if (options && options.useNativePlugin && userIds) {
|
|
47
|
+
tr = receiveTransaction(state, steps, userIds);
|
|
48
|
+
} else {
|
|
49
|
+
tr = state.tr;
|
|
50
|
+
steps.forEach(function (step) {
|
|
51
|
+
return tr.step(step);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
if (tr) {
|
|
55
|
+
tr.setMeta('addToHistory', false);
|
|
56
|
+
tr.setMeta('isRemote', true);
|
|
57
|
+
var _state$selection = state.selection,
|
|
58
|
+
from = _state$selection.from,
|
|
59
|
+
to = _state$selection.to;
|
|
60
|
+
var _json = _slicedToArray(json, 1),
|
|
61
|
+
firstStep = _json[0];
|
|
62
|
+
|
|
63
|
+
/*
|
|
64
|
+
* Persist marks across transactions. Fixes an issue where
|
|
65
|
+
* marks are lost if remote transactions are dispatched
|
|
66
|
+
* between a user creating the mark and typing.
|
|
67
|
+
*/
|
|
68
|
+
if (state.tr.storedMarks) {
|
|
69
|
+
tr.setStoredMarks(state.tr.storedMarks);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* If the cursor is a the same position as the first step in
|
|
74
|
+
* the remote data, we need to manually set it back again
|
|
75
|
+
* in order to prevent the cursor from moving.
|
|
76
|
+
*/
|
|
77
|
+
if (from === firstStep.from && to === firstStep.to) {
|
|
78
|
+
tr.setSelection(state.selection.map(tr.doc, tr.mapping));
|
|
79
|
+
}
|
|
80
|
+
view.dispatch(tr);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
export var handleTelePointer = function handleTelePointer(telepointerData, view) {
|
|
84
|
+
var tr = view.state.tr;
|
|
85
|
+
view.dispatch(tr.setMeta('telepointer', telepointerData));
|
|
86
|
+
};
|
|
87
|
+
function isAllSelection(selection) {
|
|
88
|
+
return selection instanceof AllSelection;
|
|
89
|
+
}
|
|
90
|
+
function isNodeSelection(selection) {
|
|
91
|
+
return selection instanceof NodeSelection;
|
|
92
|
+
}
|
|
93
|
+
export var getSendableSelection = function getSendableSelection(selection) {
|
|
94
|
+
/**
|
|
95
|
+
* <kbd>CMD + A</kbd> triggers a AllSelection
|
|
96
|
+
* <kbd>escape</kbd> triggers a NodeSelection
|
|
97
|
+
*/
|
|
98
|
+
return {
|
|
99
|
+
type: 'textSelection',
|
|
100
|
+
anchor: selection.anchor,
|
|
101
|
+
head: isAllSelection(selection) || isNodeSelection(selection) ? selection.head - 1 : selection.head
|
|
102
|
+
};
|
|
103
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { ACTION, ACTION_SUBJECT, EVENT_TYPE } from '@atlaskit/editor-common/analytics';
|
|
2
|
+
import { getDocStructure } from '@atlaskit/editor-common/core-utils';
|
|
3
|
+
import { sniffUserBrowserExtensions } from '@atlaskit/editor-common/utils';
|
|
4
|
+
export var addSynchronyErrorAnalytics = function addSynchronyErrorAnalytics(state, tr, featureFlags, editorAnalyticsApi) {
|
|
5
|
+
return function (error) {
|
|
6
|
+
var browserExtensions = sniffUserBrowserExtensions({
|
|
7
|
+
extensions: ['grammarly']
|
|
8
|
+
});
|
|
9
|
+
var payload = {
|
|
10
|
+
action: ACTION.SYNCHRONY_ERROR,
|
|
11
|
+
actionSubject: ACTION_SUBJECT.EDITOR,
|
|
12
|
+
eventType: EVENT_TYPE.OPERATIONAL,
|
|
13
|
+
attributes: {
|
|
14
|
+
error: error,
|
|
15
|
+
browserExtensions: browserExtensions
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
if (featureFlags.synchronyErrorDocStructure) {
|
|
19
|
+
payload.attributes.docStructure = getDocStructure(state.doc, {
|
|
20
|
+
compact: true
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
editorAnalyticsApi === null || editorAnalyticsApi === void 0 || editorAnalyticsApi.attachAnalyticsEvent(payload)(tr);
|
|
24
|
+
return tr;
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
export var addSynchronyEntityAnalytics = function addSynchronyEntityAnalytics(state, tr) {
|
|
28
|
+
return function (type, editorAnalyticsApi) {
|
|
29
|
+
editorAnalyticsApi === null || editorAnalyticsApi === void 0 || editorAnalyticsApi.attachAnalyticsEvent({
|
|
30
|
+
action: type === 'error' ? ACTION.SYNCHRONY_ENTITY_ERROR : ACTION.SYNCHRONY_DISCONNECTED,
|
|
31
|
+
actionSubject: ACTION_SUBJECT.EDITOR,
|
|
32
|
+
eventType: EVENT_TYPE.OPERATIONAL,
|
|
33
|
+
attributes: {
|
|
34
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/NavigatorOnLine/onLine
|
|
35
|
+
onLine: navigator.onLine,
|
|
36
|
+
visibilityState: document.visibilityState
|
|
37
|
+
}
|
|
38
|
+
})(tr);
|
|
39
|
+
return tr;
|
|
40
|
+
};
|
|
41
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { applyRemoteData, handleConnection, handleInit, handlePresence, handleTelePointer } from '../actions';
|
|
2
|
+
import { addSynchronyEntityAnalytics, addSynchronyErrorAnalytics } from '../analytics';
|
|
3
|
+
var effect = function effect(fn, eq) {
|
|
4
|
+
var previousDeps;
|
|
5
|
+
var cleanup;
|
|
6
|
+
return function () {
|
|
7
|
+
for (var _len = arguments.length, currentDeps = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
8
|
+
currentDeps[_key] = arguments[_key];
|
|
9
|
+
}
|
|
10
|
+
if (cleanup && eq(previousDeps, currentDeps)) {
|
|
11
|
+
return cleanup;
|
|
12
|
+
}
|
|
13
|
+
cleanup = fn.apply(void 0, currentDeps);
|
|
14
|
+
previousDeps = currentDeps;
|
|
15
|
+
return cleanup;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
export var subscribe = effect(function (view, provider, options, featureFlags, _providerFactory, editorAnalyticsApi) {
|
|
19
|
+
var entityRef;
|
|
20
|
+
var entityHandlers = {
|
|
21
|
+
disconnectedHandler: function disconnectedHandler() {
|
|
22
|
+
addSynchronyEntityAnalytics(view.state, view.state.tr)('disconnected', editorAnalyticsApi);
|
|
23
|
+
},
|
|
24
|
+
errorHandler: function errorHandler() {
|
|
25
|
+
addSynchronyEntityAnalytics(view.state, view.state.tr)('error', editorAnalyticsApi);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
var unsubscribeSynchronyEntity = function unsubscribeSynchronyEntity() {
|
|
29
|
+
if (entityRef) {
|
|
30
|
+
entityRef.off('disconnected', entityHandlers.disconnectedHandler);
|
|
31
|
+
entityRef.off('error', entityHandlers.errorHandler);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
var handlers = {
|
|
35
|
+
initHandler: function initHandler(data) {
|
|
36
|
+
view.dispatch(view.state.tr.setMeta('collabInitialised', true));
|
|
37
|
+
handleInit(data, view, options);
|
|
38
|
+
},
|
|
39
|
+
connectedHandler: function connectedHandler(data) {
|
|
40
|
+
return handleConnection(data, view);
|
|
41
|
+
},
|
|
42
|
+
dataHandler: function dataHandler(data) {
|
|
43
|
+
return applyRemoteData(data, view, options);
|
|
44
|
+
},
|
|
45
|
+
presenceHandler: function presenceHandler(data) {
|
|
46
|
+
return handlePresence(data, view);
|
|
47
|
+
},
|
|
48
|
+
telepointerHandler: function telepointerHandler(data) {
|
|
49
|
+
return handleTelePointer(data, view);
|
|
50
|
+
},
|
|
51
|
+
localStepsHandler: function localStepsHandler(data) {
|
|
52
|
+
var steps = data.steps;
|
|
53
|
+
var state = view.state;
|
|
54
|
+
var tr = state.tr;
|
|
55
|
+
steps.forEach(function (step) {
|
|
56
|
+
return tr.step(step);
|
|
57
|
+
});
|
|
58
|
+
view.dispatch(tr);
|
|
59
|
+
},
|
|
60
|
+
errorHandler: function errorHandler(error) {
|
|
61
|
+
addSynchronyErrorAnalytics(view.state, view.state.tr, featureFlags, editorAnalyticsApi)(error);
|
|
62
|
+
},
|
|
63
|
+
entityHandler: function entityHandler(_ref) {
|
|
64
|
+
var entity = _ref.entity;
|
|
65
|
+
unsubscribeSynchronyEntity();
|
|
66
|
+
if (options.EXPERIMENTAL_allowInternalErrorAnalytics) {
|
|
67
|
+
entity.on('disconnected', entityHandlers.disconnectedHandler);
|
|
68
|
+
entity.on('error', entityHandlers.errorHandler);
|
|
69
|
+
entityRef = entity;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
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);
|
|
74
|
+
return function () {
|
|
75
|
+
unsubscribeSynchronyEntity();
|
|
76
|
+
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);
|
|
77
|
+
};
|
|
78
|
+
}, function (previousDeps, currentDeps) {
|
|
79
|
+
return currentDeps && currentDeps.every(function (dep, i) {
|
|
80
|
+
return dep === previousDeps[i];
|
|
81
|
+
});
|
|
82
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
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
|
+
var initCollab = function initCollab(collabEditProvider, view) {
|
|
6
|
+
if (collabEditProvider.initialize) {
|
|
7
|
+
collabEditProvider.initialize(function () {
|
|
8
|
+
return view.state;
|
|
9
|
+
}, function (json) {
|
|
10
|
+
return Step.fromJSON(view.state.schema, json);
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
var initNewCollab = function initNewCollab(collabEditProvider, view, onSyncUpError) {
|
|
15
|
+
collabEditProvider.setup({
|
|
16
|
+
getState: function getState() {
|
|
17
|
+
return view.state;
|
|
18
|
+
},
|
|
19
|
+
onSyncUpError: onSyncUpError
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
var initCollabMemo = memoizeOne(initCollab);
|
|
23
|
+
export var initialize = function initialize(_ref) {
|
|
24
|
+
var options = _ref.options,
|
|
25
|
+
providerFactory = _ref.providerFactory,
|
|
26
|
+
view = _ref.view,
|
|
27
|
+
featureFlags = _ref.featureFlags,
|
|
28
|
+
editorAnalyticsApi = _ref.editorAnalyticsApi;
|
|
29
|
+
return function (provider) {
|
|
30
|
+
var cleanup;
|
|
31
|
+
var pluginState = pluginKey.getState(view.state);
|
|
32
|
+
if (pluginState !== null && pluginState !== void 0 && pluginState.isReady && cleanup) {
|
|
33
|
+
cleanup();
|
|
34
|
+
}
|
|
35
|
+
cleanup = subscribe(view, provider, options, featureFlags, providerFactory, editorAnalyticsApi);
|
|
36
|
+
|
|
37
|
+
// Initialize provider
|
|
38
|
+
if (options.useNativePlugin) {
|
|
39
|
+
// ED-13912 For NCS we don't want to use memoizeOne because it causes
|
|
40
|
+
// infinite text while changing page-width
|
|
41
|
+
initNewCollab(provider, view, options.onSyncUpError);
|
|
42
|
+
} else {
|
|
43
|
+
/**
|
|
44
|
+
* We only want to initialise once, if we reload/reconfigure this plugin
|
|
45
|
+
* We dont want to re-init collab, it would break existing sessions
|
|
46
|
+
*/
|
|
47
|
+
initCollabMemo(provider, view);
|
|
48
|
+
}
|
|
49
|
+
return cleanup;
|
|
50
|
+
};
|
|
51
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { getSendableSelection } from '../actions';
|
|
2
|
+
import { pluginKey } from '../plugin-key';
|
|
3
|
+
export var sendTransaction = function sendTransaction(_ref) {
|
|
4
|
+
var originalTransaction = _ref.originalTransaction,
|
|
5
|
+
transactions = _ref.transactions,
|
|
6
|
+
oldEditorState = _ref.oldEditorState,
|
|
7
|
+
newEditorState = _ref.newEditorState,
|
|
8
|
+
useNativePlugin = _ref.useNativePlugin;
|
|
9
|
+
return function (provider) {
|
|
10
|
+
var docChangedTransaction = transactions.find(function (tr) {
|
|
11
|
+
return tr.docChanged;
|
|
12
|
+
});
|
|
13
|
+
var currentPluginState = pluginKey.getState(newEditorState);
|
|
14
|
+
if (!(currentPluginState !== null && currentPluginState !== void 0 && currentPluginState.isReady)) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
var shouldSendStepForSynchronyCollabProvider = !originalTransaction.getMeta('isRemote') &&
|
|
18
|
+
// TODO: ED-8995
|
|
19
|
+
// We need to do this check to reduce the number of race conditions when working with tables.
|
|
20
|
+
// This metadata is coming from the scaleTable command in table-resizing plugin
|
|
21
|
+
!originalTransaction.getMeta('scaleTable') && docChangedTransaction;
|
|
22
|
+
if (useNativePlugin || shouldSendStepForSynchronyCollabProvider) {
|
|
23
|
+
provider.send(docChangedTransaction, oldEditorState, newEditorState);
|
|
24
|
+
}
|
|
25
|
+
var prevPluginState = pluginKey.getState(oldEditorState);
|
|
26
|
+
var _ref2 = prevPluginState || {},
|
|
27
|
+
prevActiveParticipants = _ref2.activeParticipants;
|
|
28
|
+
var activeParticipants = currentPluginState.activeParticipants,
|
|
29
|
+
sessionId = currentPluginState.sessionId;
|
|
30
|
+
var selectionChanged = !oldEditorState.selection.eq(newEditorState.selection);
|
|
31
|
+
var participantsChanged = prevActiveParticipants && !prevActiveParticipants.eq(activeParticipants);
|
|
32
|
+
if (sessionId && selectionChanged && !docChangedTransaction || sessionId && participantsChanged) {
|
|
33
|
+
var selection = getSendableSelection(newEditorState.selection);
|
|
34
|
+
var message = {
|
|
35
|
+
type: 'telepointer',
|
|
36
|
+
selection: selection,
|
|
37
|
+
sessionId: sessionId
|
|
38
|
+
};
|
|
39
|
+
provider.sendMessage(message);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
|
|
2
|
+
import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
|
|
3
|
+
import _regeneratorRuntime from "@babel/runtime/regenerator";
|
|
4
|
+
import { collab } from 'prosemirror-collab';
|
|
5
|
+
import { addSynchronyErrorAnalytics } from './analytics';
|
|
6
|
+
import { sendTransaction } from './events/send-transaction';
|
|
7
|
+
import { nativeCollabProviderPlugin } from './native-collab-provider-plugin';
|
|
8
|
+
import { createPlugin, pluginKey } from './plugin';
|
|
9
|
+
export { pluginKey };
|
|
10
|
+
import { getAvatarColor } from './utils';
|
|
11
|
+
var providerBuilder = function providerBuilder(collabEditProviderPromise) {
|
|
12
|
+
return /*#__PURE__*/function () {
|
|
13
|
+
var _ref = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(codeToExecute, onError) {
|
|
14
|
+
var provider;
|
|
15
|
+
return _regeneratorRuntime.wrap(function _callee$(_context) {
|
|
16
|
+
while (1) switch (_context.prev = _context.next) {
|
|
17
|
+
case 0:
|
|
18
|
+
_context.prev = 0;
|
|
19
|
+
_context.next = 3;
|
|
20
|
+
return collabEditProviderPromise;
|
|
21
|
+
case 3:
|
|
22
|
+
provider = _context.sent;
|
|
23
|
+
if (!provider) {
|
|
24
|
+
_context.next = 6;
|
|
25
|
+
break;
|
|
26
|
+
}
|
|
27
|
+
return _context.abrupt("return", codeToExecute(provider));
|
|
28
|
+
case 6:
|
|
29
|
+
_context.next = 11;
|
|
30
|
+
break;
|
|
31
|
+
case 8:
|
|
32
|
+
_context.prev = 8;
|
|
33
|
+
_context.t0 = _context["catch"](0);
|
|
34
|
+
if (onError) {
|
|
35
|
+
onError(_context.t0);
|
|
36
|
+
} else {
|
|
37
|
+
// eslint-disable-next-line no-console
|
|
38
|
+
console.error(_context.t0);
|
|
39
|
+
}
|
|
40
|
+
case 11:
|
|
41
|
+
case "end":
|
|
42
|
+
return _context.stop();
|
|
43
|
+
}
|
|
44
|
+
}, _callee, null, [[0, 8]]);
|
|
45
|
+
}));
|
|
46
|
+
return function (_x, _x2) {
|
|
47
|
+
return _ref.apply(this, arguments);
|
|
48
|
+
};
|
|
49
|
+
}();
|
|
50
|
+
};
|
|
51
|
+
export var collabEditPlugin = function collabEditPlugin(_ref2) {
|
|
52
|
+
var _api$featureFlags;
|
|
53
|
+
var options = _ref2.config,
|
|
54
|
+
api = _ref2.api;
|
|
55
|
+
var featureFlags = (api === null || api === void 0 || (_api$featureFlags = api.featureFlags) === null || _api$featureFlags === void 0 ? void 0 : _api$featureFlags.sharedState.currentState()) || {};
|
|
56
|
+
var providerResolver = function providerResolver() {};
|
|
57
|
+
var collabEditProviderPromise = new Promise(function (_providerResolver) {
|
|
58
|
+
providerResolver = _providerResolver;
|
|
59
|
+
});
|
|
60
|
+
var executeProviderCode = providerBuilder(collabEditProviderPromise);
|
|
61
|
+
return {
|
|
62
|
+
name: 'collabEdit',
|
|
63
|
+
getSharedState: function getSharedState(state) {
|
|
64
|
+
if (!state) {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
var collabPluginState = pluginKey.getState(state);
|
|
68
|
+
return {
|
|
69
|
+
activeParticipants: collabPluginState === null || collabPluginState === void 0 ? void 0 : collabPluginState.activeParticipants,
|
|
70
|
+
sessionId: collabPluginState === null || collabPluginState === void 0 ? void 0 : collabPluginState.sessionId
|
|
71
|
+
};
|
|
72
|
+
},
|
|
73
|
+
actions: {
|
|
74
|
+
getAvatarColor: getAvatarColor
|
|
75
|
+
},
|
|
76
|
+
pmPlugins: function pmPlugins() {
|
|
77
|
+
var _ref3 = options || {},
|
|
78
|
+
_ref3$useNativePlugin = _ref3.useNativePlugin,
|
|
79
|
+
useNativePlugin = _ref3$useNativePlugin === void 0 ? false : _ref3$useNativePlugin,
|
|
80
|
+
userId = _ref3.userId;
|
|
81
|
+
return [].concat(_toConsumableArray(useNativePlugin ? [{
|
|
82
|
+
name: 'pmCollab',
|
|
83
|
+
plugin: function plugin() {
|
|
84
|
+
return collab({
|
|
85
|
+
clientID: userId
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}, {
|
|
89
|
+
name: 'nativeCollabProviderPlugin',
|
|
90
|
+
plugin: function plugin() {
|
|
91
|
+
return nativeCollabProviderPlugin({
|
|
92
|
+
providerPromise: collabEditProviderPromise
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}] : []), [{
|
|
96
|
+
name: 'collab',
|
|
97
|
+
plugin: function plugin(_ref4) {
|
|
98
|
+
var dispatch = _ref4.dispatch,
|
|
99
|
+
providerFactory = _ref4.providerFactory;
|
|
100
|
+
return createPlugin(dispatch, providerFactory, providerResolver, executeProviderCode, options, featureFlags, api);
|
|
101
|
+
}
|
|
102
|
+
}]);
|
|
103
|
+
},
|
|
104
|
+
onEditorViewStateUpdated: function onEditorViewStateUpdated(props) {
|
|
105
|
+
var _api$analytics, _options$useNativePlu;
|
|
106
|
+
var addErrorAnalytics = addSynchronyErrorAnalytics(props.newEditorState, props.newEditorState.tr, featureFlags, api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions);
|
|
107
|
+
executeProviderCode(sendTransaction({
|
|
108
|
+
originalTransaction: props.originalTransaction,
|
|
109
|
+
transactions: props.transactions,
|
|
110
|
+
oldEditorState: props.oldEditorState,
|
|
111
|
+
newEditorState: props.newEditorState,
|
|
112
|
+
useNativePlugin: (_options$useNativePlu = options === null || options === void 0 ? void 0 : options.useNativePlugin) !== null && _options$useNativePlu !== void 0 ? _options$useNativePlu : false
|
|
113
|
+
}), addErrorAnalytics);
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
|
|
2
|
+
import { PluginKey } from '@atlaskit/editor-prosemirror/state';
|
|
3
|
+
var nativeCollabProviderPluginKey = new PluginKey('nativeCollabProviderPlugin');
|
|
4
|
+
export var nativeCollabProviderPlugin = function nativeCollabProviderPlugin(_ref) {
|
|
5
|
+
var providerPromise = _ref.providerPromise;
|
|
6
|
+
return new SafePlugin({
|
|
7
|
+
key: nativeCollabProviderPluginKey,
|
|
8
|
+
state: {
|
|
9
|
+
init: function init() {
|
|
10
|
+
return null;
|
|
11
|
+
},
|
|
12
|
+
apply: function apply(tr, currentPluginState) {
|
|
13
|
+
var provider = tr.getMeta(nativeCollabProviderPluginKey);
|
|
14
|
+
return provider ? provider : currentPluginState;
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
view: function view(editorView) {
|
|
18
|
+
providerPromise.then(function (provider) {
|
|
19
|
+
var dispatch = editorView.dispatch,
|
|
20
|
+
state = editorView.state;
|
|
21
|
+
var tr = state.tr;
|
|
22
|
+
tr.setMeta(nativeCollabProviderPluginKey, provider);
|
|
23
|
+
dispatch(tr);
|
|
24
|
+
});
|
|
25
|
+
return {};
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
export var getCollabProvider = function getCollabProvider(editorState) {
|
|
30
|
+
return nativeCollabProviderPluginKey.getState(editorState);
|
|
31
|
+
};
|