@seafile/sdoc-editor 0.1.113 → 0.1.115
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/api/seafile-api.js +6 -0
- package/dist/basic-sdk/extension/commons/menu/menu-item.js +6 -1
- package/dist/basic-sdk/extension/constants/element-type.js +1 -0
- package/dist/basic-sdk/extension/constants/index.js +7 -2
- package/dist/basic-sdk/extension/index.js +1 -1
- package/dist/basic-sdk/extension/plugins/code-block/render-elem.js +10 -5
- package/dist/basic-sdk/extension/plugins/index.js +3 -2
- package/dist/basic-sdk/extension/plugins/sdoc-link/helpers.js +124 -0
- package/dist/basic-sdk/extension/plugins/sdoc-link/index.js +11 -0
- package/dist/basic-sdk/extension/plugins/sdoc-link/menu/index.css +54 -0
- package/dist/basic-sdk/extension/plugins/sdoc-link/menu/index.js +71 -0
- package/dist/basic-sdk/extension/plugins/sdoc-link/menu/local-files.css +98 -0
- package/dist/basic-sdk/extension/plugins/sdoc-link/menu/local-files.js +133 -0
- package/dist/basic-sdk/extension/plugins/sdoc-link/menu/sdoc-link-file-dialog.css +35 -0
- package/dist/basic-sdk/extension/plugins/sdoc-link/menu/sdoc-link-file-dialog.js +62 -0
- package/dist/basic-sdk/extension/plugins/sdoc-link/menu/sdoc-link-hover-menu.css +116 -0
- package/dist/basic-sdk/extension/plugins/sdoc-link/menu/sdoc-link-hover-menu.js +114 -0
- package/dist/basic-sdk/extension/plugins/sdoc-link/plugin.js +40 -0
- package/dist/basic-sdk/extension/plugins/sdoc-link/render-elem.css +41 -0
- package/dist/basic-sdk/extension/plugins/sdoc-link/render-elem.js +118 -0
- package/dist/basic-sdk/extension/render/render-element.js +8 -2
- package/dist/basic-sdk/extension/toolbar/header-toolbar/index.js +3 -0
- package/dist/basic-sdk/utils/event-handler.js +6 -0
- package/dist/constants/index.js +2 -1
- package/dist/context.js +12 -0
- package/package.json +1 -1
- package/public/locales/en/sdoc-editor.json +7 -1
- package/public/locales/zh-CN/sdoc-editor.json +7 -1
- package/public/media/sdoc-editor-font/iconfont.eot +0 -0
- package/public/media/sdoc-editor-font/iconfont.svg +26 -0
- package/public/media/sdoc-editor-font/iconfont.ttf +0 -0
- package/public/media/sdoc-editor-font/iconfont.woff +0 -0
- package/public/media/sdoc-editor-font/iconfont.woff2 +0 -0
- package/public/media/sdoc-editor-font.css +58 -6
package/dist/api/seafile-api.js
CHANGED
|
@@ -81,6 +81,12 @@ var SeafileAPI = /*#__PURE__*/function () {
|
|
|
81
81
|
var url = 'api/v2.1/seadoc/revisions/' + docUuid + '/?page=' + page + '&per_page=' + perPage;
|
|
82
82
|
return this.req.get(url);
|
|
83
83
|
}
|
|
84
|
+
}, {
|
|
85
|
+
key: "getSdocFiles",
|
|
86
|
+
value: function getSdocFiles(docUuid, p) {
|
|
87
|
+
var url = 'api/v2.1/seadoc/dir/' + docUuid + '/?p=' + p + '&doc_uuid=' + docUuid;
|
|
88
|
+
return this.req.get(url);
|
|
89
|
+
}
|
|
84
90
|
}]);
|
|
85
91
|
return SeafileAPI;
|
|
86
92
|
}();
|
|
@@ -2,6 +2,7 @@ import React, { useCallback } from 'react';
|
|
|
2
2
|
import { useTranslation } from 'react-i18next';
|
|
3
3
|
import Tooltip from '../tooltip';
|
|
4
4
|
import classnames from 'classnames';
|
|
5
|
+
import { SDOC_LINK } from '../../constants';
|
|
5
6
|
var MenuItem = function MenuItem(_ref) {
|
|
6
7
|
var disabled = _ref.disabled,
|
|
7
8
|
isActive = _ref.isActive,
|
|
@@ -35,7 +36,11 @@ var MenuItem = function MenuItem(_ref) {
|
|
|
35
36
|
onClick: onClick
|
|
36
37
|
}, /*#__PURE__*/React.createElement("i", {
|
|
37
38
|
className: iconClass
|
|
38
|
-
})
|
|
39
|
+
}), type === SDOC_LINK && /*#__PURE__*/React.createElement("span", {
|
|
40
|
+
className: "sdoc-link-menu-item"
|
|
41
|
+
}, /*#__PURE__*/React.createElement("span", null, t(text)), /*#__PURE__*/React.createElement("i", {
|
|
42
|
+
className: "sdocfont sdoc-drop-down"
|
|
43
|
+
}))), /*#__PURE__*/React.createElement(Tooltip, {
|
|
39
44
|
target: id
|
|
40
45
|
}, t(text)));
|
|
41
46
|
};
|
|
@@ -39,6 +39,7 @@ export var ALIGN_LEFT = 'align-left';
|
|
|
39
39
|
export var ALIGN_RIGHT = 'align-right';
|
|
40
40
|
export var ALIGN_CENTER = 'align-center';
|
|
41
41
|
export var CLEAR_FORMAT = 'clear-format';
|
|
42
|
+
export var SDOC_LINK = 'sdoc-link';
|
|
42
43
|
|
|
43
44
|
// font
|
|
44
45
|
export var FONT_SIZE = 'font-size';
|
|
@@ -2,7 +2,7 @@ import _defineProperty from "@babel/runtime/helpers/esm/defineProperty";
|
|
|
2
2
|
var _MENUS_CONFIG_MAP, _HEADER_TITLE_MAP;
|
|
3
3
|
// extension plugin
|
|
4
4
|
import * as ELEMENT_TYPE from './element-type';
|
|
5
|
-
import { BLOCKQUOTE, HEADER, HEADER1, HEADER2, HEADER3, HEADER4, HEADER5, HEADER6, PARAGRAPH, BOLD, ITALIC, UNDERLINE, STRIKETHROUGH, SUPERSCRIPT, SUBSCRIPT, ORDERED_LIST, UNORDERED_LIST, LIST_ITEM, LIST_LIC, CHECK_LIST, CHECK_LIST_ITEM, LINK, HTML, CODE_BLOCK, CODE_LINE, IMAGE, TABLE, TABLE_CELL, TABLE_ROW, FORMULA, COLUMN, TEXT_STYLE, TEXT_STYLE_MORE, BOLD_ITALIC, TEXT_ALIGN, ALIGN_LEFT, ALIGN_RIGHT, ALIGN_CENTER, CLEAR_FORMAT, COLOR, HIGHLIGHT_COLOR } from './element-type';
|
|
5
|
+
import { BLOCKQUOTE, HEADER, HEADER1, HEADER2, HEADER3, HEADER4, HEADER5, HEADER6, PARAGRAPH, BOLD, ITALIC, UNDERLINE, STRIKETHROUGH, SUPERSCRIPT, SUBSCRIPT, ORDERED_LIST, UNORDERED_LIST, LIST_ITEM, LIST_LIC, CHECK_LIST, CHECK_LIST_ITEM, LINK, HTML, CODE_BLOCK, CODE_LINE, IMAGE, TABLE, TABLE_CELL, TABLE_ROW, FORMULA, COLUMN, TEXT_STYLE, TEXT_STYLE_MORE, BOLD_ITALIC, TEXT_ALIGN, ALIGN_LEFT, ALIGN_RIGHT, ALIGN_CENTER, CLEAR_FORMAT, COLOR, HIGHLIGHT_COLOR, SDOC_LINK } from './element-type';
|
|
6
6
|
import KEYBOARD from './keyboard';
|
|
7
7
|
import { DEFAULT_COLORS, STANDARD_COLORS, DEFAULT_RECENT_USED_LIST, DEFAULT_FONT_COLOR, RECENT_USED_HIGHLIGHT_COLORS_KEY, RECENT_USED_FONT_COLORS_KEY, RECENT_USED_TABLE_CELL_BG_COLORS_KEY, DEFAULT_LAST_USED_FONT_COLOR, DEFAULT_LAST_USED_HIGHLIGHT_COLOR, DEFAULT_LAST_USED_TABLE_CELL_BG_COLOR } from './color';
|
|
8
8
|
import { SPECIAL_FONT_SIZE_NAME, DEFAULT_COMMON_FONT_SIZE, FONT_SIZE, DEFAULT_FONT, FONT, GOOGLE_FONT_CLASS, RECENT_USED_FONTS_KEY, HEADER_FONT_SIZE } from './font';
|
|
@@ -131,6 +131,10 @@ export var MENUS_CONFIG_MAP = (_MENUS_CONFIG_MAP = {}, _defineProperty(_MENUS_CO
|
|
|
131
131
|
id: "sdoc_".concat(CLEAR_FORMAT),
|
|
132
132
|
iconClass: 'sdocfont sdoc-format-clear',
|
|
133
133
|
text: 'Clear_format'
|
|
134
|
+
}), _defineProperty(_MENUS_CONFIG_MAP, SDOC_LINK, {
|
|
135
|
+
id: "sdoc_".concat(SDOC_LINK),
|
|
136
|
+
iconClass: 'sdocfont sdoc-insert',
|
|
137
|
+
text: 'Insert'
|
|
134
138
|
}), _MENUS_CONFIG_MAP);
|
|
135
139
|
export var HEADERS = [HEADER1, HEADER2, HEADER3, HEADER4, HEADER5, HEADER6];
|
|
136
140
|
export var HEADER_TITLE_MAP = (_HEADER_TITLE_MAP = {}, _defineProperty(_HEADER_TITLE_MAP, HEADER1, 'Header_one'), _defineProperty(_HEADER_TITLE_MAP, HEADER2, 'Header_two'), _defineProperty(_HEADER_TITLE_MAP, HEADER3, 'Header_three'), _defineProperty(_HEADER_TITLE_MAP, HEADER4, 'Header_four'), _defineProperty(_HEADER_TITLE_MAP, HEADER5, 'Header_five'), _defineProperty(_HEADER_TITLE_MAP, HEADER6, 'Header_six'), _defineProperty(_HEADER_TITLE_MAP, PARAGRAPH, 'Paragraph'), _HEADER_TITLE_MAP);
|
|
@@ -156,4 +160,5 @@ export var HEADER_TYPE_ARRAY = ['header1', 'header2', 'header3', 'header4', 'hea
|
|
|
156
160
|
export var LIST_TYPE_ARRAY = ['unordered_list', 'ordered_list'];
|
|
157
161
|
export var TRANSPARENT = 'transparent';
|
|
158
162
|
export var CLIPBOARD_FORMAT_KEY = 'x-slate-fragment';
|
|
159
|
-
export
|
|
163
|
+
export var INSERT_FILE_DISPLAY_TYPE = ['text_link', 'icon_link', 'card_link'];
|
|
164
|
+
export { BLOCKQUOTE, HEADER, HEADER1, HEADER2, HEADER3, HEADER4, HEADER5, HEADER6, PARAGRAPH, BOLD, ITALIC, UNDERLINE, STRIKETHROUGH, SUPERSCRIPT, SUBSCRIPT, ORDERED_LIST, UNORDERED_LIST, LIST_ITEM, LIST_LIC, CHECK_LIST, CHECK_LIST_ITEM, LINK, HTML, CODE_BLOCK, CODE_LINE, IMAGE, TABLE, TABLE_CELL, TABLE_ROW, FORMULA, COLUMN, TEXT_STYLE, TEXT_STYLE_MORE, BOLD_ITALIC, TEXT_ALIGN, ALIGN_LEFT, ALIGN_RIGHT, ALIGN_CENTER, ELEMENT_TYPE, KEYBOARD, DEFAULT_COLORS, STANDARD_COLORS, DEFAULT_RECENT_USED_LIST, CLEAR_FORMAT, DEFAULT_FONT_COLOR, RECENT_USED_HIGHLIGHT_COLORS_KEY, RECENT_USED_FONT_COLORS_KEY, RECENT_USED_TABLE_CELL_BG_COLORS_KEY, DEFAULT_LAST_USED_FONT_COLOR, DEFAULT_LAST_USED_HIGHLIGHT_COLOR, DEFAULT_LAST_USED_TABLE_CELL_BG_COLOR, SPECIAL_FONT_SIZE_NAME, DEFAULT_COMMON_FONT_SIZE, FONT_SIZE, DEFAULT_FONT, FONT, GOOGLE_FONT_CLASS, RECENT_USED_FONTS_KEY, HEADER_FONT_SIZE, SDOC_LINK };
|
|
@@ -6,7 +6,7 @@ import renderElement from './render/render-element';
|
|
|
6
6
|
import renderLeaf from './render/render-leaf';
|
|
7
7
|
import { Toolbar, ContextToolbar } from './toolbar';
|
|
8
8
|
var baseEditor = withHistory(withReact(createEditor()));
|
|
9
|
-
var defaultEditor = Plugins.reduce(function (editor, pluginItem) {
|
|
9
|
+
var defaultEditor = Plugins === null || Plugins === void 0 ? void 0 : Plugins.reduce(function (editor, pluginItem) {
|
|
10
10
|
var withPlugin = pluginItem.editorPlugin;
|
|
11
11
|
if (withPlugin) {
|
|
12
12
|
return withPlugin(editor);
|
|
@@ -3,9 +3,11 @@ import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray";
|
|
|
3
3
|
import React, { useCallback, useEffect, useState, useRef } from 'react';
|
|
4
4
|
import { ReactEditor, useSlateStatic, useReadOnly } from '@seafile/slate-react';
|
|
5
5
|
import { Transforms } from '@seafile/slate';
|
|
6
|
+
import EventBus from '../../../utils/event-bus';
|
|
6
7
|
import { useScrollContext } from '../../../hooks/use-scroll-context';
|
|
7
8
|
import CodeBlockHoverMenu from './hover-menu';
|
|
8
9
|
import { setClipboardData } from './helpers';
|
|
10
|
+
import { EXTERNAL_EVENT } from '../../../../constants';
|
|
9
11
|
import '../../../assets/css/code-block.css';
|
|
10
12
|
var CodeBlock = function CodeBlock(_ref) {
|
|
11
13
|
var codeBlockProps = _ref.codeBlockProps;
|
|
@@ -114,10 +116,13 @@ var CodeBlock = function CodeBlock(_ref) {
|
|
|
114
116
|
observerRefValue.removeEventListener('scroll', onScroll);
|
|
115
117
|
};
|
|
116
118
|
}, [onScroll, readOnly, scrollRef]);
|
|
117
|
-
var
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
119
|
+
var onHiddenHoverMenu = useCallback(function () {
|
|
120
|
+
setShowHoverMenu(false);
|
|
121
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
122
|
+
}, []);
|
|
123
|
+
useEffect(function () {
|
|
124
|
+
var eventBus = EventBus.getInstance();
|
|
125
|
+
eventBus.subscribe(EXTERNAL_EVENT.HIDDEN_CODE_BLOCK_HOVER_MENU, onHiddenHoverMenu);
|
|
121
126
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
122
127
|
}, []);
|
|
123
128
|
return /*#__PURE__*/React.createElement("div", {
|
|
@@ -129,7 +134,7 @@ var CodeBlock = function CodeBlock(_ref) {
|
|
|
129
134
|
className: 'sdoc-code-block-pre'
|
|
130
135
|
}, attributes), /*#__PURE__*/React.createElement("code", {
|
|
131
136
|
className: "sdoc-code-block-code ".concat(white_space === 'nowrap' ? 'sdoc-code-no-wrap' : '')
|
|
132
|
-
}, children)), showHoverMenu &&
|
|
137
|
+
}, children)), showHoverMenu && /*#__PURE__*/React.createElement(CodeBlockHoverMenu, {
|
|
133
138
|
menuPosition: menuPosition,
|
|
134
139
|
onChangeLanguage: onChangeLanguage,
|
|
135
140
|
language: element.language,
|
|
@@ -11,6 +11,7 @@ import TablePlugin from './table';
|
|
|
11
11
|
import HtmlPlugin from './html';
|
|
12
12
|
import TextAlignPlugin from './text-align';
|
|
13
13
|
import FontPlugin from './font';
|
|
14
|
-
|
|
14
|
+
import SdocLinkPlugin from './sdoc-link';
|
|
15
|
+
var Plugins = [MarkDownPlugin, HtmlPlugin, HeaderPlugin, LinkPlugin, BlockquotePlugin, ListPlugin, CheckListPlugin, CodeBlockPlugin, ImagePlugin, TablePlugin, TextPlugin, TextAlignPlugin, FontPlugin, SdocLinkPlugin];
|
|
15
16
|
export default Plugins;
|
|
16
|
-
export { MarkDownPlugin, HeaderPlugin, LinkPlugin, BlockquotePlugin, ListPlugin, CheckListPlugin, CodeBlockPlugin, ImagePlugin, TablePlugin, TextPlugin, HtmlPlugin, TextAlignPlugin, FontPlugin };
|
|
17
|
+
export { MarkDownPlugin, HeaderPlugin, LinkPlugin, BlockquotePlugin, ListPlugin, CheckListPlugin, CodeBlockPlugin, ImagePlugin, TablePlugin, TextPlugin, HtmlPlugin, TextAlignPlugin, FontPlugin, SdocLinkPlugin };
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray";
|
|
2
|
+
import { ReactEditor } from '@seafile/slate-react';
|
|
3
|
+
import { Editor, Transforms, Range } from '@seafile/slate';
|
|
4
|
+
import slugid from 'slugid';
|
|
5
|
+
import context from '../../../../context';
|
|
6
|
+
import { SDOC_LINK, LINK, INSERT_FILE_DISPLAY_TYPE } from '../../constants';
|
|
7
|
+
import { getNodeType, getSelectedElems } from '../../core';
|
|
8
|
+
export var isMenuDisabled = function isMenuDisabled(editor) {
|
|
9
|
+
if (editor.selection == null) return true;
|
|
10
|
+
var selectedElems = getSelectedElems(editor);
|
|
11
|
+
var notMatch = selectedElems.some(function (elem) {
|
|
12
|
+
var type = elem.type;
|
|
13
|
+
if (editor.isVoid(elem)) return true;
|
|
14
|
+
if (['code-block', 'code-line', 'link'].includes(type)) return true;
|
|
15
|
+
return false;
|
|
16
|
+
});
|
|
17
|
+
if (notMatch) return true; // disabled
|
|
18
|
+
return false; // enable
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export var generateSdocFileNode = function generateSdocFileNode(uuid, text) {
|
|
22
|
+
var sdocFileNode = {
|
|
23
|
+
id: slugid.nice(),
|
|
24
|
+
type: SDOC_LINK,
|
|
25
|
+
doc_uuid: uuid,
|
|
26
|
+
title: text,
|
|
27
|
+
display_type: INSERT_FILE_DISPLAY_TYPE[0],
|
|
28
|
+
children: [{
|
|
29
|
+
id: slugid.nice(),
|
|
30
|
+
text: text || ''
|
|
31
|
+
}]
|
|
32
|
+
};
|
|
33
|
+
return sdocFileNode;
|
|
34
|
+
};
|
|
35
|
+
export var getType = function getType(editor) {
|
|
36
|
+
var _Editor$nodes = Editor.nodes(editor, {
|
|
37
|
+
match: function match(n) {
|
|
38
|
+
return getNodeType(n) === LINK;
|
|
39
|
+
},
|
|
40
|
+
universal: true
|
|
41
|
+
}),
|
|
42
|
+
_Editor$nodes2 = _slicedToArray(_Editor$nodes, 1),
|
|
43
|
+
match = _Editor$nodes2[0];
|
|
44
|
+
if (!match) return 'paragraph';
|
|
45
|
+
var _match = _slicedToArray(match, 1),
|
|
46
|
+
n = _match[0];
|
|
47
|
+
return getNodeType(n);
|
|
48
|
+
};
|
|
49
|
+
export var insertSdocFileLink = function insertSdocFileLink(editor, text, uuid) {
|
|
50
|
+
if (isMenuDisabled(editor)) return;
|
|
51
|
+
|
|
52
|
+
// Selection folded or not
|
|
53
|
+
var selection = editor.selection;
|
|
54
|
+
if (selection == null) return;
|
|
55
|
+
var isCollapsed = Range.isCollapsed(selection);
|
|
56
|
+
if (isCollapsed) {
|
|
57
|
+
// Insert Spaces before and after sdoclinks for easy operation
|
|
58
|
+
editor.insertText(' ');
|
|
59
|
+
var sdocFileNode = generateSdocFileNode(uuid, text);
|
|
60
|
+
Transforms.insertNodes(editor, sdocFileNode);
|
|
61
|
+
|
|
62
|
+
// Not being able to use insertText directly causes the added Spaces to be added to the linked text, as in the issue above, replaced by insertFragment
|
|
63
|
+
editor.insertFragment([{
|
|
64
|
+
id: slugid.nice(),
|
|
65
|
+
text: ' '
|
|
66
|
+
}]);
|
|
67
|
+
} else {
|
|
68
|
+
var selectedText = Editor.string(editor, selection); // Selected text
|
|
69
|
+
if (selectedText !== text) {
|
|
70
|
+
// If the selected text is different from the typed text, delete the text and insert the link
|
|
71
|
+
editor.deleteFragment();
|
|
72
|
+
var _sdocFileNode = generateSdocFileNode(uuid, text);
|
|
73
|
+
Transforms.insertNodes(editor, _sdocFileNode);
|
|
74
|
+
} else {
|
|
75
|
+
// If the selected text is the same as the entered text, only the link can be wrapped
|
|
76
|
+
var _sdocFileNode2 = generateSdocFileNode(uuid, text);
|
|
77
|
+
Transforms.wrapNodes(editor, _sdocFileNode2, {
|
|
78
|
+
split: true
|
|
79
|
+
});
|
|
80
|
+
Transforms.collapse(editor, {
|
|
81
|
+
edge: 'end'
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
export var unwrapLinkNode = function unwrapLinkNode(editor, element) {
|
|
87
|
+
if (editor.selection == null) return;
|
|
88
|
+
var path = ReactEditor.findPath(editor, element);
|
|
89
|
+
if (path) {
|
|
90
|
+
Transforms.unwrapNodes(editor, {
|
|
91
|
+
at: path
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
export var getParentPathNameArr = function getParentPathNameArr(fileListData, indexId, indexArray) {
|
|
96
|
+
var arr = Array.from(indexArray);
|
|
97
|
+
for (var i = 0, len = fileListData.length; i < len; i++) {
|
|
98
|
+
arr.push(fileListData[i].name);
|
|
99
|
+
if (fileListData[i].indexId === indexId) {
|
|
100
|
+
return arr;
|
|
101
|
+
}
|
|
102
|
+
var children = fileListData[i].children;
|
|
103
|
+
if (children && children.length) {
|
|
104
|
+
var result = getParentPathNameArr(children, indexId, arr);
|
|
105
|
+
if (result) return result;
|
|
106
|
+
}
|
|
107
|
+
arr.pop();
|
|
108
|
+
}
|
|
109
|
+
return null;
|
|
110
|
+
};
|
|
111
|
+
export var getNewFileListData = function getNewFileListData(fileListData, indexId, children) {
|
|
112
|
+
fileListData.forEach(function (item) {
|
|
113
|
+
if (item.indexId === indexId) {
|
|
114
|
+
item.children = children;
|
|
115
|
+
}
|
|
116
|
+
if (item === null || item === void 0 ? void 0 : item.children) {
|
|
117
|
+
getNewFileListData(item.children, indexId, children);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
return fileListData;
|
|
121
|
+
};
|
|
122
|
+
export var getUrl = function getUrl(uuid) {
|
|
123
|
+
return context.getSdocLocalFileUrl(uuid);
|
|
124
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { SDOC_LINK } from '../../constants';
|
|
2
|
+
import SdocLinkMenu from './menu';
|
|
3
|
+
import withSdocLink from './plugin';
|
|
4
|
+
import renderSdocLink from './render-elem';
|
|
5
|
+
var SdocLinkPlugin = {
|
|
6
|
+
type: SDOC_LINK,
|
|
7
|
+
editorMenus: [SdocLinkMenu],
|
|
8
|
+
editorPlugin: withSdocLink,
|
|
9
|
+
renderElements: [renderSdocLink]
|
|
10
|
+
};
|
|
11
|
+
export default SdocLinkPlugin;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
.sdoc-link-menu {
|
|
2
|
+
position: relative;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.sdoc-link-menu .menu-group-item {
|
|
6
|
+
width: 72px;
|
|
7
|
+
height: 24px;
|
|
8
|
+
line-height: 24px;
|
|
9
|
+
margin-right: 8px;
|
|
10
|
+
border: none !important;
|
|
11
|
+
color: #444;
|
|
12
|
+
background-color: #fff;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.sdoc-link-menu .menu-group-item :first-child {
|
|
16
|
+
margin-right: 5px;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.sdoc-link-menu .sdoc-link-menu-popover {
|
|
20
|
+
position: absolute;
|
|
21
|
+
top: 36px;
|
|
22
|
+
left: 0px;
|
|
23
|
+
padding: 8px 0;
|
|
24
|
+
background-color: #fff;
|
|
25
|
+
border: 1px solid #e5e6e8;
|
|
26
|
+
border-radius: 2px;
|
|
27
|
+
box-shadow: 0 0 10px #ccc;
|
|
28
|
+
display: flex;
|
|
29
|
+
flex-direction: column;
|
|
30
|
+
align-items: flex-start;
|
|
31
|
+
z-index: 101;
|
|
32
|
+
white-space: nowrap;
|
|
33
|
+
width: 180px;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.sdoc-link-menu .sdoc-link-menu-popover .sdoc-link-menu-item {
|
|
37
|
+
cursor: pointer;
|
|
38
|
+
height: 30px;
|
|
39
|
+
width: 100%;
|
|
40
|
+
user-select: none;
|
|
41
|
+
display: flex;
|
|
42
|
+
align-items: center;
|
|
43
|
+
font-size: 12px;
|
|
44
|
+
color: #212529;
|
|
45
|
+
padding-left: 24px;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.sdoc-link-menu .sdoc-link-menu-popover .sdoc-link-menu-item > span :first-child {
|
|
49
|
+
padding-right: 8px;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.sdoc-link-menu .sdoc-link-menu-popover .sdoc-link-menu-item:hover {
|
|
53
|
+
background-color: rgb(245, 245, 245);
|
|
54
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
|
|
2
|
+
import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray";
|
|
3
|
+
import React, { useCallback, useState } from 'react';
|
|
4
|
+
import { withTranslation } from 'react-i18next';
|
|
5
|
+
import { SDOC_LINK, MENUS_CONFIG_MAP } from '../../../constants';
|
|
6
|
+
import { MenuItem } from '../../../commons';
|
|
7
|
+
import { getType, isMenuDisabled } from '../helpers';
|
|
8
|
+
import InsertSdocFileDialog from './sdoc-link-file-dialog';
|
|
9
|
+
import './index.css';
|
|
10
|
+
var SdocLinkMenu = function SdocLinkMenu(_ref) {
|
|
11
|
+
var isRichEditor = _ref.isRichEditor,
|
|
12
|
+
className = _ref.className,
|
|
13
|
+
editor = _ref.editor,
|
|
14
|
+
t = _ref.t;
|
|
15
|
+
var _useState = useState(false),
|
|
16
|
+
_useState2 = _slicedToArray(_useState, 2),
|
|
17
|
+
showInsertDialog = _useState2[0],
|
|
18
|
+
setShowInsertDialog = _useState2[1];
|
|
19
|
+
var _useState3 = useState(false),
|
|
20
|
+
_useState4 = _slicedToArray(_useState3, 2),
|
|
21
|
+
showInsertPopover = _useState4[0],
|
|
22
|
+
setShowInsertPopover = _useState4[1];
|
|
23
|
+
var isActive = useCallback(function () {
|
|
24
|
+
return getType(editor) === SDOC_LINK;
|
|
25
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
26
|
+
}, []);
|
|
27
|
+
var isDisabled = useCallback(function () {
|
|
28
|
+
return isMenuDisabled(editor);
|
|
29
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
30
|
+
}, []);
|
|
31
|
+
var onInsertFileDialogToggle = useCallback(function () {
|
|
32
|
+
setShowInsertPopover(true);
|
|
33
|
+
// setShowInsertDialog(!showInsertDialog);
|
|
34
|
+
}, []);
|
|
35
|
+
var onMouseDown = useCallback(function () {
|
|
36
|
+
onInsertFileDialogToggle();
|
|
37
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
38
|
+
}, []);
|
|
39
|
+
var onInsertSdocFile = useCallback(function () {
|
|
40
|
+
setShowInsertPopover(false);
|
|
41
|
+
setShowInsertDialog(true);
|
|
42
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
43
|
+
}, []);
|
|
44
|
+
var onDialogToggle = useCallback(function (value) {
|
|
45
|
+
setShowInsertDialog(value);
|
|
46
|
+
}, []);
|
|
47
|
+
var menuConfig = MENUS_CONFIG_MAP[SDOC_LINK];
|
|
48
|
+
var props = _objectSpread(_objectSpread({
|
|
49
|
+
isRichEditor: isRichEditor,
|
|
50
|
+
className: className
|
|
51
|
+
}, menuConfig), {}, {
|
|
52
|
+
disabled: isDisabled(),
|
|
53
|
+
isActive: isActive(),
|
|
54
|
+
'onMouseDown': onMouseDown,
|
|
55
|
+
type: SDOC_LINK
|
|
56
|
+
});
|
|
57
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
58
|
+
className: "sdoc-link-menu"
|
|
59
|
+
}, /*#__PURE__*/React.createElement(MenuItem, props), showInsertPopover && /*#__PURE__*/React.createElement("div", {
|
|
60
|
+
className: "sdoc-link-menu-popover"
|
|
61
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
62
|
+
className: "sdoc-link-menu-item",
|
|
63
|
+
onMouseDown: onInsertSdocFile
|
|
64
|
+
}, /*#__PURE__*/React.createElement("span", null, /*#__PURE__*/React.createElement("i", {
|
|
65
|
+
className: "sdocfont sdoc-document"
|
|
66
|
+
})), /*#__PURE__*/React.createElement("span", null, t('Sdoc_document')))), showInsertDialog && /*#__PURE__*/React.createElement(InsertSdocFileDialog, {
|
|
67
|
+
editor: editor,
|
|
68
|
+
onDialogToggle: onDialogToggle
|
|
69
|
+
}));
|
|
70
|
+
};
|
|
71
|
+
export default withTranslation('sdoc-editor')(SdocLinkMenu);
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
.sdoc-local-files-library {
|
|
2
|
+
color: #212529;
|
|
3
|
+
cursor: pointer;
|
|
4
|
+
font-size: 14px;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.sdoc-local-files-library .sdoc-local-folder-container .sdoc-local-folder .sdoc-local-folder-info {
|
|
8
|
+
display: flex;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.sdoc-local-files-library .sdoc-local-folder-container .sdoc-local-folder .sdoc-local-folder-info > span:nth-child(-n+2) {
|
|
12
|
+
padding-right: 3px;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.sdoc-local-files-library .sdoc-local-folder-container .sdoc-local-folder .sdoc-local-folder-info :first-child {
|
|
16
|
+
transform: scale(0.6)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.sdoc-local-files-library .sdoc-local-folder-container .sdoc-local-folder .active {
|
|
20
|
+
background: #ff8000;
|
|
21
|
+
border-radius: 2px;
|
|
22
|
+
box-shadow: inset 0 0 1px #999;
|
|
23
|
+
color: #fff;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.sdoc-local-files-library .sdoc-local-folder-container .sdoc-local-folder .active .sdoc-local-file-icon {
|
|
27
|
+
color: #fff;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.sdoc-local-files-library .sdoc-local-folder-container .sdoc-local-folder .inactive:hover {
|
|
31
|
+
background: #fdefb9;
|
|
32
|
+
border-radius: 2px;
|
|
33
|
+
box-shadow: inset 0 0 1px #999;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.sdoc-local-files-library .sdoc-local-folder-container .sdoc-local-folder .inactive > span:nth-child(-n+2) {
|
|
37
|
+
color: #9aa0ac;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.sdoc-local-files-library .sdoc-local-folder-container .sdoc-local-folder .sdoc-local-folder-info .icon-drop-down-scale {
|
|
41
|
+
transform: scale(1.1) !important;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.sdoc-local-files-library .sdoc-local-folder-container .sdoc-local-folder .sdoc-local-folder-info > span:nth-child(3) {
|
|
45
|
+
padding-left: 3px;
|
|
46
|
+
white-space: nowrap;
|
|
47
|
+
overflow: hidden;
|
|
48
|
+
text-overflow: ellipsis;
|
|
49
|
+
line-height: 24px;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.sdoc-local-files-library .sdoc-local-folder-container .sdoc-local-folder .sdoc-local-folder-files {
|
|
53
|
+
padding-left: 24px;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.sdoc-local-files-library .sdoc-local-folder-container .sdoc-local-folder .sdoc-local-folder-files .active {
|
|
57
|
+
background: #ff8000;
|
|
58
|
+
border-radius: 2px;
|
|
59
|
+
box-shadow: inset 0 0 1px #999;
|
|
60
|
+
color: #fff;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.sdoc-local-files-library .sdoc-local-folder-container .sdoc-local-folder .sdoc-local-folder-files .inactive:hover {
|
|
64
|
+
background: #fdefb9;
|
|
65
|
+
border-radius: 2px;
|
|
66
|
+
box-shadow: inset 0 0 1px #999;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.sdoc-local-files-library .sdoc-local-folder-container .sdoc-local-folder .sdoc-local-folder-children {
|
|
70
|
+
padding-left: 24px
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.sdoc-local-files-library .sdoc-local-folder-container .sdoc-local-folder .sdoc-local-folder-children .inactive .sdoc-local-file-icon {
|
|
74
|
+
color: #ff9800;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.sdoc-local-files-library .sdoc-local-folder-container .inactive:hover {
|
|
78
|
+
background: #fdefb9;
|
|
79
|
+
border-radius: 2px;
|
|
80
|
+
box-shadow: inset 0 0 1px #999;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.sdoc-local-files-library .sdoc-local-folder-container .active {
|
|
84
|
+
background: #ff8000;
|
|
85
|
+
border-radius: 2px;
|
|
86
|
+
box-shadow: inset 0 0 1px #999;
|
|
87
|
+
color: #fff;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.sdoc-local-files-library .sdoc-local-folder-container .active .sdoc-local-file-icon {
|
|
91
|
+
color: #fff;
|
|
92
|
+
padding-right: 5px;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.sdoc-local-files-library .sdoc-local-folder-container .inactive .sdoc-local-file-icon {
|
|
96
|
+
color: #ff9800;
|
|
97
|
+
padding-right: 5px;
|
|
98
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray";
|
|
2
|
+
import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray";
|
|
3
|
+
import React, { useCallback, useState, useRef, useEffect } from 'react';
|
|
4
|
+
import { withTranslation } from 'react-i18next';
|
|
5
|
+
import slugid from 'slugid';
|
|
6
|
+
import context from '../../../../../context';
|
|
7
|
+
import { getErrorMsg } from '../../../../../utils';
|
|
8
|
+
import toaster from '../../../../../components/toast';
|
|
9
|
+
import { getParentPathNameArr, getNewFileListData } from '../helpers';
|
|
10
|
+
import './local-files.css';
|
|
11
|
+
var LocalFiles = function LocalFiles(_ref) {
|
|
12
|
+
var onSelectedFile = _ref.onSelectedFile,
|
|
13
|
+
toggle = _ref.toggle,
|
|
14
|
+
t = _ref.t;
|
|
15
|
+
var folderRef = useRef(null);
|
|
16
|
+
var _useState = useState(new Set([])),
|
|
17
|
+
_useState2 = _slicedToArray(_useState, 2),
|
|
18
|
+
expandedFolder = _useState2[0],
|
|
19
|
+
setExpandedFolder = _useState2[1];
|
|
20
|
+
var _useState3 = useState(null),
|
|
21
|
+
_useState4 = _slicedToArray(_useState3, 2),
|
|
22
|
+
currentActiveItem = _useState4[0],
|
|
23
|
+
setCurrentActiveItem = _useState4[1];
|
|
24
|
+
var _useState5 = useState([]),
|
|
25
|
+
_useState6 = _slicedToArray(_useState5, 2),
|
|
26
|
+
fileListData = _useState6[0],
|
|
27
|
+
setFileListData = _useState6[1];
|
|
28
|
+
var collapsedFolder = useCallback(function (data, indexId) {
|
|
29
|
+
data.forEach(function (item) {
|
|
30
|
+
if (item.indexId === indexId) {
|
|
31
|
+
item.children = null;
|
|
32
|
+
}
|
|
33
|
+
if (item === null || item === void 0 ? void 0 : item.children) {
|
|
34
|
+
collapsedFolder(item.children, indexId);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
setFileListData(_toConsumableArray(data));
|
|
38
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
39
|
+
}, []);
|
|
40
|
+
var getFileData = useCallback(function (p, indexId, fileListData) {
|
|
41
|
+
context.getSdocLocalFiles(p).then(function (res) {
|
|
42
|
+
res.data.forEach(function (item) {
|
|
43
|
+
item.indexId = slugid.nice();
|
|
44
|
+
});
|
|
45
|
+
// Open folder
|
|
46
|
+
if (indexId && fileListData.length > 0) {
|
|
47
|
+
var newFileListData = getNewFileListData(fileListData, indexId, res.data);
|
|
48
|
+
setFileListData(_toConsumableArray(newFileListData));
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
// First load
|
|
52
|
+
setFileListData(res.data);
|
|
53
|
+
}).catch(function (error) {
|
|
54
|
+
toggle();
|
|
55
|
+
var errorMessage = getErrorMsg(error);
|
|
56
|
+
toaster.danger(errorMessage);
|
|
57
|
+
});
|
|
58
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
59
|
+
}, []);
|
|
60
|
+
useEffect(function () {
|
|
61
|
+
getFileData('/');
|
|
62
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
63
|
+
}, []);
|
|
64
|
+
var onToggle = useCallback(function (e, item, fileListData) {
|
|
65
|
+
e.stopPropagation();
|
|
66
|
+
if (expandedFolder.has(item.indexId)) {
|
|
67
|
+
collapsedFolder(fileListData, item.indexId);
|
|
68
|
+
expandedFolder.delete(item.indexId);
|
|
69
|
+
} else {
|
|
70
|
+
var pathNameArr = getParentPathNameArr(fileListData, item.indexId, []);
|
|
71
|
+
var p = pathNameArr.join('/');
|
|
72
|
+
getFileData(p, item.indexId, fileListData);
|
|
73
|
+
expandedFolder.add(item.indexId);
|
|
74
|
+
}
|
|
75
|
+
setCurrentActiveItem(item);
|
|
76
|
+
setExpandedFolder(new Set(Array.from(expandedFolder)));
|
|
77
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
78
|
+
}, [expandedFolder]);
|
|
79
|
+
var onSelectFile = useCallback(function (e, file) {
|
|
80
|
+
e.stopPropagation();
|
|
81
|
+
setCurrentActiveItem(file);
|
|
82
|
+
onSelectedFile(file);
|
|
83
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
84
|
+
}, []);
|
|
85
|
+
var renderFileTree = useCallback(function (data) {
|
|
86
|
+
if (!Array.isArray(data)) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
return data.map(function (item) {
|
|
90
|
+
var _item$children, _item$children2;
|
|
91
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
92
|
+
key: item.indexId,
|
|
93
|
+
className: "sdoc-local-folder-container"
|
|
94
|
+
}, item.type === 'dir' && /*#__PURE__*/React.createElement("div", {
|
|
95
|
+
ref: folderRef,
|
|
96
|
+
className: "sdoc-local-folder",
|
|
97
|
+
onClick: function onClick(e) {
|
|
98
|
+
onToggle(e, item, fileListData);
|
|
99
|
+
}
|
|
100
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
101
|
+
className: "sdoc-local-folder-info ".concat((currentActiveItem === null || currentActiveItem === void 0 ? void 0 : currentActiveItem.indexId) === item.indexId ? 'active' : 'inactive')
|
|
102
|
+
}, /*#__PURE__*/React.createElement("span", {
|
|
103
|
+
className: "".concat(expandedFolder.has(item.indexId) ? 'icon-drop-down-scale' : '')
|
|
104
|
+
}, /*#__PURE__*/React.createElement("i", {
|
|
105
|
+
className: "sdocfont ".concat(expandedFolder.has(item.indexId) ? 'sdoc-drop-down' : 'sdoc-right-slide')
|
|
106
|
+
})), /*#__PURE__*/React.createElement("span", null, /*#__PURE__*/React.createElement("i", {
|
|
107
|
+
className: "sdocfont sdoc-file"
|
|
108
|
+
})), /*#__PURE__*/React.createElement("span", {
|
|
109
|
+
className: "sdoc-folder-name"
|
|
110
|
+
}, item.name)), /*#__PURE__*/React.createElement("div", {
|
|
111
|
+
className: "sdoc-local-folder-children"
|
|
112
|
+
}, (item === null || item === void 0 ? void 0 : (_item$children = item.children) === null || _item$children === void 0 ? void 0 : _item$children.length) === 0 && /*#__PURE__*/React.createElement("div", {
|
|
113
|
+
className: "sdoc-local-folder-children-empty"
|
|
114
|
+
}, t('Empty')), (item === null || item === void 0 ? void 0 : (_item$children2 = item.children) === null || _item$children2 === void 0 ? void 0 : _item$children2.length) > 0 && renderFileTree(item.children))), item.type === 'file' && /*#__PURE__*/React.createElement("div", {
|
|
115
|
+
className: "sdoc-local-file-item ".concat((currentActiveItem === null || currentActiveItem === void 0 ? void 0 : currentActiveItem.indexId) === item.indexId ? 'active' : 'inactive'),
|
|
116
|
+
onClick: function onClick(e) {
|
|
117
|
+
onSelectFile(e, item);
|
|
118
|
+
}
|
|
119
|
+
}, /*#__PURE__*/React.createElement("span", {
|
|
120
|
+
className: "sdoc-local-file-icon"
|
|
121
|
+
}, /*#__PURE__*/React.createElement("i", {
|
|
122
|
+
className: "sdocfont sdoc-document"
|
|
123
|
+
})), /*#__PURE__*/React.createElement("span", {
|
|
124
|
+
className: "sdoc-local-file-name"
|
|
125
|
+
}, item.name)));
|
|
126
|
+
});
|
|
127
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
128
|
+
}, [fileListData, currentActiveItem, expandedFolder]);
|
|
129
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
130
|
+
className: "sdoc-local-files-library"
|
|
131
|
+
}, renderFileTree(fileListData));
|
|
132
|
+
};
|
|
133
|
+
export default withTranslation('sdoc-editor')(LocalFiles);
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
.sdoc-file-addition-container {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: row;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.sdoc-file-addition-left {
|
|
7
|
+
border-right: 1px solid #e9ecef;
|
|
8
|
+
width: 150px;
|
|
9
|
+
padding: 12px 8px;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.sdoc-file-addition-left .sdoc-addition-item {
|
|
13
|
+
padding: 5px 0 5px 8px;
|
|
14
|
+
border-radius: 3px;
|
|
15
|
+
display: inline-block;
|
|
16
|
+
cursor: pointer;
|
|
17
|
+
width: 100%;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.sdoc-file-addition-left .sdoc-addition-item:hover {
|
|
21
|
+
background-color: #f5f5f5;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.sdoc-file-addition-container .sdoc-file-addition-left .selected-sdoc-addition-item {
|
|
25
|
+
background-color: #ff8000;
|
|
26
|
+
color: #fff;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.sdoc-file-addition-right {
|
|
30
|
+
height: 440px;
|
|
31
|
+
overflow-y: auto;
|
|
32
|
+
overflow-x: hidden;
|
|
33
|
+
padding: 16px 16px;
|
|
34
|
+
width: 470px;
|
|
35
|
+
}
|