@skbkontur/markdown 2.6.3 → 2.7.0-alpha.0
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/package.json +2 -2
- package/src/Markdown/Emoji/Emoji.logic.js +4 -4
- package/src/Markdown/Emoji/Emoji.styled.js +14 -21
- package/src/Markdown/Emoji/EmojiDropdown.js +6 -11
- package/src/Markdown/Emoji/helpers.js +4 -4
- package/src/Markdown/Files/Files.logic.js +46 -108
- package/src/Markdown/Markdown.creevey.js +73 -221
- package/src/Markdown/Markdown.js +62 -86
- package/src/Markdown/Markdown.styled.js +251 -90
- package/src/Markdown/MarkdownActions/AIActionsDropdown/AIActionsDropdown.js +45 -111
- package/src/Markdown/MarkdownActions/AIActionsDropdown/AIActionsDropdown.styled.js +16 -8
- package/src/Markdown/MarkdownActions/AIActionsDropdown/constants.js +2 -2
- package/src/Markdown/MarkdownActions/MarkdownActions.js +24 -37
- package/src/Markdown/MarkdownActions/MarkdownDropdown/MarkdownDropdown.js +3 -7
- package/src/Markdown/MarkdownActions/MarkdownDropdown/MarkdownDropdown.styled.js +8 -6
- package/src/Markdown/MarkdownEditor.js +6 -28
- package/src/Markdown/MarkdownHelpItems.js +61 -63
- package/src/Markdown/MarkdownHelpers/EmptyPreview.js +1 -1
- package/src/Markdown/MarkdownHelpers/MarkdownFormatButton.js +6 -7
- package/src/Markdown/MarkdownHelpers/constants.js +2 -2
- package/src/Markdown/MarkdownHelpers/markdownHelpers.d.ts +1 -0
- package/src/Markdown/MarkdownHelpers/markdownHelpers.js +50 -47
- package/src/Markdown/MarkdownHelpers/markdownListEnterHelpers.d.ts +2 -0
- package/src/Markdown/MarkdownHelpers/markdownListEnterHelpers.js +45 -0
- package/src/Markdown/MarkdownHelpers/markdownMentionHelpers.js +12 -12
- package/src/Markdown/MarkdownHelpers/markdownTextareaHelpers.js +34 -36
- package/src/Markdown/MarkdownMention.js +18 -64
- package/src/Markdown/constants.js +6 -6
- package/src/Markdown/utils/guid.js +13 -18
- package/src/Markdown/utils/htmlToMd.js +2 -2
- package/src/Markdown/utils/onInsertText.d.ts +1 -0
- package/src/Markdown/utils/onInsertText.js +3 -0
- package/src/Markdown/utils/saveFile.js +4 -4
- package/src/MarkdownCombination/MarkdownCombination.js +8 -9
- package/src/MarkdownCombination/MarkdownCombination.styled.js +7 -6
- package/src/MarkdownIcons/AttachLink.js +2 -2
- package/src/MarkdownIcons/AttachPaperclip.js +2 -2
- package/src/MarkdownIcons/CheckboxCheckedIcon.js +2 -2
- package/src/MarkdownIcons/CheckboxUncheckedIcon.js +2 -2
- package/src/MarkdownIcons/CheckedList.js +2 -2
- package/src/MarkdownIcons/Collapse.js +2 -2
- package/src/MarkdownIcons/Copy.js +1 -1
- package/src/MarkdownIcons/DocIcon.js +2 -2
- package/src/MarkdownIcons/EmojiFace.js +2 -2
- package/src/MarkdownIcons/EmptyPrviewArrow.js +1 -1
- package/src/MarkdownIcons/Expand.js +2 -2
- package/src/MarkdownIcons/EyeOpen.js +2 -2
- package/src/MarkdownIcons/List.js +2 -2
- package/src/MarkdownIcons/MarkdownIcons.styled.js +3 -6
- package/src/MarkdownIcons/NatureFxSparkleA2.js +1 -1
- package/src/MarkdownIcons/NumberedList.js +2 -2
- package/src/MarkdownIcons/SplitView.js +1 -1
- package/src/MarkdownIcons/Table.js +2 -2
- package/src/MarkdownIcons/ToolPencil.js +2 -2
- package/src/MarkdownViewer/Helpers/MarkdownImage.js +1 -2
- package/src/MarkdownViewer/Helpers/MarkdownLink.js +1 -4
- package/src/MarkdownViewer/Helpers/MarkdownTable.js +1 -2
- package/src/MarkdownViewer/MarkdownViewer.js +18 -26
- package/src/MarkdownViewer/MarkdownViewer.styles.js +163 -19
- package/src/styles/styled-components.js +1 -1
- package/src/styles/theme.js +5 -5
|
@@ -1,13 +1,3 @@
|
|
|
1
|
-
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
|
|
2
|
-
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
|
|
3
|
-
if (ar || !(i in from)) {
|
|
4
|
-
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
|
|
5
|
-
ar[i] = from[i];
|
|
6
|
-
}
|
|
7
|
-
}
|
|
8
|
-
return to.concat(ar || Array.prototype.slice.call(from));
|
|
9
|
-
};
|
|
10
|
-
var _a, _b;
|
|
11
1
|
import React from 'react';
|
|
12
2
|
import { MarkdownSymbolWrapper } from './Markdown.styled';
|
|
13
3
|
import { MarkdownFormat } from './MarkdownFormat';
|
|
@@ -19,10 +9,10 @@ import { List } from '../MarkdownIcons/List';
|
|
|
19
9
|
import { I } from '../MarkdownIcons/MarkdownIcons.styled';
|
|
20
10
|
import { NumberedList } from '../MarkdownIcons/NumberedList';
|
|
21
11
|
import { Table } from '../MarkdownIcons/Table';
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
export
|
|
12
|
+
const newLinesRegexp = /([\n\r]+)/g;
|
|
13
|
+
const spacesMatchRegexp = /\s/gm;
|
|
14
|
+
const spacesSplitRegexp = /(\s+)/;
|
|
15
|
+
export const eventKeyCodeToMarkdownFormat = {
|
|
26
16
|
Digit2: MarkdownFormat.h2,
|
|
27
17
|
Digit3: MarkdownFormat.h3,
|
|
28
18
|
Digit4: MarkdownFormat.h4,
|
|
@@ -36,46 +26,46 @@ export var eventKeyCodeToMarkdownFormat = {
|
|
|
36
26
|
KeyC: MarkdownFormat.codeBlock,
|
|
37
27
|
KeyQ: MarkdownFormat.quote,
|
|
38
28
|
};
|
|
39
|
-
export
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
export
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
29
|
+
export const markdownFormatToShortKeyLong = {
|
|
30
|
+
[MarkdownFormat.h2]: '2',
|
|
31
|
+
[MarkdownFormat.h3]: '3',
|
|
32
|
+
[MarkdownFormat.h4]: '4',
|
|
33
|
+
[MarkdownFormat.crossed]: 'S',
|
|
34
|
+
[MarkdownFormat.list]: 'P',
|
|
35
|
+
[MarkdownFormat.checkedList]: 'D',
|
|
36
|
+
[MarkdownFormat.numberedList]: 'O',
|
|
37
|
+
[MarkdownFormat.codeBlock]: 'C',
|
|
38
|
+
[MarkdownFormat.quote]: 'Q',
|
|
39
|
+
};
|
|
40
|
+
export const markdownFormatToShortKeyShort = {
|
|
41
|
+
[MarkdownFormat.bold]: 'B',
|
|
42
|
+
[MarkdownFormat.italic]: 'I',
|
|
43
|
+
[MarkdownFormat.ref]: 'K',
|
|
44
|
+
};
|
|
55
45
|
function reverseString(text) {
|
|
56
46
|
return text.split('').reverse().join('');
|
|
57
47
|
}
|
|
58
48
|
export function checkSpaceSymbol(text, checkedLength) {
|
|
59
|
-
|
|
60
|
-
|
|
49
|
+
const latestSymbolPos = text.length - 1;
|
|
50
|
+
const latestSymbol = text.charAt(latestSymbolPos);
|
|
61
51
|
if (latestSymbol.match(spacesMatchRegexp) && checkedLength) {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
52
|
+
const substringText = reverseString(text).split(spacesSplitRegexp);
|
|
53
|
+
const spaces = substringText[1];
|
|
54
|
+
const remainingText = substringText.slice(2);
|
|
55
|
+
const textWithoutSpaces = remainingText.join('');
|
|
56
|
+
const reversed = reverseString(textWithoutSpaces);
|
|
67
57
|
return { value: reversed, spaces: reverseString(spaces) };
|
|
68
58
|
}
|
|
69
59
|
return { value: text, spaces: '' };
|
|
70
60
|
}
|
|
71
|
-
export
|
|
61
|
+
export const markdownHelpHeaders = [
|
|
72
62
|
{
|
|
73
63
|
format: MarkdownFormat.h2,
|
|
74
64
|
tid: MarkdownTids.HeadingH2,
|
|
75
65
|
node: (React.createElement(React.Fragment, null,
|
|
76
66
|
React.createElement(MarkdownSymbolWrapper, null, "##"),
|
|
77
67
|
" \u0417\u0430\u0433\u043E\u043B\u043E\u0432\u043E\u043A 2")),
|
|
78
|
-
wrapContent:
|
|
68
|
+
wrapContent: (content) => `## ${content}`,
|
|
79
69
|
text: 'Заголовок 2',
|
|
80
70
|
},
|
|
81
71
|
{
|
|
@@ -84,7 +74,7 @@ export var markdownHelpHeaders = [
|
|
|
84
74
|
node: (React.createElement(React.Fragment, null,
|
|
85
75
|
React.createElement(MarkdownSymbolWrapper, null, "###"),
|
|
86
76
|
" \u0417\u0430\u0433\u043E\u043B\u043E\u0432\u043E\u043A 3")),
|
|
87
|
-
wrapContent:
|
|
77
|
+
wrapContent: (content) => `### ${content}`,
|
|
88
78
|
text: 'Заголовок 3',
|
|
89
79
|
},
|
|
90
80
|
{
|
|
@@ -93,11 +83,11 @@ export var markdownHelpHeaders = [
|
|
|
93
83
|
node: (React.createElement(React.Fragment, null,
|
|
94
84
|
React.createElement(MarkdownSymbolWrapper, null, "####"),
|
|
95
85
|
" \u0417\u0430\u0433\u043E\u043B\u043E\u0432\u043E\u043A 4")),
|
|
96
|
-
wrapContent:
|
|
86
|
+
wrapContent: (content) => `#### ${content}`,
|
|
97
87
|
text: 'Заголовок 4',
|
|
98
88
|
},
|
|
99
89
|
];
|
|
100
|
-
export
|
|
90
|
+
export const markdownHelpText = [
|
|
101
91
|
{
|
|
102
92
|
format: MarkdownFormat.bold,
|
|
103
93
|
tid: MarkdownTids.Bold,
|
|
@@ -106,7 +96,7 @@ export var markdownHelpText = [
|
|
|
106
96
|
"\u0416\u0438\u0440\u043D\u044B\u0439",
|
|
107
97
|
React.createElement(MarkdownSymbolWrapper, null, "**"))),
|
|
108
98
|
icon: React.createElement("strong", null, "B"),
|
|
109
|
-
wrapContent:
|
|
99
|
+
wrapContent: (content) => `**${content}**`,
|
|
110
100
|
text: 'Жирный',
|
|
111
101
|
checkLength: 2,
|
|
112
102
|
},
|
|
@@ -118,7 +108,7 @@ export var markdownHelpText = [
|
|
|
118
108
|
"\u041A\u0443\u0440\u0441\u0438\u0432",
|
|
119
109
|
React.createElement(MarkdownSymbolWrapper, null, "*"))),
|
|
120
110
|
icon: React.createElement(I, null, "I"),
|
|
121
|
-
wrapContent:
|
|
111
|
+
wrapContent: (content) => `*${content}*`,
|
|
122
112
|
text: 'Курсив',
|
|
123
113
|
checkLength: 1,
|
|
124
114
|
},
|
|
@@ -132,7 +122,7 @@ export var markdownHelpText = [
|
|
|
132
122
|
icon: (React.createElement("span", { style: {
|
|
133
123
|
textDecoration: 'line-through',
|
|
134
124
|
} }, "S")),
|
|
135
|
-
wrapContent:
|
|
125
|
+
wrapContent: (content) => `~~${content}~~`,
|
|
136
126
|
text: 'Зачеркнутый',
|
|
137
127
|
checkLength: 2,
|
|
138
128
|
},
|
|
@@ -147,12 +137,12 @@ export var markdownHelpText = [
|
|
|
147
137
|
"\u0421\u0441\u044B\u043B\u043A\u0430",
|
|
148
138
|
React.createElement(MarkdownSymbolWrapper, null, ")"))),
|
|
149
139
|
icon: React.createElement(AttachLink, null),
|
|
150
|
-
wrapContent:
|
|
140
|
+
wrapContent: (content) => `[${content}]()`,
|
|
151
141
|
text: 'Ссылка',
|
|
152
142
|
checkLength: 1,
|
|
153
143
|
},
|
|
154
144
|
];
|
|
155
|
-
export
|
|
145
|
+
export const markdownHelpLists = [
|
|
156
146
|
{
|
|
157
147
|
format: MarkdownFormat.list,
|
|
158
148
|
tid: MarkdownTids.List,
|
|
@@ -160,7 +150,7 @@ export var markdownHelpLists = [
|
|
|
160
150
|
React.createElement(MarkdownSymbolWrapper, null, "*"),
|
|
161
151
|
" \u0421\u043F\u0438\u0441\u043E\u043A")),
|
|
162
152
|
icon: React.createElement(List, null),
|
|
163
|
-
wrapContent:
|
|
153
|
+
wrapContent: (content) => getList(content, '*'),
|
|
164
154
|
text: 'Список',
|
|
165
155
|
},
|
|
166
156
|
{
|
|
@@ -170,7 +160,7 @@ export var markdownHelpLists = [
|
|
|
170
160
|
React.createElement(MarkdownSymbolWrapper, null, "* [x]"),
|
|
171
161
|
" \u0421\u043F\u0438\u0441\u043E\u043A - \u0432\u044B\u043F\u043E\u043B\u043D\u0435\u043D\u043E")),
|
|
172
162
|
icon: React.createElement(CheckedList, null),
|
|
173
|
-
wrapContent:
|
|
163
|
+
wrapContent: (content) => getList(content, '* [x]'),
|
|
174
164
|
text: 'Список - выполнено',
|
|
175
165
|
},
|
|
176
166
|
{
|
|
@@ -180,11 +170,11 @@ export var markdownHelpLists = [
|
|
|
180
170
|
React.createElement(MarkdownSymbolWrapper, null, "1. "),
|
|
181
171
|
" \u0421\u043F\u0438\u0441\u043E\u043A - \u043D\u0443\u043C\u0435\u0440\u043E\u0432\u0430\u043D\u043D\u044B\u0439")),
|
|
182
172
|
icon: React.createElement(NumberedList, null),
|
|
183
|
-
wrapContent:
|
|
173
|
+
wrapContent: (content) => getList(content, 1),
|
|
184
174
|
text: 'Список - нумерованный',
|
|
185
175
|
},
|
|
186
176
|
];
|
|
187
|
-
export
|
|
177
|
+
export const markdownHelpOther = [
|
|
188
178
|
{
|
|
189
179
|
format: MarkdownFormat.codeBlock,
|
|
190
180
|
tid: MarkdownTids.CodeBlock,
|
|
@@ -193,7 +183,7 @@ export var markdownHelpOther = [
|
|
|
193
183
|
"\u0411\u043B\u043E\u043A \u043A\u043E\u0434\u0430",
|
|
194
184
|
React.createElement(MarkdownSymbolWrapper, null, "`"))),
|
|
195
185
|
icon: React.createElement("span", null, '</>'),
|
|
196
|
-
wrapContent:
|
|
186
|
+
wrapContent: (content) => `\`${content}\``,
|
|
197
187
|
text: 'Блок кода',
|
|
198
188
|
checkLength: 1,
|
|
199
189
|
},
|
|
@@ -206,7 +196,7 @@ export var markdownHelpOther = [
|
|
|
206
196
|
" "),
|
|
207
197
|
" \u0426\u0438\u0442\u0430\u0442\u0430")),
|
|
208
198
|
icon: '>',
|
|
209
|
-
wrapContent:
|
|
199
|
+
wrapContent: (content) => `> ${content}`,
|
|
210
200
|
text: 'Цитата',
|
|
211
201
|
},
|
|
212
202
|
{
|
|
@@ -214,11 +204,14 @@ export var markdownHelpOther = [
|
|
|
214
204
|
tid: MarkdownTids.Table,
|
|
215
205
|
node: 'Таблица',
|
|
216
206
|
icon: React.createElement(Table, null),
|
|
217
|
-
wrapContent:
|
|
207
|
+
wrapContent: () => `| Заголовок | Заголовок |
|
|
208
|
+
| ------ | ------ |
|
|
209
|
+
| Ячейка | Ячейка |
|
|
210
|
+
| Ячейка | Ячейка |`,
|
|
218
211
|
text: 'Таблица',
|
|
219
212
|
},
|
|
220
213
|
];
|
|
221
|
-
export
|
|
214
|
+
export const markdownHelpFiles = (fileApiUrl) => {
|
|
222
215
|
if (!fileApiUrl)
|
|
223
216
|
return [];
|
|
224
217
|
return [
|
|
@@ -227,7 +220,7 @@ export var markdownHelpFiles = function (fileApiUrl) {
|
|
|
227
220
|
tid: MarkdownTids.AttachFile,
|
|
228
221
|
node: '',
|
|
229
222
|
icon: React.createElement("span", null),
|
|
230
|
-
wrapContent:
|
|
223
|
+
wrapContent: file => ``,
|
|
231
224
|
text: 'Картинка',
|
|
232
225
|
},
|
|
233
226
|
{
|
|
@@ -235,23 +228,28 @@ export var markdownHelpFiles = function (fileApiUrl) {
|
|
|
235
228
|
tid: MarkdownTids.AttachFile,
|
|
236
229
|
node: '',
|
|
237
230
|
icon: React.createElement(AttachPaperclip, null),
|
|
238
|
-
wrapContent:
|
|
231
|
+
wrapContent: file => `[${file.caption}](${fileApiUrl}${file.id})`,
|
|
239
232
|
text: 'Файл',
|
|
240
233
|
},
|
|
241
234
|
];
|
|
242
235
|
};
|
|
243
|
-
export
|
|
236
|
+
export const markdownHelpItems = [
|
|
237
|
+
...markdownHelpHeaders,
|
|
238
|
+
...markdownHelpText,
|
|
239
|
+
...markdownHelpLists,
|
|
240
|
+
...markdownHelpOther,
|
|
241
|
+
];
|
|
244
242
|
function getList(value, symbol) {
|
|
245
|
-
|
|
246
|
-
|
|
243
|
+
const splitValueWithNewLines = value.split(newLinesRegexp);
|
|
244
|
+
const numberList = [];
|
|
247
245
|
return splitValueWithNewLines
|
|
248
|
-
.map(
|
|
246
|
+
.map((v, idx) => {
|
|
249
247
|
if (!newLinesRegexp.test(v)) {
|
|
250
248
|
if (typeof symbol === 'number') {
|
|
251
249
|
numberList.push(idx);
|
|
252
|
-
return
|
|
250
|
+
return `${numberList.length}. ${v}`;
|
|
253
251
|
}
|
|
254
|
-
return
|
|
252
|
+
return `${symbol} ${v}`;
|
|
255
253
|
}
|
|
256
254
|
return v;
|
|
257
255
|
})
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { EmptyPreviewArrow } from '../../MarkdownIcons/EmptyPrviewArrow';
|
|
3
3
|
import { EmptyPreviewContainer, EmptyPreviewIconWrapper, EmptyPreviewText } from '../Markdown.styled';
|
|
4
|
-
export
|
|
4
|
+
export const EmptyPreview = () => {
|
|
5
5
|
return (React.createElement(EmptyPreviewContainer, null,
|
|
6
6
|
React.createElement(EmptyPreviewText, null, "\u041D\u0430\u0447\u043D\u0438 \u0432\u0432\u043E\u0434\u0438\u0442\u044C \u0442\u0435\u043A\u0441\u0442 \u0432 \u0440\u0435\u0434\u0430\u043A\u0442\u043E\u0440\u0435 "),
|
|
7
7
|
React.createElement(EmptyPreviewIconWrapper, null,
|
|
@@ -2,16 +2,15 @@ import { Hint } from '@skbkontur/react-ui';
|
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import { MarkdownCombination } from '../../MarkdownCombination/MarkdownCombination';
|
|
4
4
|
import { MarkdownButtonContentWrapper, MarkdownButtonIcon, MarkdownButtonWrapper, VisuallyHidden, } from '../Markdown.styled';
|
|
5
|
-
export
|
|
6
|
-
|
|
7
|
-
var button = (React.createElement(MarkdownButtonWrapper, { borderless: true, disabled: disabled, "data-tid": dataTid, onClick: onClick },
|
|
5
|
+
export const MarkdownFormatButton = ({ dataTid, icon, hintText, onClick, format, disabled, text, href, showActionHint, showHintWhenDisabled, showShortKey, showText, hintPos, }) => {
|
|
6
|
+
const button = (React.createElement(MarkdownButtonWrapper, { borderless: true, disabled: disabled, "data-tid": dataTid, onClick: onClick },
|
|
8
7
|
React.createElement(MarkdownButtonContentWrapper, null,
|
|
9
8
|
!!icon && React.createElement(MarkdownButtonIcon, null, icon),
|
|
10
9
|
showText ? text : React.createElement(VisuallyHidden, null, text))));
|
|
11
|
-
|
|
10
|
+
const content = href ? (React.createElement("a", { href: href, tabIndex: -1, target: "_blank", rel: "noopener noreferrer nofollow" }, button)) : (button);
|
|
12
11
|
if (!showActionHint && !showShortKey)
|
|
13
12
|
return content;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
return (React.createElement(Hint, { manual: !showHintWhenDisabled && disabled, text: hintComponent, pos: hintPos
|
|
13
|
+
const actualHintText = showActionHint && hintText;
|
|
14
|
+
const hintComponent = format ? (React.createElement(MarkdownCombination, { format: format, text: actualHintText, showShortKey: showShortKey })) : (actualHintText);
|
|
15
|
+
return (React.createElement(Hint, { manual: !showHintWhenDisabled && disabled, text: hintComponent, pos: hintPos ?? 'top center', maxWidth: 360 }, content));
|
|
17
16
|
};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export
|
|
2
|
-
export
|
|
1
|
+
export const MENU_ITEM_HOVERED_STATE_DATA_ATTRIBUTE = '[data-visual-state-hover]';
|
|
2
|
+
export const EMPTY_AVATAR = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAANTSURBVFhH7Zg/aBNRHMebXEv+CFKxSusgBAcXRdDJWnA4qAVdRMzgIogScCldmj8dQighoRgh/hnataCSSXRyCA7BQS0OdVGHgrGCOqQiUmNiEr+v960Ycum9dy9m8gPH789d775573fv/a4D/9HEQ6tFLpcL1Gq1Cx6Px9dsNj/CvonFYu9hW7zENdoCk8mk1+/3ZyBmlqktWq3WOkwB+TzElq2sOq4Ezs/PHzIMY9br9U5CyBhEDCHttc52UMU1sXg8nmeshEErTTabnYCwEo5xhMMQNwi70w8dxDVTpmkaxWLxKXPSKI1gPp/3bW5uvsMDDzIlDUaxhR9lRqNRJZHdpsWWarV6xo04Af7OgxcoxVAaJYF4wEm6bplYWFgYpS+FkkAwRusKMYqNRuMEQymUBKKG6Gmxl1YK1SneoKvDD1oppAVieREvxx4r0mIUi/tu+o6ojOAKSugyfdfgHrcCgcBdho5ICSwUCmJB32dF+ojdh64jUgLD4XADN/3GUBuMYoWuI9JTjJu+oNsLpO+lIvA2jHb7BDawFi7Td0RaIPbQR1hmpjDVaaaUECWC4zrEjc/NzX1m2hHldmtxcXGoUqmsY0T3MyUFxN1Hy3WJoTTSI7hNJBKpw6j2dk3sQjfpK6EsUBAMBnMwL63IGYzeDZTICkMlXAmcnp7+iVo6iwe/ZaoruOYe2rQ4Q2W0vknE9gcBr1GP3bau0tramrm0tCTKwhWuRnAb8TEEcV8Y2rGqI06gJZD8orVjp3NSaAlkd9y1v8P0H0bnIj6qXOOqBkW7hI7kKtwEDqcGdBVlkAyFQo/Fns6cNNICIWoEy4uJUTmH4zweuounpMDfiA/5Bzie4D4lsRJsnXBgR4GizcJbeBHuNTzgNEQpf0d34TuOh9g67yQSiedWyp6uAjOZzDGYZYg6amX+DfjhBb/fH5mZmfnKVBu2AtPp9BHDMJ7BlW7NdYDIV1jMT6VSqSpTf7B9i7Fvio6lL+IEmKXjPp/vCsM2OgSyvTetqH9gUCbpttEhsFwuH1B9Q3uEba13CKzX6yN0+wrqcJhuG3Y1qPTl30NqtG10CEQtBOn2FZSV7Zdeh0Asnq62vx7wibYNuynuxf9flEENfqD7FwMDvwH9BxCkLLaB+AAAAABJRU5ErkJggg==';
|
|
@@ -2,6 +2,7 @@ import { Textarea } from '@skbkontur/react-ui';
|
|
|
2
2
|
import { KeyboardEvent as ReactKeyboardEvent, RefObject } from 'react';
|
|
3
3
|
import { MarkdownFormat } from '../MarkdownFormat';
|
|
4
4
|
import { Nullable, RefItem } from '../types';
|
|
5
|
+
export { handleMarkdownListEnter } from './markdownListEnterHelpers';
|
|
5
6
|
export declare function setMarkdown(textareaNode: HTMLTextAreaElement, text: string, format: MarkdownFormat, selectionStart: number, selectionEnd?: number | null): void;
|
|
6
7
|
export declare function setMarkdownFiles(file: RefItem, textarea: Textarea, format: MarkdownFormat, cursorPosition?: number | null, fileApiUrl?: string): void;
|
|
7
8
|
export declare function setMarkdownPastedHtml(text: string, textareaNode: HTMLTextAreaElement): void;
|
|
@@ -1,94 +1,97 @@
|
|
|
1
1
|
import { useEffect } from 'react';
|
|
2
|
+
import { handleMarkdownListEnter } from './markdownListEnterHelpers';
|
|
2
3
|
import { getPastedHtml } from './markdownTextareaHelpers';
|
|
3
4
|
import { useFileLogic } from '../Files/Files.logic';
|
|
4
5
|
import { MarkdownFormat } from '../MarkdownFormat';
|
|
5
6
|
import { checkSpaceSymbol, eventKeyCodeToMarkdownFormat, markdownFormatToShortKeyLong, markdownHelpFiles, markdownHelpItems, } from '../MarkdownHelpItems';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
import { onInsertText } from '../utils/onInsertText';
|
|
8
|
+
const { italic, bold, crossed, codeBlock, ref, file, image } = MarkdownFormat;
|
|
9
|
+
const betweenTextFormats = [italic, bold, crossed, codeBlock, file, image];
|
|
10
|
+
const specialFormats = [ref];
|
|
11
|
+
export { handleMarkdownListEnter } from './markdownListEnterHelpers';
|
|
9
12
|
export function setMarkdown(textareaNode, text, format, selectionStart, selectionEnd) {
|
|
10
|
-
|
|
11
|
-
var markdownHelpItem = markdownHelpItems.find(function (item) { return item.format === format; });
|
|
13
|
+
const markdownHelpItem = markdownHelpItems.find(item => item.format === format);
|
|
12
14
|
if (markdownHelpItem) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
setTextareaCursor(format, prevCommentPart.length, nextCommentPart.length, textareaNode, (selectionEnd
|
|
18
|
-
(
|
|
15
|
+
const prevCommentPart = text.substring(selectionStart, selectionEnd ?? undefined);
|
|
16
|
+
const { value, spaces } = checkSpaceSymbol(prevCommentPart, markdownHelpItem.checkLength);
|
|
17
|
+
const nextCommentPart = markdownHelpItem.wrapContent(value) + spaces;
|
|
18
|
+
onInsertText(nextCommentPart);
|
|
19
|
+
setTextareaCursor(format, prevCommentPart.length, nextCommentPart.length, textareaNode, (selectionEnd ?? 0) +
|
|
20
|
+
(markdownHelpItem?.format === MarkdownFormat.ref ? 0 : markdownHelpItem?.checkLength ?? 0) -
|
|
19
21
|
spaces.length);
|
|
20
22
|
}
|
|
21
23
|
}
|
|
22
24
|
export function setMarkdownFiles(file, textarea, format, cursorPosition, fileApiUrl) {
|
|
23
|
-
|
|
24
|
-
var markdownHelpItem = markdownHelpFiles(fileApiUrl).find(function (item) { return item.format === format; });
|
|
25
|
+
const markdownHelpItem = markdownHelpFiles(fileApiUrl).find(item => item.format === format);
|
|
25
26
|
if (markdownHelpItem) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
const textareaNode = textarea.node;
|
|
28
|
+
const currentCursorPosition = cursorPosition ?? textareaNode.selectionStart ?? textareaNode?.value?.length;
|
|
29
|
+
const nextCommentPart = markdownHelpItem.wrapContent(file);
|
|
30
|
+
const newCursorPosition = currentCursorPosition + nextCommentPart.length;
|
|
30
31
|
textareaNode.selectionStart = currentCursorPosition;
|
|
31
32
|
textareaNode.focus();
|
|
32
|
-
|
|
33
|
+
onInsertText(nextCommentPart);
|
|
33
34
|
textareaNode.selectionStart = newCursorPosition;
|
|
34
35
|
textareaNode.selectionEnd = newCursorPosition;
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
38
|
export function setMarkdownPastedHtml(text, textareaNode) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
var newCursorPosition = currentCursorPosition + text.length;
|
|
39
|
+
const selectionStart = textareaNode.selectionStart;
|
|
40
|
+
const currentCursorPosition = selectionStart ?? textareaNode?.value?.length ?? 0;
|
|
41
|
+
const newCursorPosition = currentCursorPosition + text.length;
|
|
42
42
|
textareaNode.selectionStart = currentCursorPosition;
|
|
43
43
|
textareaNode.focus();
|
|
44
|
-
|
|
44
|
+
onInsertText(text);
|
|
45
45
|
textareaNode.selectionStart = newCursorPosition;
|
|
46
46
|
textareaNode.selectionEnd = newCursorPosition;
|
|
47
47
|
}
|
|
48
48
|
export function setTextareaCursor(format, prevCommentPartLength, nextCommentPartLength, textareaNode, selectionEnd) {
|
|
49
49
|
if (betweenTextFormats.includes(format)) {
|
|
50
|
-
|
|
50
|
+
const formatCenterPosition = (nextCommentPartLength - prevCommentPartLength) / 2;
|
|
51
51
|
textareaNode.selectionStart = selectionEnd + formatCenterPosition;
|
|
52
52
|
textareaNode.selectionEnd = selectionEnd + formatCenterPosition;
|
|
53
53
|
}
|
|
54
54
|
if (specialFormats.includes(format)) {
|
|
55
|
-
|
|
55
|
+
const nextCommentCursorPosition = nextCommentPartLength - 1;
|
|
56
56
|
textareaNode.selectionStart = selectionEnd - prevCommentPartLength + nextCommentCursorPosition;
|
|
57
57
|
textareaNode.selectionEnd = selectionEnd - prevCommentPartLength + nextCommentCursorPosition;
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
export function createMarkdownHelpKeyDownHandler(text, ref, callback) {
|
|
61
|
-
return
|
|
62
|
-
if (!
|
|
61
|
+
return (event) => {
|
|
62
|
+
if (!ref?.current)
|
|
63
63
|
return;
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
const textareaNode = ref.current.node;
|
|
65
|
+
const format = eventKeyCodeToMarkdownFormat[event.code];
|
|
66
|
+
const isLong = markdownFormatToShortKeyLong[format];
|
|
67
67
|
if ((event.metaKey || event.ctrlKey) && (isLong ? event.shiftKey : true) && format) {
|
|
68
|
-
|
|
68
|
+
const markdownHelpItem = markdownHelpItems.find(item => item.format === format);
|
|
69
69
|
if (markdownHelpItem && textareaNode) {
|
|
70
70
|
event.stopPropagation();
|
|
71
71
|
event.preventDefault();
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
72
|
+
const [start, end] = [textareaNode.selectionStart, textareaNode.selectionEnd];
|
|
73
|
+
const prevCommentPart = text.substring(start, end);
|
|
74
|
+
const nextCommentPart = markdownHelpItem.wrapContent(prevCommentPart);
|
|
75
|
+
onInsertText(nextCommentPart);
|
|
76
76
|
setTextareaCursor(format, prevCommentPart.length, nextCommentPart.length, textareaNode, end);
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
|
-
|
|
79
|
+
handleMarkdownListEnter(event, textareaNode);
|
|
80
|
+
callback?.(event);
|
|
80
81
|
};
|
|
81
82
|
}
|
|
82
|
-
export
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
useEffect(
|
|
83
|
+
export const usePasteFromClipboard = (textarea, uploadFileApi, downloadFileApi, fileApiUrl) => {
|
|
84
|
+
const { uploadFile } = useFileLogic(uploadFileApi, downloadFileApi, fileApiUrl, textarea);
|
|
85
|
+
const textareaNode = textarea?.node;
|
|
86
|
+
useEffect(() => {
|
|
86
87
|
if (downloadFileApi && uploadFileApi) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
88
|
+
const handlePaste = (event) => {
|
|
89
|
+
const files = event?.clipboardData?.files;
|
|
90
|
+
const html = event?.clipboardData
|
|
91
|
+
?.getData('text/html')
|
|
92
|
+
?.replace(`<meta charset='utf-8'>`, '')
|
|
93
|
+
?.replace(`<meta charset="utf-8">`, '');
|
|
94
|
+
if (files?.length) {
|
|
92
95
|
event.preventDefault();
|
|
93
96
|
void uploadFile(files[0]);
|
|
94
97
|
return;
|
|
@@ -99,9 +102,9 @@ export var usePasteFromClipboard = function (textarea, uploadFileApi, downloadFi
|
|
|
99
102
|
}
|
|
100
103
|
};
|
|
101
104
|
if (textareaNode) {
|
|
102
|
-
textareaNode.addEventListener('paste',
|
|
105
|
+
textareaNode.addEventListener('paste', handlePaste);
|
|
103
106
|
}
|
|
104
|
-
return
|
|
107
|
+
return () => textareaNode?.removeEventListener('paste', handlePaste);
|
|
105
108
|
}
|
|
106
109
|
}, [downloadFileApi, textareaNode, uploadFile, uploadFileApi]);
|
|
107
110
|
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { onInsertText } from '../utils/onInsertText';
|
|
2
|
+
const listItemRegExp = /^(?<spacesBeforeMarker> *)(?:(?<orderedListNumber>\d+)(?<orderedListDelimiter>[.)])|(?<unorderedListMarker>[*+-])(?: +(?<checkboxListMarker>\[[ xX]]))?)(?: +(?<text>.*)|$)$/;
|
|
3
|
+
export function handleMarkdownListEnter(event, textareaNode) {
|
|
4
|
+
const { selectionStart, selectionEnd, value } = textareaNode;
|
|
5
|
+
const isOnlyDownEnter = event.key === 'Enter' && !event.shiftKey && !event.altKey && !event.metaKey && !event.ctrlKey;
|
|
6
|
+
if (!isOnlyDownEnter || selectionStart !== selectionEnd)
|
|
7
|
+
return;
|
|
8
|
+
/*
|
|
9
|
+
* Находим начало текущей строки:
|
|
10
|
+
* ищем последний перенос строки перед курсором
|
|
11
|
+
* и берём позицию сразу после него
|
|
12
|
+
*/
|
|
13
|
+
const currentLineStartIndex = value.lastIndexOf('\n', selectionStart - 1) + 1;
|
|
14
|
+
/* Находим конец текущей строки */
|
|
15
|
+
const currentLineEndIndex = getCurrentLineEndIndex(value, selectionStart);
|
|
16
|
+
const currentLineText = value.slice(currentLineStartIndex, currentLineEndIndex);
|
|
17
|
+
const listLineMatch = currentLineText.match(listItemRegExp);
|
|
18
|
+
if (!listLineMatch?.groups)
|
|
19
|
+
return;
|
|
20
|
+
const { spacesBeforeMarker, orderedListNumber, orderedListDelimiter, unorderedListMarker, checkboxListMarker, text, } = listLineMatch.groups;
|
|
21
|
+
event.stopPropagation();
|
|
22
|
+
event.preventDefault();
|
|
23
|
+
if (!text?.trim()) {
|
|
24
|
+
textareaNode.setSelectionRange(currentLineStartIndex, currentLineEndIndex);
|
|
25
|
+
return document.execCommand('delete');
|
|
26
|
+
}
|
|
27
|
+
if (orderedListNumber && orderedListDelimiter)
|
|
28
|
+
return onInsertText(`\n${spacesBeforeMarker}${Number(orderedListNumber) + 1}${orderedListDelimiter} `);
|
|
29
|
+
if (checkboxListMarker)
|
|
30
|
+
return onInsertText(`\n${spacesBeforeMarker}${unorderedListMarker} ${checkboxListMarker} `);
|
|
31
|
+
if (unorderedListMarker)
|
|
32
|
+
return onInsertText(`\n${spacesBeforeMarker}${unorderedListMarker} `);
|
|
33
|
+
}
|
|
34
|
+
function getCurrentLineEndIndex(text, cursorPosition) {
|
|
35
|
+
/* Ищем ближайший перенос строки после курсора */
|
|
36
|
+
const currentLineEndIndex = text.indexOf('\n', cursorPosition);
|
|
37
|
+
/*
|
|
38
|
+
* Если переноса строки после курсора нет,
|
|
39
|
+
* значит текущая строка последняя
|
|
40
|
+
* и заканчивается в конце всего текста
|
|
41
|
+
*/
|
|
42
|
+
if (currentLineEndIndex === -1)
|
|
43
|
+
return text.length;
|
|
44
|
+
return currentLineEndIndex;
|
|
45
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { useEffect } from 'react';
|
|
2
2
|
import { EMPTY_AVATAR, MENU_ITEM_HOVERED_STATE_DATA_ATTRIBUTE } from './constants';
|
|
3
3
|
import { getTextareaTokens } from './markdownTextareaHelpers';
|
|
4
|
-
|
|
4
|
+
const ArrowsVertical = ['ArrowUp', 'ArrowDown'];
|
|
5
5
|
export function mentionActions(event, setToken) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
const { value, selectionStart } = event.currentTarget;
|
|
7
|
+
const tokens = getTextareaTokens(value);
|
|
8
|
+
const mention = tokens.find(t => selectionStart >= t.positions[0] && selectionStart <= t.positions[1] && t.value.startsWith('@'));
|
|
9
9
|
if (mention) {
|
|
10
10
|
setToken(mention);
|
|
11
11
|
}
|
|
@@ -13,10 +13,10 @@ export function mentionActions(event, setToken) {
|
|
|
13
13
|
setToken(undefined);
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
|
-
export
|
|
17
|
-
useEffect(
|
|
18
|
-
|
|
19
|
-
if (menuRef
|
|
16
|
+
export const useMenuKeyListener = (onSelectUser, menuRef) => {
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
const keyListener = (event) => {
|
|
19
|
+
if (menuRef?.current) {
|
|
20
20
|
if (ArrowsVertical.includes(event.key)) {
|
|
21
21
|
if (event.key === 'ArrowUp') {
|
|
22
22
|
menuRef.current.up();
|
|
@@ -26,7 +26,7 @@ export var useMenuKeyListener = function (onSelectUser, menuRef) {
|
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
if (event.key === 'Enter') {
|
|
29
|
-
|
|
29
|
+
const hoveredElement = document.querySelector(MENU_ITEM_HOVERED_STATE_DATA_ATTRIBUTE);
|
|
30
30
|
if (hoveredElement) {
|
|
31
31
|
onSelectUser(Number(hoveredElement.id || 0));
|
|
32
32
|
}
|
|
@@ -34,12 +34,12 @@ export var useMenuKeyListener = function (onSelectUser, menuRef) {
|
|
|
34
34
|
}
|
|
35
35
|
};
|
|
36
36
|
window.addEventListener('keydown', keyListener);
|
|
37
|
-
return
|
|
37
|
+
return () => window.removeEventListener('keydown', keyListener);
|
|
38
38
|
}, [menuRef, onSelectUser]);
|
|
39
39
|
};
|
|
40
|
-
export
|
|
40
|
+
export const getMentionValue = (mention) => mention?.value?.replace('@', '') ?? '';
|
|
41
41
|
export function getAvatarUrl(sid) {
|
|
42
42
|
if (!sid)
|
|
43
43
|
return EMPTY_AVATAR;
|
|
44
|
-
return
|
|
44
|
+
return `https://staff.skbkontur.ru/api/avatar/${sid}?size=s}`;
|
|
45
45
|
}
|