@seafile/seafile-editor 1.0.5 → 1.0.6-1
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/editors/simple-slate-editor /index.js +4 -2
- package/dist/editors/slate-editor/index.js +4 -2
- package/dist/editors/slate-viewer/index.js +4 -2
- package/dist/extension/highlight/index.js +3 -0
- package/dist/extension/highlight/normalize-tokens.js +87 -0
- package/dist/extension/highlight/prismjs.js +18 -0
- package/dist/extension/highlight/set-node-decorations.js +77 -0
- package/dist/extension/highlight/use-highlight.js +13 -0
- package/dist/extension/index.js +2 -1
- package/dist/extension/plugins/code-block/render-elem/constant.js +13 -13
- package/dist/extension/plugins/image/helper.js +10 -0
- package/dist/extension/plugins/image/menu/image-menu-popover.js +5 -10
- package/dist/extension/plugins/image/plugin.js +14 -1
- package/dist/extension/plugins/list/helpers.js +28 -0
- package/dist/extension/plugins/list/plugin/index.js +8 -1
- package/dist/extension/plugins/list/plugin/shortcut.js +44 -0
- package/dist/slate-convert/md-to-slate/transform.js +4 -2
- package/dist/slate-convert/slate-to-md/transform.js +3 -0
- package/package.json +2 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { useCallback, useState, useMemo, useEffect } from 'react';
|
|
2
2
|
import { Editable, Slate } from 'slate-react';
|
|
3
3
|
import { Editor } from 'slate';
|
|
4
|
-
import { baseEditor, Toolbar, renderElement, renderLeaf } from '../../extension';
|
|
4
|
+
import { baseEditor, Toolbar, renderElement, renderLeaf, useHighlight, SetNodeToDecorations } from '../../extension';
|
|
5
5
|
import EventBus from '../../utils/event-bus';
|
|
6
6
|
import EventProxy from '../../utils/event-handler';
|
|
7
7
|
import withPropsEditor from './with-props-editor';
|
|
@@ -23,6 +23,7 @@ export default function SimpleSlateEditor(_ref) {
|
|
|
23
23
|
const eventProxy = useMemo(() => {
|
|
24
24
|
return new EventProxy(editor);
|
|
25
25
|
}, [editor]);
|
|
26
|
+
const decorate = useHighlight(editor);
|
|
26
27
|
const onChange = useCallback(value => {
|
|
27
28
|
setSlateValue(value);
|
|
28
29
|
const operations = editor.operations;
|
|
@@ -79,7 +80,8 @@ export default function SimpleSlateEditor(_ref) {
|
|
|
79
80
|
className: "sf-slate-article-container"
|
|
80
81
|
}, /*#__PURE__*/React.createElement("div", {
|
|
81
82
|
className: "article"
|
|
82
|
-
}, /*#__PURE__*/React.createElement(Editable, {
|
|
83
|
+
}, /*#__PURE__*/React.createElement(SetNodeToDecorations, null), /*#__PURE__*/React.createElement(Editable, {
|
|
84
|
+
decorate: decorate,
|
|
83
85
|
renderElement: renderElement,
|
|
84
86
|
renderLeaf: renderLeaf,
|
|
85
87
|
onKeyDown: eventProxy.onKeyDown,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { useCallback, useState, useMemo, useEffect, useRef } from 'react';
|
|
2
2
|
import { Editable, Slate } from 'slate-react';
|
|
3
3
|
import { Editor } from 'slate';
|
|
4
|
-
import { baseEditor, Toolbar, renderElement, renderLeaf } from '../../extension';
|
|
4
|
+
import { baseEditor, Toolbar, renderElement, renderLeaf, useHighlight, SetNodeToDecorations } from '../../extension';
|
|
5
5
|
import EventBus from '../../utils/event-bus';
|
|
6
6
|
import EventProxy from '../../utils/event-handler';
|
|
7
7
|
import withPropsEditor from './with-props-editor';
|
|
@@ -30,6 +30,7 @@ export default function SlateEditor(_ref) {
|
|
|
30
30
|
return new EventProxy(editor);
|
|
31
31
|
}, [editor]);
|
|
32
32
|
useSeafileUtils(editor);
|
|
33
|
+
const decorate = useHighlight(editor);
|
|
33
34
|
const onChange = useCallback(value => {
|
|
34
35
|
setSlateValue(value);
|
|
35
36
|
const operations = editor.operations;
|
|
@@ -93,7 +94,8 @@ export default function SlateEditor(_ref) {
|
|
|
93
94
|
className: "sf-slate-article-container"
|
|
94
95
|
}, /*#__PURE__*/React.createElement("div", {
|
|
95
96
|
className: "article"
|
|
96
|
-
}, /*#__PURE__*/React.createElement(Editable, {
|
|
97
|
+
}, /*#__PURE__*/React.createElement(SetNodeToDecorations, null), /*#__PURE__*/React.createElement(Editable, {
|
|
98
|
+
decorate: decorate,
|
|
97
99
|
renderElement: renderElement,
|
|
98
100
|
renderLeaf: renderLeaf,
|
|
99
101
|
onKeyDown: eventProxy.onKeyDown,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useEffect, useRef } from 'react';
|
|
2
|
-
import { baseEditor, renderElement, renderLeaf } from '../../extension';
|
|
2
|
+
import { SetNodeToDecorations, baseEditor, renderElement, renderLeaf, useHighlight } from '../../extension';
|
|
3
3
|
import { Editable, Slate } from 'slate-react';
|
|
4
4
|
import Outline from '../../containers/outline';
|
|
5
5
|
import { ScrollContext } from '../../hooks/use-scroll-context';
|
|
@@ -12,6 +12,7 @@ export default function SlateViewer(_ref) {
|
|
|
12
12
|
} = _ref;
|
|
13
13
|
const scrollRef = useRef(null);
|
|
14
14
|
const containerScrollRef = externalScrollRef ? externalScrollRef : scrollRef;
|
|
15
|
+
const decorate = useHighlight(baseEditor);
|
|
15
16
|
|
|
16
17
|
// willUnmount
|
|
17
18
|
useEffect(() => {
|
|
@@ -33,8 +34,9 @@ export default function SlateViewer(_ref) {
|
|
|
33
34
|
className: "sf-slate-viewer-article-container"
|
|
34
35
|
}, /*#__PURE__*/React.createElement("div", {
|
|
35
36
|
className: "article"
|
|
36
|
-
}, /*#__PURE__*/React.createElement(Editable, {
|
|
37
|
+
}, /*#__PURE__*/React.createElement(SetNodeToDecorations, null), /*#__PURE__*/React.createElement(Editable, {
|
|
37
38
|
readOnly: true,
|
|
39
|
+
decorate: decorate,
|
|
38
40
|
renderElement: renderElement,
|
|
39
41
|
renderLeaf: renderLeaf
|
|
40
42
|
}))), isShowOutline && /*#__PURE__*/React.createElement("div", {
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
const newlineRe = /\r\n|\r|\n/;
|
|
2
|
+
const normalizeEmptyLines = line => {
|
|
3
|
+
if (line.length === 0) {
|
|
4
|
+
line.push({
|
|
5
|
+
types: ['plain'],
|
|
6
|
+
content: '\n',
|
|
7
|
+
empty: true
|
|
8
|
+
});
|
|
9
|
+
} else if (line.length === 1 && line[0].content === '') {
|
|
10
|
+
line[0].content = '\n';
|
|
11
|
+
line[0].empty = true;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
const appendTypes = (types, add) => {
|
|
15
|
+
const typesSize = types.length;
|
|
16
|
+
if (typesSize > 0 && types[typesSize - 1] === add) {
|
|
17
|
+
return types;
|
|
18
|
+
}
|
|
19
|
+
return types.concat(add);
|
|
20
|
+
};
|
|
21
|
+
export const normalizeTokens = tokens => {
|
|
22
|
+
const typeArrStack = [[]];
|
|
23
|
+
const tokenArrStack = [tokens];
|
|
24
|
+
const tokenArrIndexStack = [0];
|
|
25
|
+
const tokenArrSizeStack = [tokens.length];
|
|
26
|
+
let i = 0;
|
|
27
|
+
let stackIndex = 0;
|
|
28
|
+
let currentLine = [];
|
|
29
|
+
const acc = [currentLine];
|
|
30
|
+
while (stackIndex > -1) {
|
|
31
|
+
while ((i = tokenArrIndexStack[stackIndex]++) < tokenArrSizeStack[stackIndex]) {
|
|
32
|
+
let content;
|
|
33
|
+
let types = typeArrStack[stackIndex];
|
|
34
|
+
const tokenArr = tokenArrStack[stackIndex];
|
|
35
|
+
const token = tokenArr[i];
|
|
36
|
+
|
|
37
|
+
// Determine content and append type to types if necessary
|
|
38
|
+
if (typeof token === 'string') {
|
|
39
|
+
types = stackIndex > 0 ? types : ['plain'];
|
|
40
|
+
content = token;
|
|
41
|
+
} else {
|
|
42
|
+
types = appendTypes(types, token.type);
|
|
43
|
+
if (token.alias) {
|
|
44
|
+
types = appendTypes(types, token.alias);
|
|
45
|
+
}
|
|
46
|
+
content = token.content;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// If token.content is an array, increase the stack depth and repeat this while-loop
|
|
50
|
+
if (typeof content !== 'string') {
|
|
51
|
+
stackIndex++;
|
|
52
|
+
typeArrStack.push(types);
|
|
53
|
+
tokenArrStack.push(content);
|
|
54
|
+
tokenArrIndexStack.push(0);
|
|
55
|
+
tokenArrSizeStack.push(content.length);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Split by newlines
|
|
60
|
+
const splitByNewlines = content.split(newlineRe);
|
|
61
|
+
const newlineCount = splitByNewlines.length;
|
|
62
|
+
currentLine.push({
|
|
63
|
+
types,
|
|
64
|
+
content: splitByNewlines[0]
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Create a new line for each string on a new line
|
|
68
|
+
for (let i = 1; i < newlineCount; i++) {
|
|
69
|
+
normalizeEmptyLines(currentLine);
|
|
70
|
+
acc.push(currentLine = []);
|
|
71
|
+
currentLine.push({
|
|
72
|
+
types,
|
|
73
|
+
content: splitByNewlines[i]
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Decreate the stack depth
|
|
79
|
+
stackIndex--;
|
|
80
|
+
typeArrStack.pop();
|
|
81
|
+
tokenArrStack.pop();
|
|
82
|
+
tokenArrIndexStack.pop();
|
|
83
|
+
tokenArrSizeStack.pop();
|
|
84
|
+
}
|
|
85
|
+
normalizeEmptyLines(currentLine);
|
|
86
|
+
return acc;
|
|
87
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import Prism from 'prismjs';
|
|
2
|
+
import 'prismjs/themes/prism.css';
|
|
3
|
+
import 'prismjs/components/prism-javascript';
|
|
4
|
+
import 'prismjs/components/prism-typescript';
|
|
5
|
+
import 'prismjs/components/prism-markup';
|
|
6
|
+
import 'prismjs/components/prism-go';
|
|
7
|
+
import 'prismjs/components/prism-php';
|
|
8
|
+
import 'prismjs/components/prism-c';
|
|
9
|
+
import 'prismjs/components/prism-python';
|
|
10
|
+
import 'prismjs/components/prism-java';
|
|
11
|
+
import 'prismjs/components/prism-cpp';
|
|
12
|
+
import 'prismjs/components/prism-csharp';
|
|
13
|
+
import 'prismjs/components/prism-sql';
|
|
14
|
+
import 'prismjs/components/prism-ruby';
|
|
15
|
+
import 'prismjs/components/prism-swift';
|
|
16
|
+
import 'prismjs/components/prism-bash';
|
|
17
|
+
import 'prismjs/components/prism-lua';
|
|
18
|
+
export default Prism;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Editor, Node, Element } from 'slate';
|
|
2
|
+
import { useSlate } from 'slate-react';
|
|
3
|
+
import { CODE_BLOCK } from '../constants/element-types';
|
|
4
|
+
import Prism from './prismjs';
|
|
5
|
+
import { normalizeTokens } from './normalize-tokens';
|
|
6
|
+
const mergeMaps = function () {
|
|
7
|
+
const map = new Map();
|
|
8
|
+
for (var _len = arguments.length, maps = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
9
|
+
maps[_key] = arguments[_key];
|
|
10
|
+
}
|
|
11
|
+
for (const m of maps) {
|
|
12
|
+
for (const item of m) {
|
|
13
|
+
map.set(...item);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return map;
|
|
17
|
+
};
|
|
18
|
+
const getChildNodeToDecorations = _ref => {
|
|
19
|
+
let [block, blockPath] = _ref;
|
|
20
|
+
const nodeToDecorations = new Map();
|
|
21
|
+
const text = block.children.map(line => Node.string(line)).join('\n');
|
|
22
|
+
console.log(block);
|
|
23
|
+
const language = block.lang || 'text';
|
|
24
|
+
console.log(text);
|
|
25
|
+
console.log(language);
|
|
26
|
+
console.log(Prism.languages);
|
|
27
|
+
const tokens = Prism.tokenize(text, Prism.languages[language]);
|
|
28
|
+
const normalizedTokens = normalizeTokens(tokens); // make tokens flat and grouped by line
|
|
29
|
+
const blockChildren = block.children;
|
|
30
|
+
for (let index = 0; index < normalizedTokens.length; index++) {
|
|
31
|
+
const tokens = normalizedTokens[index];
|
|
32
|
+
const element = blockChildren[index];
|
|
33
|
+
if (element) {
|
|
34
|
+
if (!nodeToDecorations.has(element)) {
|
|
35
|
+
nodeToDecorations.set(element, []);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
let start = 0;
|
|
39
|
+
for (const token of tokens) {
|
|
40
|
+
const length = token.content.length;
|
|
41
|
+
if (!length) {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
const end = start + length;
|
|
45
|
+
const path = [...blockPath, index, 0];
|
|
46
|
+
const range = {
|
|
47
|
+
anchor: {
|
|
48
|
+
path,
|
|
49
|
+
offset: start
|
|
50
|
+
},
|
|
51
|
+
focus: {
|
|
52
|
+
path,
|
|
53
|
+
offset: end
|
|
54
|
+
},
|
|
55
|
+
token: true,
|
|
56
|
+
...Object.fromEntries(token.types.map(type => [type, true]))
|
|
57
|
+
};
|
|
58
|
+
if (nodeToDecorations.get(element)) {
|
|
59
|
+
nodeToDecorations.get(element).push(range);
|
|
60
|
+
}
|
|
61
|
+
start = end;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return nodeToDecorations;
|
|
65
|
+
};
|
|
66
|
+
const SetNodeToDecorations = () => {
|
|
67
|
+
const editor = useSlate();
|
|
68
|
+
const blockEntries = Array.from(Editor.nodes(editor, {
|
|
69
|
+
at: [],
|
|
70
|
+
mode: 'highest',
|
|
71
|
+
match: n => Element.isElement(n) && n.type === CODE_BLOCK
|
|
72
|
+
}));
|
|
73
|
+
const nodeToDecorations = mergeMaps(...blockEntries.map(getChildNodeToDecorations));
|
|
74
|
+
editor.nodeToDecorations = nodeToDecorations;
|
|
75
|
+
return null;
|
|
76
|
+
};
|
|
77
|
+
export default SetNodeToDecorations;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Element } from 'slate';
|
|
2
|
+
import { CODE_LINE } from '../constants/element-types';
|
|
3
|
+
export const useHighlight = editor => _ref => {
|
|
4
|
+
let [node, path] = _ref;
|
|
5
|
+
let ranges = [];
|
|
6
|
+
if (Element.isElement(node) && node.type === CODE_LINE) {
|
|
7
|
+
var _editor$nodeToDecorat;
|
|
8
|
+
ranges = (editor === null || editor === void 0 ? void 0 : (_editor$nodeToDecorat = editor.nodeToDecorations) === null || _editor$nodeToDecorat === void 0 ? void 0 : _editor$nodeToDecorat.get(node)) || [];
|
|
9
|
+
return ranges;
|
|
10
|
+
}
|
|
11
|
+
return ranges;
|
|
12
|
+
};
|
|
13
|
+
export default useHighlight;
|
package/dist/extension/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { ELementTypes } from './constants';
|
|
2
2
|
import { isEmptyParagraph } from './core';
|
|
3
|
+
import { useHighlight, SetNodeToDecorations } from './highlight';
|
|
3
4
|
import renderElement from './render/render-element';
|
|
4
5
|
import renderLeaf from './render/render-leaf';
|
|
5
6
|
import { Toolbar } from './toolbar';
|
|
6
7
|
import baseEditor from './editor';
|
|
7
|
-
export { ELementTypes, isEmptyParagraph, renderElement, renderLeaf, Toolbar, baseEditor };
|
|
8
|
+
export { ELementTypes, isEmptyParagraph, renderElement, renderLeaf, Toolbar, baseEditor, useHighlight, SetNodeToDecorations };
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
// Default language key name
|
|
2
|
-
export const EXPLAIN_TEXT = '
|
|
2
|
+
export const EXPLAIN_TEXT = 'text';
|
|
3
3
|
export const LANGUAGE_MAP = {
|
|
4
|
-
[EXPLAIN_TEXT]: '
|
|
5
|
-
html: '
|
|
6
|
-
css: '
|
|
7
|
-
javascript: '
|
|
8
|
-
c: '
|
|
9
|
-
cpp: '
|
|
10
|
-
csharp: '
|
|
11
|
-
java: '
|
|
12
|
-
python: '
|
|
13
|
-
sql: '
|
|
14
|
-
swift: '
|
|
15
|
-
json: '
|
|
4
|
+
[EXPLAIN_TEXT]: 'Text',
|
|
5
|
+
html: 'HTML',
|
|
6
|
+
css: 'CSS',
|
|
7
|
+
javascript: 'Javascript',
|
|
8
|
+
c: 'C',
|
|
9
|
+
cpp: 'C++',
|
|
10
|
+
csharp: 'C#',
|
|
11
|
+
java: 'Java',
|
|
12
|
+
python: 'Python',
|
|
13
|
+
sql: 'SQL',
|
|
14
|
+
swift: 'Swift',
|
|
15
|
+
json: 'JSON'
|
|
16
16
|
};
|
|
@@ -76,4 +76,14 @@ export const getImagesUrlList = nodes => {
|
|
|
76
76
|
nodeIndex++;
|
|
77
77
|
}
|
|
78
78
|
return list;
|
|
79
|
+
};
|
|
80
|
+
export const handleUpdateImage = async (editor, file) => {
|
|
81
|
+
if (editor.api.uploadLocalImage) {
|
|
82
|
+
try {
|
|
83
|
+
const imgUrl = await editor.api.uploadLocalImage(file);
|
|
84
|
+
insertImage(editor, imgUrl);
|
|
85
|
+
} catch (error) {
|
|
86
|
+
console.log('error', error);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
79
89
|
};
|
|
@@ -3,7 +3,8 @@ import { useTranslation } from 'react-i18next';
|
|
|
3
3
|
import ImageMenuInsertInternetDialog from './image-menu-dialog';
|
|
4
4
|
import EventBus from '../../../../utils/event-bus';
|
|
5
5
|
import { EXTERNAL_EVENTS } from '../../../../constants/event-types';
|
|
6
|
-
import {
|
|
6
|
+
import { handleUpdateImage } from '../helper';
|
|
7
|
+
import { focusEditor } from '../../../core';
|
|
7
8
|
import './style.css';
|
|
8
9
|
const ImageMenuPopover = _ref => {
|
|
9
10
|
let {
|
|
@@ -26,16 +27,10 @@ const ImageMenuPopover = _ref => {
|
|
|
26
27
|
e.nativeEvent.stopImmediatePropagation();
|
|
27
28
|
}, []);
|
|
28
29
|
const handleUploadLocalImage = useCallback(async e => {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
try {
|
|
32
|
-
const imgUrl = await editor.api.uploadLocalImage(file);
|
|
33
|
-
insertImage(editor, imgUrl);
|
|
34
|
-
} catch (error) {
|
|
35
|
-
console.log('error', error);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
30
|
+
const file = e.target.files[0];
|
|
31
|
+
handleUpdateImage(editor, file);
|
|
38
32
|
handelClosePopover();
|
|
33
|
+
focusEditor(editor);
|
|
39
34
|
}, [editor, handelClosePopover]);
|
|
40
35
|
const onToggleImageDialog = useCallback(() => {
|
|
41
36
|
setIsShowInternetImageModal(false);
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { ELementTypes } from '../../constants';
|
|
2
|
+
import { IMAGE } from '../../constants/element-types';
|
|
3
|
+
import { focusEditor } from '../../core';
|
|
4
|
+
import { handleUpdateImage } from './helper';
|
|
2
5
|
const withImages = editor => {
|
|
3
6
|
const {
|
|
4
7
|
isInline,
|
|
5
|
-
isVoid
|
|
8
|
+
isVoid,
|
|
9
|
+
insertData
|
|
6
10
|
} = editor;
|
|
7
11
|
const newEditor = editor;
|
|
8
12
|
newEditor.isInline = element => {
|
|
@@ -19,6 +23,15 @@ const withImages = editor => {
|
|
|
19
23
|
if (type === ELementTypes.IMAGE) return true;
|
|
20
24
|
return isVoid(element);
|
|
21
25
|
};
|
|
26
|
+
newEditor.insertData = data => {
|
|
27
|
+
if (data.types && data.types.includes('Files') && data.files[0].type.includes(IMAGE)) {
|
|
28
|
+
const file = data.files[0];
|
|
29
|
+
handleUpdateImage(newEditor, file);
|
|
30
|
+
focusEditor(newEditor);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
return insertData(data);
|
|
34
|
+
};
|
|
22
35
|
return newEditor;
|
|
23
36
|
};
|
|
24
37
|
export default withImages;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Editor, Element, Node, Range, Text } from 'slate';
|
|
2
2
|
import { CHECK_LIST_ITEM, CODE_BLOCK, CODE_LINE, LIST_ITEM, TABLE } from '../../constants/element-types';
|
|
3
3
|
import { LIST_TYPES } from './constant';
|
|
4
|
+
import { transformsToList } from './transforms';
|
|
4
5
|
export const isMenuDisabled = (editor, readonly) => {
|
|
5
6
|
if (readonly || !editor.selection) return true;
|
|
6
7
|
// Match disable node
|
|
@@ -61,4 +62,31 @@ export const getActiveListType = editor => {
|
|
|
61
62
|
}
|
|
62
63
|
}
|
|
63
64
|
return selectedListNodeEntry && selectedListNodeEntry[0].type;
|
|
65
|
+
};
|
|
66
|
+
export const setListType = (editor, type) => {
|
|
67
|
+
transformsToList(editor, type);
|
|
68
|
+
};
|
|
69
|
+
export const getBeforeText = editor => {
|
|
70
|
+
const {
|
|
71
|
+
selection
|
|
72
|
+
} = editor;
|
|
73
|
+
if (selection == null) return {
|
|
74
|
+
beforeText: '',
|
|
75
|
+
range: null
|
|
76
|
+
};
|
|
77
|
+
const {
|
|
78
|
+
anchor
|
|
79
|
+
} = selection;
|
|
80
|
+
// Find the near text node above the current text
|
|
81
|
+
const [, aboveNodePath] = Editor.above(editor);
|
|
82
|
+
const aboveNodeStartPoint = Editor.start(editor, aboveNodePath); // The starting position of the text node
|
|
83
|
+
const range = {
|
|
84
|
+
anchor,
|
|
85
|
+
focus: aboveNodeStartPoint
|
|
86
|
+
};
|
|
87
|
+
const beforeText = Editor.string(editor, range) || '';
|
|
88
|
+
return {
|
|
89
|
+
beforeText,
|
|
90
|
+
range
|
|
91
|
+
};
|
|
64
92
|
};
|
|
@@ -5,11 +5,13 @@ import { insertBreakList } from './insert-break-list';
|
|
|
5
5
|
import { insertFragmentList } from './insert-fragment-list';
|
|
6
6
|
import { normalizeList } from './normalize-list';
|
|
7
7
|
import { LIST_TYPES } from '../constant';
|
|
8
|
+
import { handleShortcut } from './shortcut';
|
|
8
9
|
const withList = editor => {
|
|
9
10
|
const {
|
|
10
11
|
insertBreak,
|
|
11
12
|
onHotKeyDown,
|
|
12
|
-
deleteBackWord
|
|
13
|
+
deleteBackWord,
|
|
14
|
+
insertText
|
|
13
15
|
} = editor;
|
|
14
16
|
const newEditor = editor;
|
|
15
17
|
newEditor.insertBreak = () => {
|
|
@@ -28,6 +30,11 @@ const withList = editor => {
|
|
|
28
30
|
// nothing todo
|
|
29
31
|
deleteBackWord(unit);
|
|
30
32
|
};
|
|
33
|
+
newEditor.insertText = text => {
|
|
34
|
+
const isPreventInsert = handleShortcut(newEditor, text);
|
|
35
|
+
if (isPreventInsert) return;
|
|
36
|
+
return insertText(text);
|
|
37
|
+
};
|
|
31
38
|
newEditor.onHotKeyDown = event => {
|
|
32
39
|
const activeListType = getActiveListType(editor);
|
|
33
40
|
const isListActive = LIST_TYPES.includes(activeListType);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Editor, Range, Transforms } from 'slate';
|
|
2
|
+
import { getBeforeText, setListType } from '../helpers';
|
|
3
|
+
import { ORDERED_LIST, PARAGRAPH } from '../../../constants/element-types';
|
|
4
|
+
import { focusEditor, getPreviousPath } from '../../../core';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @param {Editor} editor
|
|
8
|
+
* @param {String} text
|
|
9
|
+
* @returns {Boolean} isPreventInsert
|
|
10
|
+
*/
|
|
11
|
+
export const handleShortcut = (editor, text) => {
|
|
12
|
+
if (text !== ' ') return false;
|
|
13
|
+
const {
|
|
14
|
+
selection
|
|
15
|
+
} = editor;
|
|
16
|
+
if (!Range.isCollapsed(selection)) return false;
|
|
17
|
+
let [aboveNode, aboveNodePath] = Editor.above(editor);
|
|
18
|
+
if (aboveNode.type !== PARAGRAPH) return false;
|
|
19
|
+
// Match ordered list shortcut
|
|
20
|
+
const regShortcut = /^\s*[1]+\.\s*$/;
|
|
21
|
+
const {
|
|
22
|
+
beforeText,
|
|
23
|
+
range
|
|
24
|
+
} = getBeforeText(editor);
|
|
25
|
+
const matchResult = beforeText.match(regShortcut);
|
|
26
|
+
const matchedText = matchResult && matchResult[0];
|
|
27
|
+
// If the match fails or the match is not at the beginning of the line, it is not a shortcut
|
|
28
|
+
if (!matchResult || matchResult.index !== 0) return false;
|
|
29
|
+
const previousNodePath = getPreviousPath(aboveNodePath);
|
|
30
|
+
const [previousNode] = Editor.node(editor, previousNodePath);
|
|
31
|
+
// If the previous node is not an ordered list and is not start with `1.`,it is not a shortcut
|
|
32
|
+
if (previousNode.type !== ORDERED_LIST && matchedText !== '1.') return false;
|
|
33
|
+
// If the previous node is not an ordered list and is start with `1.`,transforms to ordered list
|
|
34
|
+
if (previousNode.type !== ORDERED_LIST && matchedText === '1.') {
|
|
35
|
+
// Delete shortcut key text
|
|
36
|
+
Transforms.delete(editor, {
|
|
37
|
+
at: range
|
|
38
|
+
});
|
|
39
|
+
setListType(editor, ORDERED_LIST);
|
|
40
|
+
focusEditor(editor);
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
};
|
|
@@ -152,7 +152,9 @@ export const transformListItem = node => {
|
|
|
152
152
|
children: children.map(child => {
|
|
153
153
|
if (child.type === PARAGRAPH) {
|
|
154
154
|
return transformListContent(child);
|
|
155
|
-
} else {
|
|
155
|
+
} else if (child.type === 'code') {
|
|
156
|
+
return transformCodeBlock(child);
|
|
157
|
+
} else if (child.type === 'list') {
|
|
156
158
|
return transformList(child);
|
|
157
159
|
}
|
|
158
160
|
})
|
|
@@ -254,7 +256,7 @@ export const transformCodeBlock = node => {
|
|
|
254
256
|
lang,
|
|
255
257
|
value
|
|
256
258
|
} = node;
|
|
257
|
-
const children = value.split('\n');
|
|
259
|
+
const children = value.split('\n').filter(Boolean);
|
|
258
260
|
return {
|
|
259
261
|
id: slugid.nice(),
|
|
260
262
|
type: CODE_BLOCK,
|
|
@@ -140,6 +140,9 @@ const transformListItem = node => {
|
|
|
140
140
|
if (child.type === 'paragraph') {
|
|
141
141
|
return transformListContent(child);
|
|
142
142
|
}
|
|
143
|
+
if (child.type === 'code_block') {
|
|
144
|
+
return transformCodeBlock(child);
|
|
145
|
+
}
|
|
143
146
|
if (child.type === 'unordered_list' || child.type === 'ordered_list') {
|
|
144
147
|
return transformList(child);
|
|
145
148
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seafile/seafile-editor",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6-1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -91,6 +91,7 @@
|
|
|
91
91
|
"is-url": "^1.2.4",
|
|
92
92
|
"jest-environment-jsdom": "29.7.0",
|
|
93
93
|
"npm": "10.2.4",
|
|
94
|
+
"prismjs": "1.29.0",
|
|
94
95
|
"reactstrap": "8.9.0",
|
|
95
96
|
"rehype-format": "5.0.0",
|
|
96
97
|
"rehype-mathjax": "5.0.0",
|