@seafile/sdoc-editor 0.1.117 → 0.1.119

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.
@@ -2,6 +2,7 @@ import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray";
2
2
  import { ReactEditor } from '@seafile/slate-react';
3
3
  import { Editor, Transforms, Range } from '@seafile/slate';
4
4
  import slugid from 'slugid';
5
+ import copy from 'copy-to-clipboard';
5
6
  import context from '../../../../context';
6
7
  import { SDOC_LINK, LINK, INSERT_FILE_DISPLAY_TYPE } from '../../constants';
7
8
  import { getNodeType, getSelectedElems } from '../../core';
@@ -121,4 +122,18 @@ export var getNewFileListData = function getNewFileListData(fileListData, indexI
121
122
  };
122
123
  export var getUrl = function getUrl(uuid) {
123
124
  return context.getSdocLocalFileUrl(uuid);
125
+ };
126
+ export var onCopySdocLinkNode = function onCopySdocLinkNode(editor, element) {
127
+ if (editor.selection == null || Range.isExpanded(editor.selection)) return;
128
+ var p = ReactEditor.findPath(editor, element);
129
+ Transforms.select(editor, p);
130
+ var newData = editor.setFragmentData(new DataTransfer());
131
+ copy('copy', {
132
+ onCopy: function onCopy(clipboardData) {
133
+ newData.types.forEach(function (type) {
134
+ var data = newData.getData(type);
135
+ clipboardData.setData(type, data);
136
+ });
137
+ }
138
+ });
124
139
  };
@@ -19,7 +19,7 @@
19
19
  .sdoc-link-menu .sdoc-link-menu-popover {
20
20
  position: absolute;
21
21
  top: 36px;
22
- left: 0px;
22
+ left: -15px;
23
23
  padding: 8px 0;
24
24
  background-color: #fff;
25
25
  border: 1px solid #e5e6e8;
@@ -30,7 +30,7 @@
30
30
  align-items: flex-start;
31
31
  z-index: 101;
32
32
  white-space: nowrap;
33
- width: 180px;
33
+ width: 138px;
34
34
  }
35
35
 
36
36
  .sdoc-link-menu .sdoc-link-menu-popover .sdoc-link-menu-item {
@@ -40,9 +40,9 @@
40
40
  user-select: none;
41
41
  display: flex;
42
42
  align-items: center;
43
- font-size: 12px;
43
+ font-size: 14px;
44
44
  color: #212529;
45
- padding-left: 24px;
45
+ padding-left: 5px;
46
46
  }
47
47
 
48
48
  .sdoc-link-menu .sdoc-link-menu-popover .sdoc-link-menu-item > span :first-child {
@@ -1,6 +1,6 @@
1
1
  import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
2
2
  import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray";
3
- import React, { useCallback, useState } from 'react';
3
+ import React, { useCallback, useEffect, useState } from 'react';
4
4
  import { withTranslation } from 'react-i18next';
5
5
  import { SDOC_LINK, MENUS_CONFIG_MAP } from '../../../constants';
6
6
  import { MenuItem } from '../../../commons';
@@ -30,13 +30,13 @@ var SdocLinkMenu = function SdocLinkMenu(_ref) {
30
30
  }, []);
31
31
  var onInsertFileDialogToggle = useCallback(function () {
32
32
  setShowInsertPopover(true);
33
- // setShowInsertDialog(!showInsertDialog);
34
33
  }, []);
35
- var onMouseDown = useCallback(function () {
34
+ var onMouseDown = useCallback(function (e) {
35
+ e.stopPropagation();
36
36
  onInsertFileDialogToggle();
37
37
  // eslint-disable-next-line react-hooks/exhaustive-deps
38
38
  }, []);
39
- var onInsertSdocFile = useCallback(function () {
39
+ var onInsertSdocFile = useCallback(function (e) {
40
40
  setShowInsertPopover(false);
41
41
  setShowInsertDialog(true);
42
42
  // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -44,6 +44,20 @@ var SdocLinkMenu = function SdocLinkMenu(_ref) {
44
44
  var onDialogToggle = useCallback(function (value) {
45
45
  setShowInsertDialog(value);
46
46
  }, []);
47
+ var onHidenInsertPopover = useCallback(function () {
48
+ setShowInsertPopover(false);
49
+ }, []);
50
+ useEffect(function () {
51
+ if (showInsertPopover) {
52
+ window.addEventListener('click', onHidenInsertPopover);
53
+ } else {
54
+ window.removeEventListener('click', onHidenInsertPopover);
55
+ }
56
+ return function () {
57
+ window.removeEventListener('click', onHidenInsertPopover);
58
+ };
59
+ // eslint-disable-next-line react-hooks/exhaustive-deps
60
+ }, [showInsertPopover]);
47
61
  var menuConfig = MENUS_CONFIG_MAP[SDOC_LINK];
48
62
  var props = _objectSpread(_objectSpread({
49
63
  isRichEditor: isRichEditor,
@@ -2,37 +2,37 @@ import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray";
2
2
  import React, { useCallback, useState } from 'react';
3
3
  import { Transforms } from '@seafile/slate';
4
4
  import { ReactEditor } from '@seafile/slate-react';
5
- import copy from 'copy-to-clipboard';
6
5
  import { withTranslation } from 'react-i18next';
7
6
  import { ElementPopover } from '../../../commons';
8
7
  import toaster from '../../../../../components/toast';
9
- import { getUrl } from '../helpers';
8
+ import { getUrl, onCopySdocLinkNode } from '../helpers';
10
9
  import './sdoc-link-hover-menu.css';
11
10
  var SdocLinkHoverMenu = function SdocLinkHoverMenu(_ref) {
12
11
  var editor = _ref.editor,
13
12
  menuPosition = _ref.menuPosition,
14
13
  element = _ref.element,
15
14
  onUnwrapFileLinkNode = _ref.onUnwrapFileLinkNode,
15
+ onHideInsertHoverMenu = _ref.onHideInsertHoverMenu,
16
16
  t = _ref.t;
17
17
  var _useState = useState(false),
18
18
  _useState2 = _slicedToArray(_useState, 2),
19
19
  isShowDisplayStylePopover = _useState2[0],
20
20
  setIsShowDisplayStylePopover = _useState2[1];
21
- var onCopy = useCallback(function () {
22
- copy(String(getUrl(element.doc_uuid)), {
23
- format: 'text/plain'
24
- });
21
+ var onCopy = useCallback(function (e) {
22
+ e.stopPropagation();
23
+ onCopySdocLinkNode(editor, element);
25
24
  toaster.success(t('Copied'), {
26
25
  hasCloseButton: false,
27
26
  duration: 2
28
27
  });
28
+ onHideInsertHoverMenu();
29
29
  // eslint-disable-next-line react-hooks/exhaustive-deps
30
30
  }, []);
31
31
  var onShowProver = useCallback(function (e) {
32
- e.stopPropagation();
33
32
  setIsShowDisplayStylePopover(true);
34
33
  }, []);
35
- var onSelect = useCallback(function (value) {
34
+ var onSelect = useCallback(function (e, value) {
35
+ e.stopPropagation();
36
36
  var path = ReactEditor.findPath(editor, element);
37
37
  if (path) {
38
38
  Transforms.setNodes(editor, {
@@ -41,6 +41,7 @@ var SdocLinkHoverMenu = function SdocLinkHoverMenu(_ref) {
41
41
  at: path
42
42
  });
43
43
  }
44
+ onHideInsertHoverMenu();
44
45
  // eslint-disable-next-line react-hooks/exhaustive-deps
45
46
  }, []);
46
47
  return /*#__PURE__*/React.createElement(ElementPopover, null, /*#__PURE__*/React.createElement("div", {
@@ -84,8 +85,8 @@ var SdocLinkHoverMenu = function SdocLinkHoverMenu(_ref) {
84
85
  className: "sdoc-file-display-style-popover"
85
86
  }, /*#__PURE__*/React.createElement("div", {
86
87
  className: "sdoc-file-display-style-item ".concat(element.display_type === 'text_link' ? 'active' : 'inactive'),
87
- onClick: function onClick() {
88
- onSelect('text_link');
88
+ onClick: function onClick(e) {
89
+ onSelect(e, 'text_link');
89
90
  }
90
91
  }, /*#__PURE__*/React.createElement("span", null, /*#__PURE__*/React.createElement("i", {
91
92
  className: "sdocfont sdoc-text-link"
@@ -93,8 +94,8 @@ var SdocLinkHoverMenu = function SdocLinkHoverMenu(_ref) {
93
94
  className: "sdocfont sdoc-check-mark"
94
95
  }))), /*#__PURE__*/React.createElement("div", {
95
96
  className: "sdoc-file-display-style-item ".concat(element.display_type === 'icon_link' ? 'active' : 'inactive'),
96
- onClick: function onClick() {
97
- onSelect('icon_link');
97
+ onClick: function onClick(e) {
98
+ onSelect(e, 'icon_link');
98
99
  }
99
100
  }, /*#__PURE__*/React.createElement("span", null, /*#__PURE__*/React.createElement("i", {
100
101
  className: "sdocfont sdoc-inline-link"
@@ -102,8 +103,8 @@ var SdocLinkHoverMenu = function SdocLinkHoverMenu(_ref) {
102
103
  className: "sdocfont sdoc-check-mark"
103
104
  }))), /*#__PURE__*/React.createElement("div", {
104
105
  className: "sdoc-file-display-style-item ".concat(element.display_type === 'card_link' ? 'active' : 'inactive'),
105
- onClick: function onClick() {
106
- onSelect('card_link');
106
+ onClick: function onClick(e) {
107
+ onSelect(e, 'card_link');
107
108
  }
108
109
  }, /*#__PURE__*/React.createElement("span", null, /*#__PURE__*/React.createElement("i", {
109
110
  className: "sdocfont sdoc-card-link"
@@ -69,6 +69,7 @@ var SdocFileLink = function SdocFileLink(_ref) {
69
69
  // eslint-disable-next-line react-hooks/exhaustive-deps
70
70
  }, [isShowInsertHoverMenu]);
71
71
  var onClickFile = useCallback(function (e) {
72
+ e.stopPropagation();
72
73
  setPosition(e.currentTarget);
73
74
  setIsShowInsertHoverMenu(true);
74
75
  setTimeout(function () {
@@ -76,7 +77,7 @@ var SdocFileLink = function SdocFileLink(_ref) {
76
77
  }, 0);
77
78
  // eslint-disable-next-line react-hooks/exhaustive-deps
78
79
  }, []);
79
- var onHideInsertHoverMenu = useCallback(function () {
80
+ var onHideInsertHoverMenu = useCallback(function (e) {
80
81
  setIsShowInsertHoverMenu(false);
81
82
  unregisterEventHandle();
82
83
  // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -107,7 +108,8 @@ var SdocFileLink = function SdocFileLink(_ref) {
107
108
  editor: editor,
108
109
  menuPosition: menuPosition,
109
110
  element: element,
110
- onUnwrapFileLinkNode: onUnwrapFileLinkNode
111
+ onUnwrapFileLinkNode: onUnwrapFileLinkNode,
112
+ onHideInsertHoverMenu: onHideInsertHoverMenu
111
113
  }));
112
114
  };
113
115
  var renderSdocLink = function renderSdocLink(props, editor) {
@@ -25,11 +25,8 @@ var SocketClient = /*#__PURE__*/_createClass(function SocketClient(config) {
25
25
  // sync operations or document
26
26
  if (_this.isReconnect) {
27
27
  _this.isReconnect = false;
28
- var serverVersion = result.version;
29
- var clientVersion = socketManager.getDocumentVersion();
30
- if (serverVersion !== clientVersion) {
31
- _this.syncDocumentBySocket();
32
- }
28
+ // The reconnect of socketManager needs to be triggered after entering the room again
29
+ socketManager.onReconnect(result);
33
30
  }
34
31
  socketManager.dispatchConnectState('onConnected', result);
35
32
  return;
@@ -92,14 +89,14 @@ var SocketClient = /*#__PURE__*/_createClass(function SocketClient(config) {
92
89
  * receive remote broadcast operations
93
90
  * @param {*} params {operations, version}
94
91
  */
95
- this.receiveOperations = function (params) {
92
+ this.onReceiveRemoteOperations = function (params) {
96
93
  debug('********* receive operations *********');
97
94
  debug('%O', params);
98
95
  debug('**************************************');
99
96
  var socketManager = SocketManager.getInstance();
100
- socketManager.receiveOperations(params);
97
+ socketManager.onReceiveRemoteOperations(params);
101
98
  };
102
- this.syncDocumentBySocket = function () {
99
+ this.getRecentOperations = function () {
103
100
  var docUuid = _this.config.docUuid;
104
101
  var socketManager = SocketManager.getInstance();
105
102
  var clientVersion = socketManager.getDocumentVersion();
@@ -108,7 +105,7 @@ var SocketClient = /*#__PURE__*/_createClass(function SocketClient(config) {
108
105
  version: clientVersion
109
106
  }, function (result) {
110
107
  if (result.success) {
111
- socketManager.syncDocumentBySocket(result);
108
+ socketManager.onGetRecentOperations(result);
112
109
  }
113
110
  });
114
111
  };
@@ -139,7 +136,7 @@ var SocketClient = /*#__PURE__*/_createClass(function SocketClient(config) {
139
136
  this.socket.on('connect_error', this.onConnectError);
140
137
  this.socket.on('join-room', this.onJoinRoom);
141
138
  this.socket.on('leave-room', this.onLeaveRoom);
142
- this.socket.on('update-document', this.receiveOperations);
139
+ this.socket.on('update-document', this.onReceiveRemoteOperations);
143
140
  this.socket.on('update-cursor', this.receiveCursorLocation);
144
141
  this.socket.io.on('reconnect', this.onReconnect);
145
142
  this.socket.io.on('reconnect_attempt', this.onReconnectAttempt);
@@ -1,3 +1,4 @@
1
+ import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray";
1
2
  import _createClass from "@babel/runtime/helpers/esm/createClass";
2
3
  import _classCallCheck from "@babel/runtime/helpers/esm/classCallCheck";
3
4
  import EventBus from '../utils/event-bus';
@@ -5,31 +6,43 @@ import { syncRemoteOperations, reExecRevertOperationList, revertOperationList, s
5
6
  import SocketClient from './socket-client';
6
7
  import debug from '../utils/debug';
7
8
  import { deleteCursor } from '../cursor/helper';
9
+
10
+ // idle --> sending --> conflict --> idle
11
+ // --> conflict --> idle
12
+ // --> disconnect --> conflict --> idle
13
+ // --> idle
14
+ var STATE = {
15
+ IDLE: 'idle',
16
+ SENDING: 'sending',
17
+ CONFLICT: 'conflict',
18
+ DISCONNECT: 'disconnect',
19
+ NEED_RELOAD: 'need_reload'
20
+ };
8
21
  var SocketManager = /*#__PURE__*/_createClass(function SocketManager(editor, document, config) {
9
22
  var _this = this;
10
23
  _classCallCheck(this, SocketManager);
11
- this.addOperations = function (operations) {
24
+ this.getDocumentVersion = function () {
25
+ var version = _this.document.version;
26
+ return version;
27
+ };
28
+ this.onReceiveLocalOperations = function (operations) {
12
29
  _this.pendingOperationList.push(operations);
13
30
  if (_this.pendingOperationList.length > 5) {
14
31
  _this.dispatchConnectState('pending_operations_exceed_limit');
15
32
  }
16
33
  _this.sendOperations();
17
34
  };
18
- this.getDocumentVersion = function () {
19
- var version = _this.document.version;
20
- return version;
21
- };
22
35
  this.sendOperations = function () {
23
- if (_this.isSending || _this.pendingOperationList.length === 0) return;
24
- _this.isSending = true;
36
+ if (_this.state !== STATE.IDLE) return;
37
+ _this.state = STATE.SENDING;
25
38
  _this.sendNextOperations();
26
39
  };
27
40
  this.sendNextOperations = function () {
41
+ if (_this.state !== STATE.SENDING) return;
28
42
  if (_this.pendingOperationList.length === 0) {
29
- _this.isSending = false;
43
+ _this.state = STATE.IDLE;
30
44
  return;
31
45
  }
32
- if (_this._sendingOperations || _this.disconnect) return;
33
46
  _this.dispatchConnectState('is-saving');
34
47
  var version = _this.document.version;
35
48
  var operations = _this.pendingOperationList.shift();
@@ -38,113 +51,97 @@ var SocketManager = /*#__PURE__*/_createClass(function SocketManager(editor, doc
38
51
  _this.socketClient.sendOperations(operations, version, selection, _this.sendOperationsCallback);
39
52
  };
40
53
  this.sendOperationsCallback = function (result) {
41
- _this._sendingOperations = null;
42
54
  if (result && result.success) {
43
55
  var serverVersion = result.version;
44
56
  _this.document['version'] = serverVersion;
45
57
  var lastSavedAt = new Date().getTime();
46
58
  _this.dispatchConnectState('saved', lastSavedAt);
59
+
60
+ // send next operations
61
+ _this._sendingOperations = null;
47
62
  _this.sendNextOperations();
48
63
  return;
49
64
  }
50
65
 
51
66
  // Operations are execute failure
52
- var error_type = result.error_type,
53
- operations = result.operations;
67
+ var error_type = result.error_type;
54
68
  if (error_type === 'version_behind_server') {
55
69
  // Put the failed operation into the pending list and re-execute it
56
- _this.pendingOperationList.unshift(operations);
57
- _this.sendNextOperations();
70
+ _this.pendingOperationList.unshift(_toConsumableArray(_this._sendingOperations));
71
+ _this.state = STATE.CONFLICT;
72
+ var lose_operations = result.lose_operations;
73
+ _this.resolveConflicting(lose_operations);
58
74
  } else if (error_type === 'document_content_load_failed') {
59
75
  // After a short-term reconnection, the content of the document fails to load
60
76
  _this.dispatchConnectState(error_type);
77
+
78
+ // reset sending control
79
+ _this.state = STATE.NEED_RELOAD;
80
+ _this._sendingOperations = null;
61
81
  } else if (error_type === 'Internal_server_error') {
62
82
  _this.dispatchConnectState(error_type);
83
+
84
+ // reset sending control
85
+ _this.state = STATE.NEED_RELOAD;
86
+ _this._sendingOperations = null;
63
87
  } else if (error_type === 'operation_exec_error') {
64
88
  _this.editor.isRemote = true;
65
- revertOperationList(_this.editor, [operations]);
89
+ var dupSendingOperations = _toConsumableArray(_this._sendingOperations);
90
+ revertOperationList(_this.editor, [dupSendingOperations]);
66
91
 
67
- // reset control flag
92
+ // Set isRemote to false must be in Promise.resolve function, make sure the modification of isRemote is later than the onChange event
68
93
  Promise.resolve().then(function (_) {
69
94
  _this.editor.isRemote = false;
70
95
  _this.dispatchConnectState(error_type);
96
+
97
+ // send next operations
98
+ _this._sendingOperations = null;
71
99
  _this.sendNextOperations();
72
100
  });
73
101
  }
74
102
  };
75
- this.receiveOperations = function (params) {
76
- _this.remoteOperationsList.push(params);
77
- // sort operations by version
78
- if (_this.remoteOperationsList.length > 1) {
79
- _this.remoteOperationsList = _this.remoteOperationsList.sort(function (prev, next) {
80
- return prev.version - next.version;
103
+ this.onReceiveRemoteOperations = function (params) {
104
+ // if this.disconnect is true, Then the message sent by the remote end cannot be received
105
+ if (_this.state !== STATE.IDLE) return;
106
+ var serverVersion = params.version;
107
+ var clientVersion = _this.document.version;
108
+ if (serverVersion === clientVersion + 1) {
109
+ // update execute remote operations flag
110
+ _this.editor.isRemote = true;
111
+ debug('Editor isRemote is true: %s', _this.editor.isRemote);
112
+ var operations = params.operations;
113
+ // Update content & version
114
+ debug('execute remote operations: %O', operations);
115
+ syncRemoteOperations(_this.editor, operations);
116
+
117
+ // Update document
118
+ _this.document.version = serverVersion;
119
+ _this.document.children = _this.editor.children;
120
+ Promise.resolve().then(function () {
121
+ _this.editor.isRemote = false;
122
+ _this.revertOperationList = [];
81
123
  });
124
+ } else {
125
+ // isConflict
126
+ _this.onConflictHappen();
82
127
  }
83
- if (_this.isExecRemoteOperations) return;
84
-
85
- // update execute remote operations flag
86
- _this.isExecRemoteOperations = true;
87
- _this.editor.isRemote = true;
88
- _this.execRemoteOperations();
89
128
  };
90
- this.execRemoteOperations = function () {
91
- if (_this.remoteOperationsList.length > 0) {
92
- var params = _this.remoteOperationsList.shift();
93
- var serverVersion = params.version;
94
- var clientVersion = _this.document.version;
95
- if (serverVersion === clientVersion + 1) {
96
- // When performing remote operations, the revert operation is only required for the first time
97
- if (_this.revertOperationList.length === 0) {
98
- // 1. Revert operations
99
- // 1.1 record reverted operationList & clear pendingOperationList
100
- _this.revertOperationList = _this.pendingOperationList.slice();
101
- _this.pendingOperationList = [];
102
-
103
- // 1.2 Revert operationList
104
- debug('revert locale operations: %O', _this.revertOperationList);
105
- revertOperationList(_this.editor, _this.revertOperationList);
106
- }
107
-
108
- // 2. execute operations
109
- var operations = params.operations;
110
- // 2.1 Update content & version
111
- debug('execute remote operations: %O', operations);
112
- syncRemoteOperations(_this.editor, operations);
113
-
114
- // 2.2 Update document
115
- _this.document.version = serverVersion;
116
- _this.document.children = _this.editor.children;
117
-
118
- // Execute next operations
119
- if (_this.remoteOperationsList.length > 0) {
120
- _this.execRemoteOperations();
121
- return;
122
- }
123
-
124
- // Re-execute undone operations after isRemote is set to false
125
- // Need resend this operations to server
126
- Promise.resolve().then(function (_) {
127
- // reset execute remote operations flag
128
- _this.isExecRemoteOperations = false;
129
- _this.editor.isRemote = false;
130
-
131
- // 3. Execute pending operations
132
- // 3.1 Re-execute operations
133
- debug('Editor isRemote is false: %s', _this.editor.isRemote);
134
- debug('Re-execute pending operations, %O', _this.revertOperationList);
135
- reExecRevertOperationList(_this.editor, _this.revertOperationList);
136
-
137
- // 3.2 Clear revert operationList
138
- _this.revertOperationList = [];
139
- });
140
- } else {
141
- // conflict:
142
- // remote operations is not empty, waiting until remote operations all executed
143
- debug('remote operations is not empty, and local version is not match remote version.');
144
- }
129
+ this.onReconnect = function (result) {
130
+ var serverVersion = result.version;
131
+ var clientVersion = _this.getDocumentVersion();
132
+ // The client version is inconsistent with the server version, and the latest operations performed by the server need to be loaded
133
+ if (serverVersion !== clientVersion) {
134
+ _this.onConflictHappen();
145
135
  }
136
+
137
+ // The version consistency indicates that there is no conflict and no processing is required
138
+ _this.state = STATE.IDLE;
139
+ };
140
+ this.onConflictHappen = function () {
141
+ _this.state = STATE.CONFLICT;
142
+ _this.socketClient.getRecentOperations();
146
143
  };
147
- this.syncDocumentBySocket = function (result) {
144
+ this.onGetRecentOperations = function (result) {
148
145
  var mode = result.mode,
149
146
  content = result.content;
150
147
  // sync document
@@ -158,6 +155,8 @@ var SocketManager = /*#__PURE__*/_createClass(function SocketManager(editor, doc
158
155
  _this.editor.isRemote = true;
159
156
  _this.editor.onChange();
160
157
  _this.editor.isRemote = false;
158
+ _this.state = STATE.IDLE;
159
+ _this._sendingOperations = null;
161
160
 
162
161
  // 2. exec client operationList
163
162
  var pendingOperationList = _this.pendingOperationList.slice();
@@ -169,14 +168,67 @@ var SocketManager = /*#__PURE__*/_createClass(function SocketManager(editor, doc
169
168
  return;
170
169
  }
171
170
 
172
- // sync operations
171
+ // mode os operations: sync operations
173
172
  // content is [{version, operations}, {version, operations}, ...]
174
- _this.remoteOperationsList = content;
175
-
176
- // update execute remote operations flag
177
- _this.isExecRemoteOperations = true;
173
+ var loseOperations = content;
174
+ _this.resolveConflicting(loseOperations);
175
+ };
176
+ this.resolveConflicting = function (loseOperations) {
178
177
  _this.editor.isRemote = true;
179
- _this.execRemoteOperations();
178
+ debug('Editor isRemote is true: %s', _this.editor.isRemote);
179
+ if (_this.pendingOperationList.length !== 0) {
180
+ // 1. Revert operations
181
+ // 1.1 record reverted operationList & clear pendingOperationList
182
+ _this.revertOperationList = _this.pendingOperationList.slice();
183
+ _this.pendingOperationList = [];
184
+
185
+ // 1.2 Revert operationList
186
+ debug('revert locale operations: %O', _this.revertOperationList);
187
+ revertOperationList(_this.editor, _this.revertOperationList);
188
+ }
189
+ loseOperations = loseOperations.sort(function (prev, next) {
190
+ return prev.version - next.version;
191
+ });
192
+ debug('lose operations length: %s', loseOperations.length);
193
+ while (loseOperations.length > 0) {
194
+ var operationParams = loseOperations.shift();
195
+ // 2. execute operations
196
+ var operations = operationParams.operations,
197
+ serverVersion = operationParams.version;
198
+ // 2.1 Update content & version
199
+ debug('execute lose operations: %O', operations);
200
+ syncRemoteOperations(_this.editor, operations);
201
+
202
+ // 2.2 Update document
203
+ _this.document.version = serverVersion;
204
+ _this.document.children = _this.editor.children;
205
+ }
206
+ if (_this.revertOperationList.length === 0) {
207
+ Promise.resolve().then(function () {
208
+ _this.editor.isRemote = false;
209
+ _this.state = STATE.IDLE;
210
+ _this._sendingOperations = null;
211
+ _this.revertOperationList = [];
212
+ });
213
+ return;
214
+ }
215
+
216
+ // Set isRemote to false must be in Promise.resolve function, make sure the modification of isRemote is later than the onChange event
217
+ Promise.resolve().then(function () {
218
+ // reset execute remote operations flag
219
+ _this.editor.isRemote = false;
220
+ _this.state = STATE.IDLE;
221
+ _this._sendingOperations = null;
222
+
223
+ // 3. Execute pending operations
224
+ // 3.1 Re-execute operations
225
+ debug('Editor isRemote is false: %s', _this.editor.isRemote);
226
+ debug('Re-execute pending operations, %O', _this.revertOperationList);
227
+ reExecRevertOperationList(_this.editor, _this.revertOperationList);
228
+
229
+ // 3.2 Clear revert operationList
230
+ _this.revertOperationList = [];
231
+ });
180
232
  };
181
233
  this.sendCursorLocation = function (location) {
182
234
  _this.socketClient.sendCursorLocation(location);
@@ -198,16 +250,12 @@ var SocketManager = /*#__PURE__*/_createClass(function SocketManager(editor, doc
198
250
  _this.pendingOperationList.unshift(_this._sendingOperations.slice());
199
251
  _this._sendingOperations = null;
200
252
  }
201
- _this.isSending = false;
202
- _this.disconnect = true;
253
+ _this.state = STATE.DISCONNECT;
203
254
 
204
255
  // Update saved state
205
256
  var lastSavedAt = new Date().getTime();
206
257
  _this.dispatchConnectState('saved', lastSavedAt);
207
258
  }
208
- if (type === 'reconnect') {
209
- _this.disconnect = false;
210
- }
211
259
  _this.eventBus.dispatch(type, message);
212
260
  };
213
261
  this.closeSocketConnect = function () {
@@ -216,11 +264,11 @@ var SocketManager = /*#__PURE__*/_createClass(function SocketManager(editor, doc
216
264
  this.editor = editor;
217
265
  this.document = document;
218
266
  this.socketClient = new SocketClient(config);
219
- this.isSending = false;
220
267
  this.pendingOperationList = []; // Two-dimensional arrays: [operations, operations, ...]
221
268
  this.remoteOperationsList = []; // Same with pending operations
222
269
  this.revertOperationList = [];
223
270
  this.eventBus = EventBus.getInstance();
271
+ this.state = STATE.IDLE;
224
272
  });
225
273
  SocketManager.getInstance = function (editor, document, socketConfig) {
226
274
  if (SocketManager.instance) {
@@ -30,7 +30,7 @@ var withSocketIO = function withSocketIO(editor, options) {
30
30
  var updateOperations = operations.filter(function (operation) {
31
31
  return operation.type !== 'set_selection';
32
32
  });
33
- _socketManager.addOperations && _socketManager.addOperations(updateOperations);
33
+ _socketManager.onReceiveLocalOperations(updateOperations);
34
34
  }
35
35
  _socketManager.sendCursorLocation(editor.selection);
36
36
  }
@@ -141,7 +141,7 @@ var SimpleEditor = function SimpleEditor(_ref) {
141
141
  className: "error-page"
142
142
  }, /*#__PURE__*/React.createElement("div", {
143
143
  className: "error-tip"
144
- }, t(errorMessage))), !isLoadingSdoc && /*#__PURE__*/React.createElement(React.Fragment, null, isShowChanges ? /*#__PURE__*/React.createElement(DiffViewer, {
144
+ }, t(errorMessage))), !isLoadingSdoc && !errorMessage && /*#__PURE__*/React.createElement(React.Fragment, null, isShowChanges ? /*#__PURE__*/React.createElement(DiffViewer, {
145
145
  currentContent: currentContent,
146
146
  lastContent: lastContent
147
147
  }) : /*#__PURE__*/React.createElement(SDocEditor, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seafile/sdoc-editor",
3
- "version": "0.1.117",
3
+ "version": "0.1.119",
4
4
  "private": false,
5
5
  "description": "This is a sdoc editor",
6
6
  "main": "dist/index.js",