@seafile/sdoc-editor 0.1.48 → 0.1.49

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.
@@ -32,3 +32,7 @@
32
32
  border: 1px solid #e5e6e8;
33
33
  box-shadow: 0 0 15px rgba(0, 0, 0, 0.06);
34
34
  }
35
+
36
+ .sdoc-editor-container .sdoc-editor-content .article > div {
37
+ caret-color: blue;
38
+ }
@@ -69,3 +69,12 @@
69
69
  font-size: 12px;
70
70
  }
71
71
 
72
+ /* caret */
73
+ .sdoc-editor-container .article .caret-item .caret-name {
74
+ display: none;
75
+ }
76
+
77
+ .sdoc-editor-container .article .caret-item:hover .caret-name {
78
+ display: block;
79
+ }
80
+
@@ -0,0 +1,32 @@
1
+ import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
2
+ import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray";
3
+ import { Text, Path, Range } from '@seafile/slate';
4
+ export var decorateCursors = function decorateCursors(nodeEntry, storageCursors) {
5
+ var ranges = [];
6
+ var _nodeEntry = _slicedToArray(nodeEntry, 2),
7
+ node = _nodeEntry[0],
8
+ path = _nodeEntry[1];
9
+ var cursors = Object.values(storageCursors || {});
10
+ if (Text.isText(node) && (cursors === null || cursors === void 0 ? void 0 : cursors.length)) {
11
+ cursors.forEach(function (cursor) {
12
+ if (Range.includes(cursor, path)) {
13
+ var focus = cursor.focus,
14
+ anchor = cursor.anchor;
15
+ var isFocusNode = Path.equals(focus.path, path);
16
+ var isAnchorNode = Path.equals(anchor.path, path);
17
+ ranges.push(_objectSpread(_objectSpread({}, cursor), {}, {
18
+ isCaret: isFocusNode,
19
+ anchor: {
20
+ path: path,
21
+ offset: isAnchorNode ? anchor.offset : node.text.length
22
+ },
23
+ focus: {
24
+ path: path,
25
+ offset: isFocusNode ? focus.offset : 0
26
+ }
27
+ }));
28
+ }
29
+ });
30
+ }
31
+ return ranges;
32
+ };
@@ -0,0 +1,39 @@
1
+ import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
2
+ import randomColor from 'randomcolor';
3
+
4
+ // selection: { anchor, focus }
5
+ // cursor: { anchor, focus }
6
+
7
+ export var setCursor = function setCursor(editor, operations, user, selection, cursorData) {
8
+ var clientId = user.username;
9
+ var cursorOps = operations.filter(function (operation) {
10
+ return operation.type === 'set_selection';
11
+ });
12
+ if (!editor.cursors) editor.cursors = {};
13
+ var oldCursor = editor.cursors[clientId] ? editor.cursors[clientId] : {};
14
+ var lastCursorOp = cursorOps[cursorOps.length - 1];
15
+ if (selection) {
16
+ var newCursor = lastCursorOp && lastCursorOp.newProperties || {};
17
+ var newCursorData = _objectSpread(_objectSpread(_objectSpread(_objectSpread({}, oldCursor), newCursor), selection), cursorData);
18
+ editor.cursors[clientId] = newCursorData;
19
+ } else {
20
+ delete editor.cursors[clientId];
21
+ }
22
+ return editor;
23
+ };
24
+ export var deleteCursor = function deleteCursor(editor, username) {
25
+ delete editor.cursors[username];
26
+ };
27
+ export var generateCursorData = function generateCursorData(config) {
28
+ var user = config.user;
29
+ var options = {
30
+ luminosity: 'dark',
31
+ format: 'rgba',
32
+ alpha: 1
33
+ };
34
+ var color = randomColor(options);
35
+ return {
36
+ name: user.name,
37
+ color: color
38
+ };
39
+ };
@@ -0,0 +1,25 @@
1
+ import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray";
2
+ import { useCallback } from 'react';
3
+ import { decorateCursors } from '../cursor/decorate-cursors';
4
+ export var useDecorate = function useDecorate(editor) {
5
+ var cursors = editor.cursors;
6
+ var decorate = useCallback(function (nodeEntry) {
7
+ var ranges = [];
8
+ var addRanges = function addRanges(newRanges) {
9
+ if (newRanges.length) {
10
+ ranges = [].concat(_toConsumableArray(ranges), _toConsumableArray(newRanges));
11
+ }
12
+ };
13
+ console.log('eeee');
14
+ // useCursor
15
+ addRanges(decorateCursors(nodeEntry, cursors));
16
+
17
+ // others decorate
18
+ // ...
19
+
20
+ return ranges;
21
+ }, [cursors]);
22
+ return {
23
+ decorate: decorate
24
+ };
25
+ };
@@ -13,6 +13,7 @@ import withNodeId from './node-id';
13
13
  import SDocOutline from './outline';
14
14
  import EventProxy from './utils/event-handler';
15
15
  import { focusEditor } from './extension/core';
16
+ import { uiDecorate } from './decorates';
16
17
  import './assets/css/layout.css';
17
18
  import './assets/css/sdoc-editor-plugins.css';
18
19
  var SDocEditor = /*#__PURE__*/function (_React$Component) {
@@ -45,7 +46,9 @@ var SDocEditor = /*#__PURE__*/function (_React$Component) {
45
46
  });
46
47
  }
47
48
  };
48
- var children = props.document.children;
49
+ var _props$document = props.document,
50
+ children = _props$document.children,
51
+ cursors = _props$document.cursors;
49
52
  _this.state = {
50
53
  slateValue: children,
51
54
  isLoading: true
@@ -60,6 +63,7 @@ var SDocEditor = /*#__PURE__*/function (_React$Component) {
60
63
  config: config
61
64
  });
62
65
  }
66
+ _this.editor.cursors = cursors;
63
67
  _this.eventProxy = new EventProxy(_this.editor);
64
68
  _this.renderElement = function (props) {
65
69
  return renderElement(props, _this.editor);
@@ -67,6 +71,7 @@ var SDocEditor = /*#__PURE__*/function (_React$Component) {
67
71
  _this.renderLeaf = function (props) {
68
72
  return renderLeaf(props, _this.editor);
69
73
  };
74
+ _this.decorate = uiDecorate(_this.editor);
70
75
  return _this;
71
76
  }
72
77
  _createClass(SDocEditor, [{
@@ -110,7 +115,8 @@ var SDocEditor = /*#__PURE__*/function (_React$Component) {
110
115
  renderElement: this.renderElement,
111
116
  renderLeaf: this.renderLeaf,
112
117
  onKeyDown: this.eventProxy.onKeyDown,
113
- onDOMBeforeInput: this.onDOMBeforeInput
118
+ onDOMBeforeInput: this.onDOMBeforeInput,
119
+ decorate: this.decorate
114
120
  }))))));
115
121
  }
116
122
  }]);
@@ -0,0 +1,107 @@
1
+ import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray";
2
+ import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray";
3
+ var _this = this;
4
+ import React, { useCallback, useEffect, useMemo, useState } from 'react';
5
+ import { Node } from '@seafile/slate';
6
+ import { Editable, Slate, ReactEditor } from '@seafile/slate-react';
7
+ import { Editor } from '@seafile/slate';
8
+ import { useUpdate } from 'ahooks';
9
+ import editor2, { renderLeaf, renderElement, Toolbar } from './extension';
10
+ import { withSocketIO } from './socket';
11
+ import withNodeId from './node-id';
12
+ import SDocOutline from './outline';
13
+ import EventProxy from './utils/event-handler';
14
+ import { focusEditor } from './extension/core';
15
+ import './assets/css/layout.css';
16
+ import './assets/css/sdoc-editor-plugins.css';
17
+ import { useDecorate } from './decorates';
18
+ var SDocEditor = function SDocEditor(_ref) {
19
+ var document = _ref.document,
20
+ config = _ref.config;
21
+ var _useState = useState(document.children),
22
+ _useState2 = _slicedToArray(_useState, 2),
23
+ slateValue = _useState2[0],
24
+ setSlateValue = _useState2[1];
25
+
26
+ // init editor
27
+ var editor = useMemo(function () {
28
+ var newEditor = withNodeId(withSocketIO(editor2, {
29
+ document: document,
30
+ config: config
31
+ }));
32
+ var cursors = document.cursors;
33
+ newEditor.cursors = cursors || {};
34
+ return newEditor;
35
+ // eslint-disable-next-line react-hooks/exhaustive-deps
36
+ }, []);
37
+ var eventProxy = useMemo(function () {
38
+ return new EventProxy(editor);
39
+ // eslint-disable-next-line react-hooks/exhaustive-deps
40
+ }, []);
41
+
42
+ // useMount: init socket connection
43
+ useEffect(function () {
44
+ editor.openConnection();
45
+ return function () {
46
+ editor.closeConnection();
47
+ };
48
+ // eslint-disable-next-line react-hooks/exhaustive-deps
49
+ }, []);
50
+
51
+ // useMount: focus editor
52
+ useEffect(function () {
53
+ if (Node.string(editor) === '') {
54
+ var firstNodePath = ReactEditor.findPath(editor, editor.children[0]);
55
+ focusEditor(_this.editor, firstNodePath);
56
+ }
57
+ }, []);
58
+ var _renderElement = useCallback(function (props) {
59
+ return renderElement(props, editor);
60
+ }, []);
61
+ var _renderLeaf = useCallback(function (props) {
62
+ return renderLeaf(props, editor);
63
+ }, []);
64
+ var onChange = useCallback(function (slateValue) {
65
+ setSlateValue(_toConsumableArray(slateValue));
66
+ }, []);
67
+ var update = useUpdate();
68
+ var onDOMBeforeInput = useCallback(function (e) {
69
+ if (e.data && e.data !== '') {
70
+ var _node$;
71
+ var node = Editor.parent(editor, editor.selection);
72
+ editor.onDOMBeforeInputId = (_node$ = node[0]) === null || _node$ === void 0 ? void 0 : _node$.id;
73
+ update();
74
+ } else {
75
+ editor.onDOMBeforeInputId = null;
76
+ update();
77
+ }
78
+ }, []);
79
+ var _useDecorate = useDecorate(editor),
80
+ decorate = _useDecorate.decorate;
81
+ console.log('dadadihuaile');
82
+ return /*#__PURE__*/React.createElement("div", {
83
+ className: "sdoc-editor-container"
84
+ }, /*#__PURE__*/React.createElement(Toolbar, {
85
+ editor: editor
86
+ }), /*#__PURE__*/React.createElement("div", {
87
+ className: "sdoc-editor-content"
88
+ }, /*#__PURE__*/React.createElement(SDocOutline, {
89
+ doc: slateValue,
90
+ docUuid: config.docUuid
91
+ }), /*#__PURE__*/React.createElement("div", {
92
+ className: "flex-fill o-auto"
93
+ }, /*#__PURE__*/React.createElement(Slate, {
94
+ editor: editor,
95
+ value: slateValue,
96
+ onChange: onChange
97
+ }, /*#__PURE__*/React.createElement("div", {
98
+ className: "article"
99
+ }, /*#__PURE__*/React.createElement(Editable, {
100
+ renderElement: _renderElement,
101
+ renderLeaf: _renderLeaf,
102
+ onKeyDown: eventProxy.onKeyDown,
103
+ onDOMBeforeInput: onDOMBeforeInput,
104
+ decorate: decorate
105
+ }))))));
106
+ };
107
+ export default SDocEditor;
@@ -6,12 +6,12 @@ import renderElement from './render/render-element';
6
6
  import renderLeaf from './render/render-leaf';
7
7
  import Toolbar from './toolbar';
8
8
  var baseEditor = withHistory(withReact(createEditor()));
9
- var editor = Plugins.reduce(function (editor, pluginItem) {
9
+ var editor2 = Plugins.reduce(function (editor, pluginItem) {
10
10
  var withPlugin = pluginItem.editorPlugin;
11
11
  if (withPlugin) {
12
12
  return withPlugin(editor);
13
13
  }
14
14
  return editor;
15
15
  }, baseEditor);
16
- export default editor;
16
+ export default editor2;
17
17
  export { renderLeaf, renderElement, Toolbar };
@@ -0,0 +1,49 @@
1
+ import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
2
+ import React from 'react';
3
+ var cursorStyleBase = {
4
+ position: 'absolute',
5
+ top: -2,
6
+ pointerEvents: 'none',
7
+ userSelect: 'none',
8
+ transform: 'translateY(-100%)',
9
+ fontSize: 10,
10
+ color: 'white',
11
+ background: 'palevioletred',
12
+ whiteSpace: 'nowrap'
13
+ };
14
+ var caretStyleBase = {
15
+ position: 'absolute',
16
+ // pointerEvents: 'none',
17
+ userSelect: 'none',
18
+ height: '1.2em',
19
+ width: 2,
20
+ background: 'palevioletred'
21
+ };
22
+ var Caret = function Caret(_ref) {
23
+ var color = _ref.color,
24
+ name = _ref.name;
25
+ var cursorStyles = _objectSpread(_objectSpread({}, cursorStyleBase), {}, {
26
+ background: color,
27
+ left: '0%',
28
+ cursor: 'default'
29
+ });
30
+ var caretStyles = _objectSpread(_objectSpread({}, caretStyleBase), {}, {
31
+ background: color,
32
+ left: '0%'
33
+ });
34
+ caretStyles['top'] = 1;
35
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("span", {
36
+ className: "caret-item",
37
+ contentEditable: false,
38
+ style: caretStyles
39
+ }, /*#__PURE__*/React.createElement("span", {
40
+ style: {
41
+ position: 'relative'
42
+ }
43
+ }, /*#__PURE__*/React.createElement("span", {
44
+ className: "caret-name",
45
+ contentEditable: false,
46
+ style: cursorStyles
47
+ }, name))));
48
+ };
49
+ export default Caret;
@@ -1,4 +1,5 @@
1
1
  import React from 'react';
2
+ import Caret from './caret';
2
3
  var renderText = function renderText(props, editor) {
3
4
  var attributes = props.attributes,
4
5
  children = props.children,
@@ -38,8 +39,17 @@ var renderText = function renderText(props, editor) {
38
39
  className: "token ".concat(leaf.type)
39
40
  }, markedChildren);
40
41
  }
42
+ var style = {
43
+ position: 'relative'
44
+ };
45
+ if (leaf.isCaret) {
46
+ style['display'] = 'inline-block';
47
+ style['minWidth'] = '2px';
48
+ }
41
49
  return /*#__PURE__*/React.createElement("span", Object.assign({
42
50
  "data-id": leaf.id
43
- }, attributes), markedChildren);
51
+ }, attributes, {
52
+ style: style
53
+ }), leaf.isCaret ? /*#__PURE__*/React.createElement(Caret, leaf) : null, markedChildren);
44
54
  };
45
55
  export default renderText;
@@ -1,4 +1,4 @@
1
- import SDocEditor from './editor';
1
+ import SDocEditor from './editor2';
2
2
  import SDocViewer from './viewer';
3
3
  import SDocOutline from './outline';
4
4
  import EventBus from './utils/event-bus';
@@ -3,6 +3,8 @@ import deepCopy from 'deep-copy';
3
3
  import { Editor, Operation } from '@seafile/slate';
4
4
  import { getNode } from '../extension/core';
5
5
  import * as OPERATION from '../node-id/constants';
6
+ import { setCursor } from '../cursor/helper';
7
+ import context from '../../context';
6
8
  export var getNodePathById = function getNodePathById(rootNode, nodeId) {
7
9
  var path = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : [];
8
10
  if (rootNode.id === nodeId) return path;
@@ -250,11 +252,21 @@ export var reExecRevertOperationList = function reExecRevertOperationList(editor
250
252
  _loop2();
251
253
  }
252
254
  };
253
- export var syncRemoteOperations = function syncRemoteOperations(editor, remoteOperations) {
255
+ export var syncRemoteOperations = function syncRemoteOperations(editor, remoteOperations, user, selection, cursorData) {
254
256
  if (remoteOperations.length === 0) return;
255
257
  Editor.withoutNormalizing(editor, function () {
256
- remoteOperations.forEach(function (item) {
257
- editor.apply(item);
258
- });
258
+ for (var i = 0; i < remoteOperations.length; i++) {
259
+ var op = remoteOperations[i];
260
+ if (op.type === 'set_selection') {
261
+ continue;
262
+ }
263
+ editor.apply(op);
264
+ }
265
+ var currentUser = editor.user;
266
+ if (user && user.username !== currentUser.username) {
267
+ setCursor(editor, remoteOperations, user, selection, cursorData);
268
+ // sync cursor position
269
+ editor.onChange();
270
+ }
259
271
  });
260
272
  };
@@ -11,10 +11,12 @@ var SocketClient = /*#__PURE__*/_createClass(function SocketClient(config) {
11
11
  var params = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
12
12
  var _this$config = _this.config,
13
13
  docUuid = _this$config.docUuid,
14
- user = _this$config.user;
14
+ user = _this$config.user,
15
+ cursorData = _this$config.cursorData;
15
16
  return _objectSpread({
16
17
  doc_uuid: docUuid,
17
- user: user
18
+ user: user,
19
+ cursor_data: cursorData
18
20
  }, params);
19
21
  };
20
22
  this.onConnected = function () {
@@ -76,13 +78,14 @@ var SocketClient = /*#__PURE__*/_createClass(function SocketClient(config) {
76
78
  var socketManager = SocketManager.getInstance();
77
79
  socketManager.dispatchConnectState('leave-room', username);
78
80
  };
79
- this.sendOperations = function (operations, version, callback) {
81
+ this.sendOperations = function (operations, version, selection, callback) {
80
82
  debug('=========== send operations ==========');
81
83
  debug('%O', operations);
82
84
  debug('======================================');
83
85
  _this.socket.emit('update-document', _this.getParams({
84
86
  operations: operations,
85
- version: version
87
+ version: version,
88
+ selection: selection
86
89
  }), function (result) {
87
90
  callback && callback(result);
88
91
  });
@@ -4,6 +4,7 @@ import EventBus from '../utils/event-bus';
4
4
  import { syncRemoteOperations, reExecRevertOperationList, revertOperationList } from './helpers';
5
5
  import SocketClient from './socket-client';
6
6
  import debug from '../utils/debug';
7
+ import { deleteCursor } from '../cursor/helper';
7
8
  var SocketManager = /*#__PURE__*/_createClass(function SocketManager(editor, document, config) {
8
9
  var _this = this;
9
10
  _classCallCheck(this, SocketManager);
@@ -28,7 +29,8 @@ var SocketManager = /*#__PURE__*/_createClass(function SocketManager(editor, doc
28
29
  _this.dispatchConnectState('is-saving');
29
30
  var version = _this.document.version;
30
31
  var operations = _this.pendingOperationList.shift();
31
- _this.socketClient.sendOperations(operations, version, _this.sendOperationsCallback);
32
+ var selection = _this.editor.selection;
33
+ _this.socketClient.sendOperations(operations, version, selection, _this.sendOperationsCallback);
32
34
  };
33
35
  this.sendOperationsCallback = function (result) {
34
36
  if (result && result.success) {
@@ -83,10 +85,13 @@ var SocketManager = /*#__PURE__*/_createClass(function SocketManager(editor, doc
83
85
  }
84
86
 
85
87
  // 2. execute operations
86
- var operations = params.operations;
88
+ var operations = params.operations,
89
+ user = params.user,
90
+ selection = params.selection,
91
+ cursorData = params.cursor_data;
87
92
  // 2.1 Update content & version
88
93
  debug('execute remote operations: %O', operations);
89
- syncRemoteOperations(_this.editor, operations);
94
+ syncRemoteOperations(_this.editor, operations, user, selection, cursorData);
90
95
 
91
96
  // 2.2 Update document
92
97
  _this.document.version = serverVersion;
@@ -157,6 +162,10 @@ var SocketManager = /*#__PURE__*/_createClass(function SocketManager(editor, doc
157
162
  });
158
163
  };
159
164
  this.dispatchConnectState = function (type, message) {
165
+ if (type === 'leave-room') {
166
+ deleteCursor(_this.editor, message);
167
+ _this.editor.onChange();
168
+ }
160
169
  _this.eventBus.dispatch(type, message);
161
170
  };
162
171
  this.closeSocketConnect = function () {
@@ -1,12 +1,17 @@
1
1
  var _this = this;
2
+ import { generateCursorData } from '../cursor/helper';
2
3
  import SocketManager from './socket-manager';
3
4
  var withSocketIO = function withSocketIO(editor, options) {
4
5
  var onChange = editor.onChange;
5
6
  var newEditor = editor;
6
7
  var socketManager = null;
8
+ var user = options.config.user;
9
+ newEditor.user = user;
7
10
  newEditor.openConnection = function () {
8
11
  var document = options.document,
9
12
  config = options.config;
13
+ var cursorData = generateCursorData(options.config);
14
+ config['cursorData'] = cursorData;
10
15
  socketManager = SocketManager.getInstance(newEditor, document, config);
11
16
  };
12
17
  newEditor.closeConnection = function () {
@@ -14,9 +19,7 @@ var withSocketIO = function withSocketIO(editor, options) {
14
19
  };
15
20
  newEditor.onChange = function () {
16
21
  var operations = newEditor.operations;
17
- operations = operations.filter(function (item) {
18
- return item.type !== 'set_selection';
19
- });
22
+ // operations = operations.filter(item => item.type !== 'set_selection');
20
23
  if (!newEditor.isRemote && operations.length > 0) {
21
24
  var _socketManager = SocketManager.getInstance();
22
25
  _socketManager.addOperations && _socketManager.addOperations(operations);
@@ -68,7 +68,8 @@ var SimpleEditor = /*#__PURE__*/function (_React$Component) {
68
68
  if (result && !result.children) {
69
69
  result = {
70
70
  version: 0,
71
- children: result.content
71
+ children: result.content,
72
+ cursors: result.cursors || {}
72
73
  };
73
74
  }
74
75
  this.setState({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seafile/sdoc-editor",
3
- "version": "0.1.48",
3
+ "version": "0.1.49",
4
4
  "private": false,
5
5
  "description": "This is a sdoc editor",
6
6
  "main": "dist/index.js",
@@ -10,10 +10,12 @@
10
10
  "@seafile/slate-history": "0.86.2",
11
11
  "@seafile/slate-hyperscript": "0.81.7",
12
12
  "@seafile/slate-react": "0.92.2",
13
+ "ahooks": "3.7.7",
13
14
  "classnames": "2.3.2",
14
15
  "deep-copy": "1.4.2",
15
16
  "is-hotkey": "0.2.0",
16
17
  "is-url": "^1.2.4",
18
+ "randomcolor": "0.6.2",
17
19
  "react-cookies": "0.1.1",
18
20
  "reactstrap": "8.9.0",
19
21
  "slugid": "3.2.0",