@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.
@@ -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,3 @@
1
+ import SetNodeToDecorations from './set-node-decorations';
2
+ import useHighlight from './use-highlight';
3
+ export { useHighlight, SetNodeToDecorations };
@@ -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;
@@ -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 = 'none';
2
+ export const EXPLAIN_TEXT = 'text';
3
3
  export const LANGUAGE_MAP = {
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'
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 { insertImage } from '../helper';
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
- if (editor.api.uploadLocalImage) {
30
- const file = e.target.files[0];
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.5",
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",