@seafile/sdoc-editor 0.5.4 → 0.5.6
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/dist/assets/css/simple-editor.css +15 -0
- package/dist/basic-sdk/assets/css/layout.css +15 -0
- package/dist/basic-sdk/comment/components/comment-editor.js +49 -33
- package/dist/basic-sdk/comment/components/comment-item-content.js +11 -3
- package/dist/basic-sdk/comment/components/comment-item-reply.js +13 -5
- package/dist/basic-sdk/comment/components/comment-item-wrapper.js +1 -1
- package/dist/basic-sdk/comment/components/comment-list.css +27 -19
- package/dist/basic-sdk/comment/components/comment-list.js +0 -1
- package/dist/basic-sdk/comment/components/global-comment/index.css +2 -6
- package/dist/basic-sdk/comment/utils/index.js +5 -5
- package/dist/basic-sdk/constants/index.js +5 -2
- package/dist/basic-sdk/editor/comment-article.js +175 -0
- package/dist/basic-sdk/editor/editable-article.js +2 -1
- package/dist/basic-sdk/editor/sdoc-comment-editor.js +108 -0
- package/dist/basic-sdk/extension/commons/insert-element-dialog/index.js +12 -7
- package/dist/basic-sdk/extension/commons/menu/menu-item.js +1 -1
- package/dist/basic-sdk/extension/constants/element-type.js +3 -1
- package/dist/basic-sdk/extension/constants/index.js +2 -2
- package/dist/basic-sdk/extension/constants/menus-config.js +2 -2
- package/dist/basic-sdk/extension/index.js +11 -1
- package/dist/basic-sdk/extension/plugins/blockquote/plugin.js +3 -3
- package/dist/basic-sdk/extension/plugins/callout/helper.js +5 -2
- package/dist/basic-sdk/extension/plugins/image/helpers.js +6 -3
- package/dist/basic-sdk/extension/plugins/image/menu/index.js +25 -4
- package/dist/basic-sdk/extension/plugins/index.js +3 -1
- package/dist/basic-sdk/extension/plugins/link/menu/index.js +22 -3
- package/dist/basic-sdk/extension/plugins/markdown/plugin.js +17 -6
- package/dist/basic-sdk/extension/plugins/mention/helper.js +142 -0
- package/dist/basic-sdk/extension/plugins/mention/index.js +10 -0
- package/dist/basic-sdk/extension/plugins/mention/plugin.js +258 -0
- package/dist/basic-sdk/{comment/components/comment-input → extension/plugins/mention/render-elem}/comment-participant-item.js +1 -1
- package/dist/basic-sdk/{comment/components/comment-input → extension/plugins/mention/render-elem}/index.css +22 -1
- package/dist/basic-sdk/extension/plugins/mention/render-elem/index.js +51 -0
- package/dist/basic-sdk/extension/plugins/mention/render-elem/participant-popover.js +219 -0
- package/dist/basic-sdk/extension/plugins/paragraph/helper.js +10 -0
- package/dist/basic-sdk/extension/plugins/paragraph/render-elem.js +9 -3
- package/dist/basic-sdk/extension/plugins/text-style/menu/comemnt-editor-menu.js +68 -0
- package/dist/basic-sdk/extension/plugins/text-style/menu/index.js +19 -8
- package/dist/basic-sdk/extension/render/custom-element.js +12 -2
- package/dist/basic-sdk/extension/toolbar/comment-editor-toolbar/index.js +45 -0
- package/dist/basic-sdk/utils/diff.js +13 -0
- package/dist/basic-sdk/utils/event-handler.js +21 -0
- package/dist/components/doc-operations/revision-operations/changes-count/index.js +19 -9
- package/dist/slate-convert/md-to-slate/transform.js +17 -0
- package/dist/slate-convert/slate-to-md/transform.js +28 -2
- package/package.json +1 -1
- package/dist/basic-sdk/comment/components/comment-input/helpers.js +0 -15
- package/dist/basic-sdk/comment/components/comment-input/index.js +0 -306
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
|
|
2
|
+
import { Editor, Node, Range, Text, Transforms } from '@seafile/slate';
|
|
3
|
+
import EventBus from '../../../utils/event-bus';
|
|
4
|
+
import { INTERNAL_EVENT } from '../../../constants';
|
|
5
|
+
import { MENTION, MENTION_TEMP } from '../../constants/element-type';
|
|
6
|
+
import { insertTemporaryMentionInput, getMentionTempIptEntry, transformToText, getMentionEntry, getPrevMentionIptEntry } from './helper';
|
|
7
|
+
import { KeyCodes } from '../../../../constants';
|
|
8
|
+
import { focusEditor } from '../../core';
|
|
9
|
+
const withMention = editor => {
|
|
10
|
+
const {
|
|
11
|
+
insertText,
|
|
12
|
+
onHotKeyDown,
|
|
13
|
+
isInline,
|
|
14
|
+
deleteBackward,
|
|
15
|
+
deleteForward,
|
|
16
|
+
normalizeNode
|
|
17
|
+
} = editor;
|
|
18
|
+
const newEditor = editor;
|
|
19
|
+
const eventBus = EventBus.getInstance();
|
|
20
|
+
newEditor.insertText = text => {
|
|
21
|
+
const {
|
|
22
|
+
selection
|
|
23
|
+
} = editor;
|
|
24
|
+
if (text === '@' && !getMentionTempIptEntry(editor)) {
|
|
25
|
+
insertTemporaryMentionInput(newEditor);
|
|
26
|
+
const {
|
|
27
|
+
anchor
|
|
28
|
+
} = selection;
|
|
29
|
+
const path = Editor.path(editor, anchor);
|
|
30
|
+
const abovePath = path.slice(0, path.length - 1);
|
|
31
|
+
const focusPath = abovePath.concat(path.at(-1) + 1);
|
|
32
|
+
focusEditor(editor, focusPath);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const prevNodeEntry = Editor.previous(editor);
|
|
36
|
+
if (prevNodeEntry) {
|
|
37
|
+
const aboveNodeEntry = Editor.above(editor, {
|
|
38
|
+
match: n => n.type === MENTION_TEMP,
|
|
39
|
+
at: prevNodeEntry[1]
|
|
40
|
+
});
|
|
41
|
+
if (aboveNodeEntry) {
|
|
42
|
+
var _text$match;
|
|
43
|
+
const inputCnCharacter = (_text$match = text.match(/^[\u4e00-\u9fa5]+$/)) === null || _text$match === void 0 ? void 0 : _text$match.input;
|
|
44
|
+
if (inputCnCharacter) {
|
|
45
|
+
const insertPoint = Editor.end(editor, aboveNodeEntry[1]);
|
|
46
|
+
const nextNodeEntry = Editor.next(editor, {
|
|
47
|
+
at: aboveNodeEntry[1]
|
|
48
|
+
});
|
|
49
|
+
Transforms.insertText(editor, text, {
|
|
50
|
+
at: insertPoint
|
|
51
|
+
});
|
|
52
|
+
if (nextNodeEntry) {
|
|
53
|
+
const [nextNode, nextPath] = nextNodeEntry;
|
|
54
|
+
if (Text.isText(nextNode) && nextNode.text === '') {
|
|
55
|
+
Transforms.removeNodes(editor, {
|
|
56
|
+
at: nextPath
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return focusEditor(editor, _objectSpread(_objectSpread({}, insertPoint), {}, {
|
|
61
|
+
offset: insertPoint.offset + text.length
|
|
62
|
+
}));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return insertText(text);
|
|
67
|
+
};
|
|
68
|
+
newEditor.deleteBackward = unit => {
|
|
69
|
+
const mentionTempInputEntry = getMentionTempIptEntry(editor);
|
|
70
|
+
if (mentionTempInputEntry) {
|
|
71
|
+
const {
|
|
72
|
+
selection
|
|
73
|
+
} = editor;
|
|
74
|
+
if (selection && Range.isCollapsed(selection)) {
|
|
75
|
+
const [node, path] = mentionTempInputEntry;
|
|
76
|
+
const contentString = Node.string(node);
|
|
77
|
+
if (!contentString) {
|
|
78
|
+
return Transforms.delete(editor, {
|
|
79
|
+
at: path
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Delete mention, when in mention
|
|
86
|
+
const prevNodeEntry = Editor.previous(editor);
|
|
87
|
+
if (prevNodeEntry) {
|
|
88
|
+
const aboveNodeEntry = Editor.above(editor, {
|
|
89
|
+
match: n => n.type === MENTION,
|
|
90
|
+
at: prevNodeEntry[1]
|
|
91
|
+
});
|
|
92
|
+
const mentionEntry = getMentionEntry(editor);
|
|
93
|
+
if (mentionEntry || aboveNodeEntry) {
|
|
94
|
+
const {
|
|
95
|
+
selection
|
|
96
|
+
} = editor;
|
|
97
|
+
if (selection && Range.isCollapsed(selection)) {
|
|
98
|
+
const [, mentionPath] = mentionEntry || aboveNodeEntry;
|
|
99
|
+
return Transforms.removeNodes(editor, {
|
|
100
|
+
at: mentionPath
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return deleteBackward(unit);
|
|
106
|
+
};
|
|
107
|
+
newEditor.deleteForward = unit => {
|
|
108
|
+
const mentionEntry = Editor.next(editor, {
|
|
109
|
+
match: n => n.type === MENTION
|
|
110
|
+
});
|
|
111
|
+
if (mentionEntry) {
|
|
112
|
+
const [, mentionPath] = mentionEntry;
|
|
113
|
+
return Transforms.removeNodes(editor, {
|
|
114
|
+
at: mentionPath
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
return deleteForward(unit);
|
|
118
|
+
};
|
|
119
|
+
newEditor.onHotKeyDown = event => {
|
|
120
|
+
const mentionTempIptEntry = getMentionTempIptEntry(editor);
|
|
121
|
+
if (mentionTempIptEntry) {
|
|
122
|
+
const [, mentionTempIptPath] = mentionTempIptEntry;
|
|
123
|
+
const {
|
|
124
|
+
DownArrow,
|
|
125
|
+
UpArrow,
|
|
126
|
+
Enter,
|
|
127
|
+
Esc,
|
|
128
|
+
RightArrow,
|
|
129
|
+
LeftArrow
|
|
130
|
+
} = KeyCodes;
|
|
131
|
+
const {
|
|
132
|
+
keyCode
|
|
133
|
+
} = event;
|
|
134
|
+
if (keyCode === RightArrow || keyCode === LeftArrow) {
|
|
135
|
+
const {
|
|
136
|
+
selection
|
|
137
|
+
} = editor;
|
|
138
|
+
if (!selection) return;
|
|
139
|
+
if (!Range.isCollapsed(selection)) return;
|
|
140
|
+
if (keyCode === RightArrow) {
|
|
141
|
+
if (Editor.isEnd(editor, selection.focus, mentionTempIptPath)) return transformToText(newEditor);
|
|
142
|
+
}
|
|
143
|
+
if (keyCode === LeftArrow) {
|
|
144
|
+
if (Editor.isStart(editor, selection.focus, mentionTempIptPath)) {
|
|
145
|
+
event.preventDefault();
|
|
146
|
+
return transformToText(newEditor, false);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Handle by collaborators list
|
|
152
|
+
const interceptorKeyCodes = [DownArrow, UpArrow, Enter, Esc];
|
|
153
|
+
if (interceptorKeyCodes.includes(keyCode)) {
|
|
154
|
+
event.preventDefault();
|
|
155
|
+
eventBus.dispatch(INTERNAL_EVENT.HANDLE_MENTION_TEMP_CHOSEN, {
|
|
156
|
+
event
|
|
157
|
+
});
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
const mentionEntry = getMentionEntry(editor);
|
|
162
|
+
if (mentionEntry) {
|
|
163
|
+
const [, mentionPath] = mentionEntry;
|
|
164
|
+
const {
|
|
165
|
+
RightArrow,
|
|
166
|
+
LeftArrow
|
|
167
|
+
} = KeyCodes;
|
|
168
|
+
const {
|
|
169
|
+
keyCode
|
|
170
|
+
} = event;
|
|
171
|
+
if (keyCode === RightArrow || keyCode === LeftArrow) {
|
|
172
|
+
event.preventDefault();
|
|
173
|
+
if (keyCode === LeftArrow) {
|
|
174
|
+
const beginPoint = Editor.start(editor, mentionPath);
|
|
175
|
+
const focusPoint = Editor.before(editor, beginPoint, {
|
|
176
|
+
distance: 1
|
|
177
|
+
});
|
|
178
|
+
focusEditor(newEditor, focusPoint);
|
|
179
|
+
} else {
|
|
180
|
+
const endPoint = Editor.end(editor, mentionPath);
|
|
181
|
+
const focusPoint = Editor.after(editor, endPoint, {
|
|
182
|
+
distance: 1
|
|
183
|
+
});
|
|
184
|
+
focusEditor(newEditor, focusPoint);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return onHotKeyDown && onHotKeyDown(event);
|
|
189
|
+
};
|
|
190
|
+
newEditor.onCompositionUpdate = event => {
|
|
191
|
+
const mentionTempIptEntry = getMentionTempIptEntry(newEditor);
|
|
192
|
+
if (mentionTempIptEntry) {
|
|
193
|
+
const {
|
|
194
|
+
data
|
|
195
|
+
} = event;
|
|
196
|
+
const compositionText = data.replace(/\'/g, '');
|
|
197
|
+
eventBus.dispatch(INTERNAL_EVENT.UPDATE_MENTION_TEMP_CONTENT, {
|
|
198
|
+
compositionText
|
|
199
|
+
});
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
newEditor.onCompositionStart = event => {
|
|
204
|
+
const mentionTempIptEntry = getMentionTempIptEntry(editor);
|
|
205
|
+
if (mentionTempIptEntry) {
|
|
206
|
+
event.preventDefault();
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
newEditor.onCompositionEnd = event => {
|
|
211
|
+
const PrevMentionIptEntry = getPrevMentionIptEntry(newEditor);
|
|
212
|
+
if (PrevMentionIptEntry) {
|
|
213
|
+
const {
|
|
214
|
+
data
|
|
215
|
+
} = event;
|
|
216
|
+
const insertPoint = Editor.end(editor, PrevMentionIptEntry[1]);
|
|
217
|
+
const nextNodeEntry = Editor.next(editor, {
|
|
218
|
+
at: PrevMentionIptEntry[1]
|
|
219
|
+
});
|
|
220
|
+
Transforms.insertText(editor, data, {
|
|
221
|
+
at: insertPoint
|
|
222
|
+
});
|
|
223
|
+
event.preventDefault();
|
|
224
|
+
focusEditor(editor, _objectSpread(_objectSpread({}, insertPoint), {}, {
|
|
225
|
+
offset: insertPoint.offset + data.length
|
|
226
|
+
}));
|
|
227
|
+
if (nextNodeEntry) {
|
|
228
|
+
const [nextNode, nextPath] = nextNodeEntry;
|
|
229
|
+
if (Text.isText(nextNode) && nextNode.text === '') {
|
|
230
|
+
Transforms.removeNodes(editor, {
|
|
231
|
+
at: nextPath
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return true;
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
newEditor.isInline = element => {
|
|
239
|
+
if ([MENTION, MENTION_TEMP].includes(element.type)) {
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
242
|
+
return isInline(element);
|
|
243
|
+
};
|
|
244
|
+
newEditor.normalizeNode = _ref => {
|
|
245
|
+
let [node, path] = _ref;
|
|
246
|
+
const mentionEntry = getMentionEntry(editor);
|
|
247
|
+
if (mentionEntry) {
|
|
248
|
+
const nextEntry = Editor.next(editor, {
|
|
249
|
+
at: mentionEntry[1]
|
|
250
|
+
});
|
|
251
|
+
const focusPoint = Editor.start(editor, nextEntry[1]);
|
|
252
|
+
focusEditor(editor, focusPoint);
|
|
253
|
+
}
|
|
254
|
+
return normalizeNode([node, path]);
|
|
255
|
+
};
|
|
256
|
+
return newEditor;
|
|
257
|
+
};
|
|
258
|
+
export default withMention;
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
max-height: 200px;
|
|
6
6
|
overflow: auto;
|
|
7
7
|
min-width: 150px;
|
|
8
|
-
/* higher than row expand */
|
|
9
8
|
z-index: 1049;
|
|
10
9
|
border-radius: 5px;
|
|
11
10
|
border: 1px solid #ededed;
|
|
@@ -43,3 +42,25 @@
|
|
|
43
42
|
text-overflow: ellipsis;
|
|
44
43
|
white-space: nowrap;
|
|
45
44
|
}
|
|
45
|
+
|
|
46
|
+
.sdoc-mention-temp-ipt {
|
|
47
|
+
padding: 0 5px;
|
|
48
|
+
display: inline-block;
|
|
49
|
+
border-radius: 5px;
|
|
50
|
+
margin: 0 1px;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.sdoc-mention {
|
|
54
|
+
display: inline-block;
|
|
55
|
+
margin: 0 2px;
|
|
56
|
+
padding: 0 2px;
|
|
57
|
+
border: none;
|
|
58
|
+
background-color: transparent;
|
|
59
|
+
color: #1677ff;
|
|
60
|
+
border-radius: 5px;
|
|
61
|
+
cursor: pointer;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.sdoc-mention:hover {
|
|
65
|
+
background-color: rgb(221, 236, 253);
|
|
66
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/* eslint-disable react-hooks/rules-of-hooks */
|
|
2
|
+
import React, { useCallback, useEffect, useState } from 'react';
|
|
3
|
+
import { Node } from '@seafile/slate';
|
|
4
|
+
import ParticipantPopover from './participant-popover';
|
|
5
|
+
import EventBus from '../../../../utils/event-bus';
|
|
6
|
+
import { INTERNAL_EVENT } from '../../../../constants';
|
|
7
|
+
import './index.css';
|
|
8
|
+
const renderMention = _ref => {
|
|
9
|
+
let {
|
|
10
|
+
attributes,
|
|
11
|
+
children,
|
|
12
|
+
element,
|
|
13
|
+
editor,
|
|
14
|
+
readonly
|
|
15
|
+
} = _ref;
|
|
16
|
+
return /*#__PURE__*/React.createElement("span", Object.assign({}, attributes, {
|
|
17
|
+
contentEditable: "false",
|
|
18
|
+
key: element.id
|
|
19
|
+
}), /*#__PURE__*/React.createElement("button", {
|
|
20
|
+
className: "sdoc-mention"
|
|
21
|
+
}, children));
|
|
22
|
+
};
|
|
23
|
+
const renderMentionTemporaryInput = (_ref2, editor) => {
|
|
24
|
+
let {
|
|
25
|
+
attributes,
|
|
26
|
+
children,
|
|
27
|
+
element,
|
|
28
|
+
readonly
|
|
29
|
+
} = _ref2;
|
|
30
|
+
const [searchText, setSearchText] = useState('');
|
|
31
|
+
const updateSearchText = useCallback(_ref3 => {
|
|
32
|
+
let {
|
|
33
|
+
compositionText
|
|
34
|
+
} = _ref3;
|
|
35
|
+
setSearchText(Node.string(element) + compositionText);
|
|
36
|
+
}, [element]);
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
setSearchText(Node.string(element));
|
|
39
|
+
}, [element]);
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
const eventBus = EventBus.getInstance();
|
|
42
|
+
eventBus.subscribe(INTERNAL_EVENT.UPDATE_MENTION_TEMP_CONTENT, updateSearchText);
|
|
43
|
+
}, [updateSearchText]);
|
|
44
|
+
return /*#__PURE__*/React.createElement("span", Object.assign({}, attributes, {
|
|
45
|
+
className: "sdoc-mention-temp-ipt"
|
|
46
|
+
}), /*#__PURE__*/React.createElement("span", null, "@"), /*#__PURE__*/React.createElement("span", null, children), /*#__PURE__*/React.createElement(ParticipantPopover, {
|
|
47
|
+
searchText: searchText,
|
|
48
|
+
editor: editor
|
|
49
|
+
}));
|
|
50
|
+
};
|
|
51
|
+
export { renderMention, renderMentionTemporaryInput };
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { Editor, Transforms } from '@seafile/slate';
|
|
3
|
+
import EventBus from '../../../../utils/event-bus';
|
|
4
|
+
import { focusEditor } from '../../../core';
|
|
5
|
+
import { useCollaborators } from '../../../../../hooks';
|
|
6
|
+
import { useParticipantsContext } from '../../../../comment/hooks/use-participants';
|
|
7
|
+
import { searchCollaborators } from '../../../../comment/utils';
|
|
8
|
+
import { INTERNAL_EVENT } from '../../../../constants';
|
|
9
|
+
import { DOWN, FONT_SIZE_WIDTH, LINE_HEIGHT, POPOVER_ADDING_HEIGHT, UP } from '../../../../comment/constants';
|
|
10
|
+
import { eventStopPropagation } from '../../../../utils/mouse-event';
|
|
11
|
+
import { getSelectionCoords } from '../../../../../utils';
|
|
12
|
+
import { KeyCodes } from '../../../../../constants';
|
|
13
|
+
import CommentParticipantItem from './comment-participant-item';
|
|
14
|
+
import { getMentionTempIptEntry, insertMention, sortCollaborators, transformToText } from '../helper';
|
|
15
|
+
import { ElementPopover } from '../../../commons';
|
|
16
|
+
import './index.css';
|
|
17
|
+
const ParticipantPopover = _ref => {
|
|
18
|
+
let {
|
|
19
|
+
editor,
|
|
20
|
+
searchText
|
|
21
|
+
} = _ref;
|
|
22
|
+
const collaboratorsPopoverRef = useRef(null);
|
|
23
|
+
const {
|
|
24
|
+
collaborators
|
|
25
|
+
} = useCollaborators();
|
|
26
|
+
const {
|
|
27
|
+
addParticipants,
|
|
28
|
+
participants
|
|
29
|
+
} = useParticipantsContext();
|
|
30
|
+
const [searchedCollaborators, setSearchedCollaborators] = useState([]);
|
|
31
|
+
const [activeCollaboratorIndex, setActiveCollaboratorIndex] = useState(-1);
|
|
32
|
+
const [validCollaborators, setValidCollaborators] = useState([]);
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
const sortedCollaborators = sortCollaborators(collaborators, participants);
|
|
35
|
+
setValidCollaborators(sortedCollaborators);
|
|
36
|
+
}, [collaborators, participants]);
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
return () => {
|
|
39
|
+
transformToText(editor);
|
|
40
|
+
};
|
|
41
|
+
}, [editor]);
|
|
42
|
+
const hideCommentPopover = useCallback(() => {
|
|
43
|
+
if (searchedCollaborators.length === 0) return;
|
|
44
|
+
setSearchedCollaborators([]);
|
|
45
|
+
setActiveCollaboratorIndex(-1);
|
|
46
|
+
}, [searchedCollaborators]);
|
|
47
|
+
const handleForceClickPopover = useCallback(event => {
|
|
48
|
+
var _collaboratorsPopover;
|
|
49
|
+
if (!((_collaboratorsPopover = collaboratorsPopoverRef.current) === null || _collaboratorsPopover === void 0 ? void 0 : _collaboratorsPopover.contains(event.target))) {
|
|
50
|
+
transformToText(editor);
|
|
51
|
+
}
|
|
52
|
+
}, [editor]);
|
|
53
|
+
|
|
54
|
+
// onMount: handleCommentPopover
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
document.addEventListener('mousedown', handleForceClickPopover);
|
|
57
|
+
return () => {
|
|
58
|
+
document.removeEventListener('mousedown', handleForceClickPopover);
|
|
59
|
+
};
|
|
60
|
+
}, [handleForceClickPopover]);
|
|
61
|
+
const setScrollTop = useCallback((offsetTop, itemOffsetHeight, mouseDownType) => {
|
|
62
|
+
const {
|
|
63
|
+
offsetHeight,
|
|
64
|
+
scrollTop
|
|
65
|
+
} = collaboratorsPopoverRef.current;
|
|
66
|
+
if (mouseDownType === DOWN) {
|
|
67
|
+
if (offsetTop + itemOffsetHeight - scrollTop - offsetHeight + POPOVER_ADDING_HEIGHT > 0) {
|
|
68
|
+
let top = offsetTop + itemOffsetHeight - offsetHeight + POPOVER_ADDING_HEIGHT;
|
|
69
|
+
collaboratorsPopoverRef.current.scrollTop = top;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (mouseDownType === UP) {
|
|
73
|
+
if (offsetTop < scrollTop) {
|
|
74
|
+
collaboratorsPopoverRef.current.scrollTop = offsetTop - POPOVER_ADDING_HEIGHT;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}, []);
|
|
78
|
+
const setCollaboratorsPopoverPosition = useCallback(caretPosition => {
|
|
79
|
+
if (!collaboratorsPopoverRef.current) return;
|
|
80
|
+
const {
|
|
81
|
+
height,
|
|
82
|
+
width
|
|
83
|
+
} = collaboratorsPopoverRef.current.getBoundingClientRect();
|
|
84
|
+
const {
|
|
85
|
+
offsetHeight
|
|
86
|
+
} = collaboratorsPopoverRef.current;
|
|
87
|
+
|
|
88
|
+
// Whether the vertical direction exceeds the screen
|
|
89
|
+
const isVerticalDirectionBeyondScreen = height + caretPosition.y + LINE_HEIGHT > window.innerHeight;
|
|
90
|
+
|
|
91
|
+
// if the vertical direction exceeds the screen, collaboratorsPopoverRef appear above the cursor
|
|
92
|
+
const top = isVerticalDirectionBeyondScreen ? "".concat(caretPosition.y - offsetHeight + LINE_HEIGHT, "px") : "".concat(caretPosition.y + LINE_HEIGHT, "px");
|
|
93
|
+
collaboratorsPopoverRef.current.style.top = top;
|
|
94
|
+
|
|
95
|
+
// Whether the horizontal direction exceeds the screen
|
|
96
|
+
const isHorizontalDirectionBeyondScreen = caretPosition.x + FONT_SIZE_WIDTH + width > window.innerWidth;
|
|
97
|
+
|
|
98
|
+
// if the horizontal direction exceeds the screen, collaboratorsPopoverRef is displayed against the right side of the screen
|
|
99
|
+
const left = isHorizontalDirectionBeyondScreen ? "".concat(window.innerWidth - width, "px") : "".concat(caretPosition.x + FONT_SIZE_WIDTH, "px");
|
|
100
|
+
collaboratorsPopoverRef.current.style.left = left;
|
|
101
|
+
}, [collaboratorsPopoverRef]);
|
|
102
|
+
const getSearchedCollaborators = useCallback(searchingText => {
|
|
103
|
+
if (!searchingText.length) return validCollaborators;
|
|
104
|
+
if (searchingText) return searchCollaborators(validCollaborators, searchingText);
|
|
105
|
+
return [];
|
|
106
|
+
}, [validCollaborators]);
|
|
107
|
+
const handleInvolvedKeyUp = useCallback(() => {
|
|
108
|
+
const searchedCollaborators = getSearchedCollaborators(searchText);
|
|
109
|
+
if (searchedCollaborators.length === 0) {
|
|
110
|
+
hideCommentPopover();
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
setActiveCollaboratorIndex(0);
|
|
114
|
+
setSearchedCollaborators(searchedCollaborators);
|
|
115
|
+
setTimeout(() => {
|
|
116
|
+
const caretPosition = getSelectionCoords();
|
|
117
|
+
setCollaboratorsPopoverPosition(caretPosition);
|
|
118
|
+
}, 1);
|
|
119
|
+
}, [getSearchedCollaborators, searchText, hideCommentPopover, setCollaboratorsPopoverPosition]);
|
|
120
|
+
const handleSelectingCollaborator = useCallback((event, direction) => {
|
|
121
|
+
eventStopPropagation(event);
|
|
122
|
+
const collaboratorsLen = searchedCollaborators.length;
|
|
123
|
+
if (collaboratorsLen === 0) return;
|
|
124
|
+
let nextActiveCollaboratorIndex = activeCollaboratorIndex;
|
|
125
|
+
if (direction === DOWN) {
|
|
126
|
+
nextActiveCollaboratorIndex++;
|
|
127
|
+
if (nextActiveCollaboratorIndex >= collaboratorsLen) {
|
|
128
|
+
nextActiveCollaboratorIndex = 0;
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
nextActiveCollaboratorIndex--;
|
|
132
|
+
if (nextActiveCollaboratorIndex < 0) {
|
|
133
|
+
nextActiveCollaboratorIndex = collaboratorsLen - 1;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
setActiveCollaboratorIndex(nextActiveCollaboratorIndex);
|
|
137
|
+
}, [searchedCollaborators, activeCollaboratorIndex]);
|
|
138
|
+
const onSelectCollaborator = useCallback(collaborator => {
|
|
139
|
+
const [node, path] = getMentionTempIptEntry(editor);
|
|
140
|
+
insertMention(editor, collaborator);
|
|
141
|
+
addParticipants(collaborator.username);
|
|
142
|
+
Transforms.removeNodes(editor, {
|
|
143
|
+
at: path
|
|
144
|
+
});
|
|
145
|
+
const focusPath = Editor.next(editor, {
|
|
146
|
+
at: path
|
|
147
|
+
})[1];
|
|
148
|
+
focusEditor(editor, Editor.start(editor, focusPath));
|
|
149
|
+
hideCommentPopover();
|
|
150
|
+
}, [editor, hideCommentPopover, addParticipants]);
|
|
151
|
+
const handleSelectCollaborator = useCallback(event => {
|
|
152
|
+
if (searchedCollaborators.length === 0) return;
|
|
153
|
+
onSelectCollaborator(searchedCollaborators[activeCollaboratorIndex]);
|
|
154
|
+
}, [searchedCollaborators, activeCollaboratorIndex, onSelectCollaborator]);
|
|
155
|
+
const handleMentionChosen = useCallback(_ref2 => {
|
|
156
|
+
let {
|
|
157
|
+
event
|
|
158
|
+
} = _ref2;
|
|
159
|
+
if (event.keyCode === KeyCodes.DownArrow) {
|
|
160
|
+
handleSelectingCollaborator(event, DOWN);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (event.keyCode === KeyCodes.UpArrow) {
|
|
164
|
+
handleSelectingCollaborator(event, UP);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
if (event.keyCode === KeyCodes.Enter) {
|
|
168
|
+
if (collaboratorsPopoverRef.current) {
|
|
169
|
+
handleSelectCollaborator();
|
|
170
|
+
event.preventDefault();
|
|
171
|
+
} else {
|
|
172
|
+
transformToText(editor);
|
|
173
|
+
}
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
if (event.keyCode === KeyCodes.Esc) {
|
|
177
|
+
transformToText(editor);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
handleInvolvedKeyUp(event);
|
|
181
|
+
}, [editor, handleInvolvedKeyUp, handleSelectCollaborator, handleSelectingCollaborator]);
|
|
182
|
+
const handleSearchTextChange = useCallback(() => {
|
|
183
|
+
const newCollaborators = getSearchedCollaborators(searchText);
|
|
184
|
+
if (newCollaborators.length === 0) {
|
|
185
|
+
hideCommentPopover();
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
setSearchedCollaborators(newCollaborators);
|
|
189
|
+
setTimeout(() => {
|
|
190
|
+
const caretPosition = getSelectionCoords();
|
|
191
|
+
setCollaboratorsPopoverPosition(caretPosition);
|
|
192
|
+
}, 1);
|
|
193
|
+
}, [getSearchedCollaborators, hideCommentPopover, searchText, setCollaboratorsPopoverPosition]);
|
|
194
|
+
useEffect(() => {
|
|
195
|
+
handleSearchTextChange();
|
|
196
|
+
setActiveCollaboratorIndex(0);
|
|
197
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
198
|
+
}, [searchText, validCollaborators]);
|
|
199
|
+
useEffect(() => {
|
|
200
|
+
const eventBus = EventBus.getInstance();
|
|
201
|
+
const unsubscribe = eventBus.subscribe(INTERNAL_EVENT.HANDLE_MENTION_TEMP_CHOSEN, handleMentionChosen);
|
|
202
|
+
return () => {
|
|
203
|
+
unsubscribe();
|
|
204
|
+
};
|
|
205
|
+
}, [handleMentionChosen, searchText, validCollaborators]);
|
|
206
|
+
if (searchedCollaborators.length === 0) return null;
|
|
207
|
+
return /*#__PURE__*/React.createElement(ElementPopover, null, /*#__PURE__*/React.createElement("div", {
|
|
208
|
+
className: "sdoc-comment-caret-list",
|
|
209
|
+
ref: collaboratorsPopoverRef
|
|
210
|
+
}, searchedCollaborators.map((participant, index) => /*#__PURE__*/React.createElement(CommentParticipantItem, {
|
|
211
|
+
key: participant.username,
|
|
212
|
+
participantIndex: index,
|
|
213
|
+
activeParticipantIndex: activeCollaboratorIndex,
|
|
214
|
+
participant: participant,
|
|
215
|
+
setScrollTop: setScrollTop,
|
|
216
|
+
onSelectParticipant: onSelectCollaborator
|
|
217
|
+
}))));
|
|
218
|
+
};
|
|
219
|
+
export default ParticipantPopover;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Node, Text } from '@seafile/slate';
|
|
2
|
+
export const isEmptyNode = node => {
|
|
3
|
+
const nodeChildren = node.children;
|
|
4
|
+
const isSingleChild = nodeChildren.length === 1;
|
|
5
|
+
const firstChild = nodeChildren[0];
|
|
6
|
+
const isText = Text.isText(firstChild);
|
|
7
|
+
const isEmptyContent = Node.string(firstChild) === '';
|
|
8
|
+
let isEmpty = isSingleChild && isText && isEmptyContent;
|
|
9
|
+
return isEmpty;
|
|
10
|
+
};
|
|
@@ -3,6 +3,8 @@ import React from 'react';
|
|
|
3
3
|
import { Node } from '@seafile/slate';
|
|
4
4
|
import { useSlateStatic } from '@seafile/slate-react';
|
|
5
5
|
import { Placeholder } from '../../core';
|
|
6
|
+
import { isEmptyNode } from './helper';
|
|
7
|
+
const PLACEHOLDER = 'Please_enter_text';
|
|
6
8
|
const Paragraph = _ref => {
|
|
7
9
|
let {
|
|
8
10
|
isComposing,
|
|
@@ -11,12 +13,16 @@ const Paragraph = _ref => {
|
|
|
11
13
|
children
|
|
12
14
|
} = _ref;
|
|
13
15
|
const {
|
|
14
|
-
indent
|
|
16
|
+
indent,
|
|
17
|
+
placeholder = PLACEHOLDER
|
|
15
18
|
} = element;
|
|
16
19
|
const editor = useSlateStatic();
|
|
17
20
|
let isShowPlaceHolder = false;
|
|
18
21
|
if (editor.children.length === 1) {
|
|
19
|
-
|
|
22
|
+
const node = editor.children[0];
|
|
23
|
+
const isChildEmpty = isEmptyNode(node);
|
|
24
|
+
const isParagraphEmpty = Node.string(element) === '';
|
|
25
|
+
isShowPlaceHolder = isChildEmpty && isParagraphEmpty && !isComposing;
|
|
20
26
|
}
|
|
21
27
|
if (editor.children.length === 2 && editor.children[0].type.startsWith('header')) {
|
|
22
28
|
const node = editor.children[1];
|
|
@@ -33,7 +39,7 @@ const Paragraph = _ref => {
|
|
|
33
39
|
position: isShowPlaceHolder ? 'relative' : ''
|
|
34
40
|
}, style)
|
|
35
41
|
}), children, isShowPlaceHolder && /*#__PURE__*/React.createElement(Placeholder, {
|
|
36
|
-
title:
|
|
42
|
+
title: placeholder
|
|
37
43
|
}));
|
|
38
44
|
};
|
|
39
45
|
export const renderParagraph = props => {
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
|
|
2
|
+
import React, { useCallback } from 'react';
|
|
3
|
+
import { withTranslation } from 'react-i18next';
|
|
4
|
+
import { TEXT_STYLE, MENUS_CONFIG_MAP } from '../../../constants';
|
|
5
|
+
import { focusEditor } from '../../../core';
|
|
6
|
+
import { MenuItem } from '../../../commons';
|
|
7
|
+
import { getValue, isMenuDisabled, addMark, removeMark } from '../helpers';
|
|
8
|
+
import { BOLD, ITALIC } from '../../../constants/menus-config';
|
|
9
|
+
const filterFontTypes = _ref => {
|
|
10
|
+
let {
|
|
11
|
+
id
|
|
12
|
+
} = _ref;
|
|
13
|
+
return [BOLD, ITALIC].includes(id);
|
|
14
|
+
};
|
|
15
|
+
const CommentEditorTextStyleMenuList = _ref2 => {
|
|
16
|
+
let {
|
|
17
|
+
editor,
|
|
18
|
+
isRichEditor,
|
|
19
|
+
className,
|
|
20
|
+
idPrefix,
|
|
21
|
+
readonly
|
|
22
|
+
} = _ref2;
|
|
23
|
+
const isActive = useCallback(type => {
|
|
24
|
+
const isMark = getValue(editor, type);
|
|
25
|
+
return !!isMark;
|
|
26
|
+
|
|
27
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
28
|
+
}, [editor]);
|
|
29
|
+
const isDisabled = useCallback(() => {
|
|
30
|
+
return isMenuDisabled(editor, readonly);
|
|
31
|
+
|
|
32
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
33
|
+
}, [editor, readonly]);
|
|
34
|
+
const onMouseDown = useCallback((event, type) => {
|
|
35
|
+
event.preventDefault();
|
|
36
|
+
event.stopPropagation();
|
|
37
|
+
if (isDisabled()) return;
|
|
38
|
+
const active = isActive(type);
|
|
39
|
+
if (active) {
|
|
40
|
+
removeMark(editor, type);
|
|
41
|
+
} else {
|
|
42
|
+
addMark(editor, type);
|
|
43
|
+
}
|
|
44
|
+
focusEditor(editor);
|
|
45
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
46
|
+
}, [editor]);
|
|
47
|
+
const getTextStyleList = useCallback(key => {
|
|
48
|
+
return MENUS_CONFIG_MAP[key].map(item => {
|
|
49
|
+
let itemProps = {
|
|
50
|
+
isRichEditor,
|
|
51
|
+
className,
|
|
52
|
+
disabled: isDisabled(),
|
|
53
|
+
isActive: isActive(item.type),
|
|
54
|
+
onMouseDown: onMouseDown
|
|
55
|
+
};
|
|
56
|
+
return _objectSpread(_objectSpread(_objectSpread({}, itemProps), item), {}, {
|
|
57
|
+
id: idPrefix ? "".concat(idPrefix, "_").concat(item.id) : item.id
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
62
|
+
}, [editor, readonly]);
|
|
63
|
+
const list = getTextStyleList(TEXT_STYLE).filter(filterFontTypes);
|
|
64
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, list.map((itemProps, index) => /*#__PURE__*/React.createElement(MenuItem, Object.assign({
|
|
65
|
+
key: index
|
|
66
|
+
}, itemProps))));
|
|
67
|
+
};
|
|
68
|
+
export default withTranslation('sdoc-editor')(CommentEditorTextStyleMenuList);
|