@skbkontur/markdown 2.7.0-alpha.3 → 2.7.0-alpha.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.d.ts +1 -1
- package/package.json +1 -1
- package/src/Markdown/Emoji/EmojiDropdown.d.ts +1 -0
- package/src/Markdown/Emoji/EmojiDropdown.js +5 -2
- package/src/Markdown/Markdown.d.ts +4 -2
- package/src/Markdown/Markdown.js +4 -4
- package/src/Markdown/MarkdownActions/AIActionsDropdown/AIActionsDropdown.d.ts +1 -0
- package/src/Markdown/MarkdownActions/AIActionsDropdown/AIActionsDropdown.js +4 -1
- package/src/Markdown/MarkdownActions/MarkdownActions.d.ts +3 -2
- package/src/Markdown/MarkdownActions/MarkdownActions.js +24 -9
- package/src/Markdown/MarkdownEditor.d.ts +2 -1
- package/src/Markdown/MarkdownEditor.js +2 -2
- package/src/Markdown/MarkdownHelpers/markdownHelpers.d.ts +2 -2
- package/src/Markdown/MarkdownHelpers/markdownHelpers.js +3 -2
- package/src/Markdown/MarkdownHelpers/markdownListEnterHelpers.d.ts +2 -1
- package/src/Markdown/MarkdownHelpers/markdownListEnterHelpers.js +9 -3
- package/src/Markdown/types.d.ts +2 -1
package/index.d.ts
CHANGED
|
@@ -2,6 +2,6 @@ export { Markdown, MarkdownProps } from './src/Markdown/Markdown';
|
|
|
2
2
|
export { MarkdownCombination } from './src/MarkdownCombination/MarkdownCombination';
|
|
3
3
|
export { markdownHelpItems, markdownHelpFiles, markdownHelpLists, markdownHelpOther, } from './src/Markdown/MarkdownHelpItems';
|
|
4
4
|
export { MarkdownViewer } from './src/MarkdownViewer/MarkdownViewer';
|
|
5
|
-
export { MarkdownApi, RefItem, User, Token, HorizontalPaddings, ViewMode,
|
|
5
|
+
export { MarkdownApi, RefItem, User, Token, HorizontalPaddings, ViewMode, ActionsOptions, AIMethod, AIApi, TrackEventCategory, } from '././src/Markdown/types';
|
|
6
6
|
export { ThemeMode, ColorScheme } from './src/styles/types';
|
|
7
7
|
export { MarkdownTheme, MarkdownThemeProvider, MarkdownThemeConsumer, MarkdownThemeContext } from './src/styles/theme';
|
package/package.json
CHANGED
|
@@ -9,11 +9,14 @@ import { DEFAULT_MARKDOWN_THEME, MarkdownThemeConsumer } from '../../styles/them
|
|
|
9
9
|
import { MarkdownFormatButton } from '../MarkdownHelpers/MarkdownFormatButton';
|
|
10
10
|
import { MarkdownTids } from '../MarkdownTids';
|
|
11
11
|
emojiLocale.search = 'Поиск на английском';
|
|
12
|
-
export const EmojiDropdown = ({ isPreviewMode, showShortKey, onSelect, showActionHint }) => {
|
|
12
|
+
export const EmojiDropdown = ({ isPreviewMode, showShortKey, onSelect, showActionHint, onTrackEvent, }) => {
|
|
13
13
|
const dropdownMenuRef = useRef(null);
|
|
14
14
|
return (React.createElement(MarkdownThemeConsumer, null, theme => {
|
|
15
15
|
const currentTheme = theme ?? DEFAULT_MARKDOWN_THEME;
|
|
16
|
-
return (React.createElement(DropdownMenu, { ref: dropdownMenuRef, caption: ({ toggleMenu }) => (React.createElement(MarkdownFormatButton, { dataTid: MarkdownTids.Emoji, showShortKey: showShortKey, showActionHint: showActionHint, hintText: "Emoji", disabled: isPreviewMode, icon: React.createElement(EmojiFace, null), text: "Emoji", onClick:
|
|
16
|
+
return (React.createElement(DropdownMenu, { ref: dropdownMenuRef, caption: ({ toggleMenu }) => (React.createElement(MarkdownFormatButton, { dataTid: MarkdownTids.Emoji, showShortKey: showShortKey, showActionHint: showActionHint, hintText: "Emoji", disabled: isPreviewMode, icon: React.createElement(EmojiFace, null), text: "Emoji", onClick: () => {
|
|
17
|
+
onTrackEvent?.();
|
|
18
|
+
toggleMenu();
|
|
19
|
+
} })), menuWidth: 300, positions: ['bottom right', 'bottom left', 'top right', 'top right'] },
|
|
17
20
|
React.createElement(EmojiPickerWrapper, null,
|
|
18
21
|
React.createElement(EmojiPicker, { dynamicWidth: true, data: data, i18n: emojiLocale, locale: "ru", theme: currentTheme.themeMode, skinTonePosition: "none", previewPosition: "none", onEmojiSelect: handleSelectEmoji }))));
|
|
19
22
|
}));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { FC, ReactNode } from 'react';
|
|
2
2
|
import { MarkdownEditorProps } from './MarkdownEditor';
|
|
3
|
-
import {
|
|
3
|
+
import { ActionsOptions, TrackEventCategory, HorizontalPaddings, MarkdownApi, ViewMode } from './types';
|
|
4
4
|
export interface MarkdownProps extends MarkdownEditorProps {
|
|
5
5
|
/** Методы апи для загрузки/скачивания файлов, меншена, ИИ */
|
|
6
6
|
api?: MarkdownApi;
|
|
@@ -9,11 +9,13 @@ export interface MarkdownProps extends MarkdownEditorProps {
|
|
|
9
9
|
/** Url апи для файлов */
|
|
10
10
|
fileApiUrl?: string;
|
|
11
11
|
/** Скрывать выборочно опции */
|
|
12
|
-
hideActionsOptions?:
|
|
12
|
+
hideActionsOptions?: ActionsOptions;
|
|
13
13
|
/** Превьювер мардауна, по умолчанию используется MarkdownViewer */
|
|
14
14
|
markdownViewer?: (value: string) => ReactNode;
|
|
15
15
|
/** Колбек, срабатывает на изменение режима редактирования или просмотра */
|
|
16
16
|
onChangeViewMode?: (mode: ViewMode) => void;
|
|
17
|
+
/** Колбек для отправки метрик */
|
|
18
|
+
onTrackEvent?: (category: TrackEventCategory, action: 'click' | 'hotkey', label?: string) => void;
|
|
17
19
|
/** Padding markdownActions (кнопки помощи форматирования текста), включает режим panel */
|
|
18
20
|
panelHorizontalPadding?: number;
|
|
19
21
|
/** Url для профиля сотрудника */
|
package/src/Markdown/Markdown.js
CHANGED
|
@@ -13,7 +13,7 @@ import { usePasteFromClipboard } from './MarkdownHelpers/markdownHelpers';
|
|
|
13
13
|
import { getMentionValue, mentionActions } from './MarkdownHelpers/markdownMentionHelpers';
|
|
14
14
|
import { getCursorCoordinates, useFullscreenHorizontalPadding, useListenTextareaScroll, } from './MarkdownHelpers/markdownTextareaHelpers';
|
|
15
15
|
import { MarkdownMention } from './MarkdownMention';
|
|
16
|
-
import { ViewMode } from './types';
|
|
16
|
+
import { ViewMode, } from './types';
|
|
17
17
|
import { Guid } from './utils/guid';
|
|
18
18
|
import { onInsertText } from './utils/onInsertText';
|
|
19
19
|
import { RequestStatus } from './utils/requestStatus';
|
|
@@ -21,7 +21,7 @@ import { MarkdownViewer } from '../MarkdownViewer';
|
|
|
21
21
|
import { ThemeProvider } from '../styles/styled-components';
|
|
22
22
|
import { DEFAULT_MARKDOWN_THEME, MarkdownThemeConsumer } from '../styles/theme';
|
|
23
23
|
export const Markdown = props => {
|
|
24
|
-
const { panelHorizontalPadding, onClick, onChange, onSelect, markdownViewer, renderFilesValidation, fileApiUrl, profileUrl, api, borderless, showActionHints, showShortKeys, showShotKeys = true, hideActionsOptions, onChangeViewMode, ...textareaProps } = props;
|
|
24
|
+
const { panelHorizontalPadding, onClick, onChange, onSelect, markdownViewer, renderFilesValidation, fileApiUrl, profileUrl, api, borderless, showActionHints, showShortKeys, showShotKeys = true, hideActionsOptions, onChangeViewMode, onTrackEvent, ...textareaProps } = props;
|
|
25
25
|
const [mention, setMention] = useState();
|
|
26
26
|
const [viewMode, setViewMode] = useState(ViewMode.Edit);
|
|
27
27
|
const [fullscreen, setFullScreen] = useState(false);
|
|
@@ -84,7 +84,7 @@ export const Markdown = props => {
|
|
|
84
84
|
const content = (React.createElement(Foco, { component: "div", onClickOutside: resetStates },
|
|
85
85
|
React.createElement(Wrapper, { ...getRootProps() },
|
|
86
86
|
React.createElement("input", { ...getInputProps() }),
|
|
87
|
-
!hideActionsOptions?.allActions && (React.createElement(MarkdownActions, { showActionHints: showActionHints !== undefined ? showActionHints : showShotKeys, showShortKeys: showShortKeys !== undefined ? showShortKeys : showShotKeys, textAreaRef: textareaRef, width: width, viewMode: viewMode, loadingFile: requestStatus === RequestStatus.isFetching, fullscreen: fullscreen, selectionStart: selectionStart, selectionEnd: selectionEnd, horizontalPaddings: horizontalPaddings, hideOptions: hideActionsOptions, hasFilesApi: !!api?.fileDownloadApi && !!api?.fileUploadApi, isSplitViewAvailable: isSplitViewAvailable, disableFullscreen: isMobile, AIApi: api?.AIApi, onOpenFileDialog: open, onChangeViewMode: handleChangeViewMode, onClickFullscreen: handleClickFullscreen, onSelectEmoji: onSelectEmoji })),
|
|
87
|
+
!hideActionsOptions?.allActions && (React.createElement(MarkdownActions, { showActionHints: showActionHints !== undefined ? showActionHints : showShotKeys, showShortKeys: showShortKeys !== undefined ? showShortKeys : showShotKeys, textAreaRef: textareaRef, width: width, viewMode: viewMode, loadingFile: requestStatus === RequestStatus.isFetching, fullscreen: fullscreen, selectionStart: selectionStart, selectionEnd: selectionEnd, horizontalPaddings: horizontalPaddings, hideOptions: hideActionsOptions, hasFilesApi: !!api?.fileDownloadApi && !!api?.fileUploadApi, isSplitViewAvailable: isSplitViewAvailable, disableFullscreen: isMobile, AIApi: api?.AIApi, onTrackEvent: onTrackEvent, onOpenFileDialog: open, onChangeViewMode: handleChangeViewMode, onClickFullscreen: handleClickFullscreen, onSelectEmoji: onSelectEmoji })),
|
|
88
88
|
isEditMode && error && api?.getUsersApi && renderFilesValidation?.(horizontalPaddings, onResetError),
|
|
89
89
|
fullscreen && viewMode === ViewMode.Split && !fullscreenTextareaPadding && (React.createElement(SplitViewContainer, null,
|
|
90
90
|
React.createElement(SplitViewEditContainer, null, renderEditContainer()),
|
|
@@ -110,7 +110,7 @@ export const Markdown = props => {
|
|
|
110
110
|
return (React.createElement(MarkdownEditorBlock, null,
|
|
111
111
|
React.createElement(MentionWrapper, { id: `${guid}${MENTION_WRAPPER_ID_POSTFIX}` }),
|
|
112
112
|
showMention && renderMentions(),
|
|
113
|
-
React.createElement(MarkdownEditor, { ...textareaProps, maxRows: fullscreen ? undefined : textareaProps.maxRows, width: width, textareaRef: textareaRef, onChange: listenChange, onSelect: listenSelect, onClick: listenClick })));
|
|
113
|
+
React.createElement(MarkdownEditor, { ...textareaProps, maxRows: fullscreen ? undefined : textareaProps.maxRows, width: width, textareaRef: textareaRef, onChange: listenChange, onSelect: listenSelect, onClick: listenClick, onTrackEvent: onTrackEvent })));
|
|
114
114
|
}
|
|
115
115
|
function renderPreview() {
|
|
116
116
|
if (!props.value && viewMode === ViewMode.Split)
|
|
@@ -10,7 +10,7 @@ import { Guid } from '../../utils/guid';
|
|
|
10
10
|
import { onInsertText } from '../../utils/onInsertText';
|
|
11
11
|
import { RequestStatus } from '../../utils/requestStatus';
|
|
12
12
|
import { MarkdownDropdown } from '../MarkdownDropdown/MarkdownDropdown';
|
|
13
|
-
export const AIActionsDropdown = ({ textareaRef, isPreviewMode, showActionHint, api }) => {
|
|
13
|
+
export const AIActionsDropdown = ({ textareaRef, isPreviewMode, showActionHint, api, onTrackEvent }) => {
|
|
14
14
|
const [processedText, setProcessedText] = useState();
|
|
15
15
|
const [requestStatus, setRequestStatus] = useState(RequestStatus.Default);
|
|
16
16
|
const tooltipRef = useRef(null);
|
|
@@ -41,6 +41,7 @@ export const AIActionsDropdown = ({ textareaRef, isPreviewMode, showActionHint,
|
|
|
41
41
|
React.createElement(Button, { "aria-label": COPY_BUTTON_TEXT, icon: React.createElement(Copy, null), onMouseDown: e => e.preventDefault(), onClick: handleCopyText }))))));
|
|
42
42
|
}
|
|
43
43
|
async function handleProcessText(method) {
|
|
44
|
+
onTrackEvent?.(method);
|
|
44
45
|
const taskId = taskIdRef.current.generate();
|
|
45
46
|
try {
|
|
46
47
|
tooltipRef?.current?.show();
|
|
@@ -60,10 +61,12 @@ export const AIActionsDropdown = ({ textareaRef, isPreviewMode, showActionHint,
|
|
|
60
61
|
}
|
|
61
62
|
}
|
|
62
63
|
async function handleCopyText() {
|
|
64
|
+
onTrackEvent?.('copyText');
|
|
63
65
|
await navigator.clipboard.writeText(processedText || '');
|
|
64
66
|
handleCloseTooltip();
|
|
65
67
|
}
|
|
66
68
|
function handleSetText() {
|
|
69
|
+
onTrackEvent?.('setText');
|
|
67
70
|
if (!textareaRef?.current)
|
|
68
71
|
return null;
|
|
69
72
|
textareaRef.current.focus();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Textarea } from '@skbkontur/react-ui';
|
|
2
2
|
import { FC, RefObject } from 'react';
|
|
3
3
|
import { EmojiData } from '../Emoji/Emoji.logic';
|
|
4
|
-
import { AIApi,
|
|
4
|
+
import { AIApi, ActionsOptions, TrackEventCategory, HorizontalPaddings, Nullable, ViewMode } from '../types';
|
|
5
5
|
interface Props {
|
|
6
6
|
horizontalPaddings: HorizontalPaddings;
|
|
7
7
|
onChangeViewMode: (viewMode: ViewMode) => void;
|
|
@@ -16,10 +16,11 @@ interface Props {
|
|
|
16
16
|
disableFullscreen?: boolean;
|
|
17
17
|
fullscreen?: boolean;
|
|
18
18
|
hasFilesApi?: boolean;
|
|
19
|
-
hideOptions?:
|
|
19
|
+
hideOptions?: ActionsOptions;
|
|
20
20
|
isFocused?: boolean;
|
|
21
21
|
isSplitViewAvailable?: boolean;
|
|
22
22
|
loadingFile?: boolean;
|
|
23
|
+
onTrackEvent?: (category: TrackEventCategory, action: 'click' | 'hotkey', label?: string) => void;
|
|
23
24
|
selectionEnd?: Nullable<number>;
|
|
24
25
|
selectionStart?: Nullable<number>;
|
|
25
26
|
width?: Nullable<number | string>;
|
|
@@ -12,12 +12,13 @@ import { ToolPencil } from '../../MarkdownIcons/ToolPencil';
|
|
|
12
12
|
import { COMMONMARK_HELP_URL } from '../constants';
|
|
13
13
|
import { EmojiDropdown } from '../Emoji/EmojiDropdown';
|
|
14
14
|
import { ActionsLeftWrapper, ActionsRightWrapper, ButtonsWrapper, MarkdownActionsWrapper, MarkdownMenuItem, ViewModeButtonsWrapper, } from '../Markdown.styled';
|
|
15
|
+
import { MarkdownFormat } from '../MarkdownFormat';
|
|
15
16
|
import { MarkdownFormatButton } from '../MarkdownHelpers/MarkdownFormatButton';
|
|
16
17
|
import { setMarkdown } from '../MarkdownHelpers/markdownHelpers';
|
|
17
18
|
import { markdownHelpHeaders, markdownHelpLists, markdownHelpOther, markdownHelpText } from '../MarkdownHelpItems';
|
|
18
19
|
import { MarkdownTids } from '../MarkdownTids';
|
|
19
20
|
import { ViewMode } from '../types';
|
|
20
|
-
export const MarkdownActions = ({ textAreaRef, selectionStart, selectionEnd, loadingFile, onOpenFileDialog, onClickFullscreen, fullscreen, viewMode, onChangeViewMode, horizontalPaddings, hasFilesApi, width, showActionHints, showShortKeys, hideOptions, onSelectEmoji, isSplitViewAvailable, disableFullscreen, AIApi, }) => {
|
|
21
|
+
export const MarkdownActions = ({ textAreaRef, selectionStart, selectionEnd, loadingFile, onOpenFileDialog, onClickFullscreen, fullscreen, viewMode, onChangeViewMode, horizontalPaddings, hasFilesApi, width, showActionHints, showShortKeys, hideOptions, onTrackEvent, onSelectEmoji, isSplitViewAvailable, disableFullscreen, AIApi, }) => {
|
|
21
22
|
const isPreviewMode = viewMode === ViewMode.Preview;
|
|
22
23
|
return (React.createElement(MarkdownActionsWrapper, { ...horizontalPaddings, width: width, fullscreen: fullscreen },
|
|
23
24
|
React.createElement(ButtonsWrapper, { fullscreen: fullscreen },
|
|
@@ -39,20 +40,27 @@ export const MarkdownActions = ({ textAreaRef, selectionStart, selectionEnd, loa
|
|
|
39
40
|
return null;
|
|
40
41
|
return (React.createElement(MarkdownFormatButton, { key: idx, showActionHint: showActionHints, showShortKey: showShortKeys, disabled: isPreviewMode, format: helper.format, dataTid: helper.tid, hintText: helper.node, icon: helper.icon, text: helper.text, onClick: event => handleMarkdownItemClick(event, helper.format) }));
|
|
41
42
|
}),
|
|
42
|
-
hasFilesApi && !hideOptions?.file && (React.createElement(MarkdownFormatButton, { dataTid: MarkdownTids.AttachFile, hintText: "\u041F\u0440\u0438\u043A\u0440\u0435\u043F\u0438\u0442\u044C \u0444\u0430\u0439\u043B", showActionHint: showActionHints, showShortKey: showShortKeys, disabled: isPreviewMode, isLoading: loadingFile, icon: React.createElement(AttachPaperclip, null), text: "\u041F\u0440\u0438\u043A\u0440\u0435\u043F\u0438\u0442\u044C \u0444\u0430\u0439\u043B", onClick:
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
hasFilesApi && !hideOptions?.file && (React.createElement(MarkdownFormatButton, { dataTid: MarkdownTids.AttachFile, hintText: "\u041F\u0440\u0438\u043A\u0440\u0435\u043F\u0438\u0442\u044C \u0444\u0430\u0439\u043B", showActionHint: showActionHints, showShortKey: showShortKeys, disabled: isPreviewMode, isLoading: loadingFile, icon: React.createElement(AttachPaperclip, null), text: "\u041F\u0440\u0438\u043A\u0440\u0435\u043F\u0438\u0442\u044C \u0444\u0430\u0439\u043B", onClick: () => {
|
|
44
|
+
handleTrackClick(MarkdownFormat.file);
|
|
45
|
+
onOpenFileDialog();
|
|
46
|
+
} })),
|
|
47
|
+
!hideOptions?.emoji && (React.createElement(EmojiDropdown, { showActionHint: showActionHints, showShortKey: showShortKeys, isPreviewMode: isPreviewMode, onTrackEvent: () => handleTrackClick('emoji'), onSelect: onSelectEmoji })),
|
|
48
|
+
!hideOptions?.AI && !!AIApi && (React.createElement(AIActionsDropdown, { showActionHint: showActionHints, textareaRef: textAreaRef, isPreviewMode: isPreviewMode, api: AIApi, onTrackEvent: label => handleTrackClick('AI', label) }))),
|
|
45
49
|
React.createElement(ActionsRightWrapper, null,
|
|
46
|
-
!hideOptions?.help && (React.createElement(MarkdownFormatButton, { showActionHint: showActionHints, dataTid: MarkdownTids.Help, hintText: "\u0414\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u0430\u0446\u0438\u044F Markdown", icon: React.createElement(DocIcon, null), text: "\u0414\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u0430\u0446\u0438\u044F Markdown", href: COMMONMARK_HELP_URL })),
|
|
50
|
+
!hideOptions?.help && (React.createElement(MarkdownFormatButton, { showActionHint: showActionHints, dataTid: MarkdownTids.Help, hintText: "\u0414\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u0430\u0446\u0438\u044F Markdown", icon: React.createElement(DocIcon, null), text: "\u0414\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u0430\u0446\u0438\u044F Markdown", href: COMMONMARK_HELP_URL, onClick: () => handleTrackClick('help') })),
|
|
47
51
|
!hideOptions?.viewMode && renderViewModeButton(),
|
|
48
|
-
!hideOptions?.screenMode && !disableFullscreen && (React.createElement(MarkdownFormatButton, { showActionHint: showActionHints, dataTid: MarkdownTids.FullscreenToggle, hintText: fullscreen ? 'Свернуть' : 'Развернуть', icon: fullscreen ? React.createElement(Collapse, null) : React.createElement(Expand, null), text: fullscreen ? 'Свернуть' : ' Развернуть', onClick:
|
|
52
|
+
!hideOptions?.screenMode && !disableFullscreen && (React.createElement(MarkdownFormatButton, { showActionHint: showActionHints, dataTid: MarkdownTids.FullscreenToggle, hintText: fullscreen ? 'Свернуть' : 'Развернуть', icon: fullscreen ? React.createElement(Collapse, null) : React.createElement(Expand, null), text: fullscreen ? 'Свернуть' : ' Развернуть', onClick: () => {
|
|
53
|
+
handleTrackClick('screenMode');
|
|
54
|
+
onClickFullscreen();
|
|
55
|
+
} }))))));
|
|
49
56
|
function renderViewModeButton() {
|
|
50
57
|
return (React.createElement(ViewModeButtonsWrapper, null,
|
|
51
|
-
viewMode !== ViewMode.Split && fullscreen && isSplitViewAvailable && (React.createElement(MarkdownFormatButton, { dataTid: MarkdownTids.SplitView, icon: React.createElement(SplitView, null), hintText: "\u0421\u043F\u043B\u0438\u0442", text: "\u0421\u043F\u043B\u0438\u0442", showActionHint: showActionHints, showShortKey: showShortKeys, onClick: () =>
|
|
52
|
-
viewMode !== ViewMode.Edit && (React.createElement(MarkdownFormatButton, { dataTid: MarkdownTids.EditView, icon: React.createElement(ToolPencil, null), hintText: "\u0420\u0435\u0434\u0430\u043A\u0442\u043E\u0440", text: "\u0420\u0435\u0434\u0430\u043A\u0442\u043E\u0440", showActionHint: showActionHints, showShortKey: showShortKeys, onClick: () =>
|
|
53
|
-
viewMode !== ViewMode.Preview && (React.createElement(MarkdownFormatButton, { dataTid: MarkdownTids.PreviewView, icon: React.createElement(EyeOpen, null), hintText: "\u041F\u0440\u0435\u0432\u044C\u044E", text: "\u041F\u0440\u0435\u0432\u044C\u044E", showActionHint: showActionHints, showShortKey: showShortKeys, onClick: () =>
|
|
58
|
+
viewMode !== ViewMode.Split && fullscreen && isSplitViewAvailable && (React.createElement(MarkdownFormatButton, { dataTid: MarkdownTids.SplitView, icon: React.createElement(SplitView, null), hintText: "\u0421\u043F\u043B\u0438\u0442", text: "\u0421\u043F\u043B\u0438\u0442", showActionHint: showActionHints, showShortKey: showShortKeys, onClick: () => handleChangeViewMode(ViewMode.Split) })),
|
|
59
|
+
viewMode !== ViewMode.Edit && (React.createElement(MarkdownFormatButton, { dataTid: MarkdownTids.EditView, icon: React.createElement(ToolPencil, null), hintText: "\u0420\u0435\u0434\u0430\u043A\u0442\u043E\u0440", text: "\u0420\u0435\u0434\u0430\u043A\u0442\u043E\u0440", showActionHint: showActionHints, showShortKey: showShortKeys, onClick: () => handleChangeViewMode(ViewMode.Edit) })),
|
|
60
|
+
viewMode !== ViewMode.Preview && (React.createElement(MarkdownFormatButton, { dataTid: MarkdownTids.PreviewView, icon: React.createElement(EyeOpen, null), hintText: "\u041F\u0440\u0435\u0432\u044C\u044E", text: "\u041F\u0440\u0435\u0432\u044C\u044E", showActionHint: showActionHints, showShortKey: showShortKeys, onClick: () => handleChangeViewMode(ViewMode.Preview) }))));
|
|
54
61
|
}
|
|
55
62
|
function handleMarkdownItemClick(event, format) {
|
|
63
|
+
handleTrackClick(format);
|
|
56
64
|
if (!isNaN(Number(selectionStart)) && !isNaN(Number(selectionEnd))) {
|
|
57
65
|
event.stopPropagation();
|
|
58
66
|
event.preventDefault();
|
|
@@ -68,4 +76,11 @@ export const MarkdownActions = ({ textAreaRef, selectionStart, selectionEnd, loa
|
|
|
68
76
|
function isHiddenOptions(format) {
|
|
69
77
|
return hideOptions?.[format];
|
|
70
78
|
}
|
|
79
|
+
function handleChangeViewMode(mode) {
|
|
80
|
+
handleTrackClick('viewMode', mode);
|
|
81
|
+
onChangeViewMode(mode);
|
|
82
|
+
}
|
|
83
|
+
function handleTrackClick(category, label) {
|
|
84
|
+
onTrackEvent?.(category, 'click', label);
|
|
85
|
+
}
|
|
71
86
|
};
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { Textarea, TextareaProps } from '@skbkontur/react-ui';
|
|
2
2
|
import { ValidationInfo, RenderErrorMessage } from '@skbkontur/react-ui-validations';
|
|
3
3
|
import { FC, RefObject } from 'react';
|
|
4
|
-
import { Nullable } from './types';
|
|
4
|
+
import { TrackEventCategory, Nullable } from './types';
|
|
5
5
|
export interface MarkdownEditorProps extends Omit<TextareaProps, 'rows' | 'maxRows' | 'disableAnimations' | 'extraRow'> {
|
|
6
6
|
disableAnimations?: boolean;
|
|
7
7
|
extraRow?: boolean;
|
|
8
8
|
maxRows?: number;
|
|
9
|
+
onTrackEvent?: (category: TrackEventCategory, action: 'click' | 'hotkey', label?: string) => void;
|
|
9
10
|
/** Рендер сообщения валидации react-ui-validations */
|
|
10
11
|
renderMessage?: Nullable<RenderErrorMessage>;
|
|
11
12
|
rows?: number;
|
|
@@ -4,11 +4,11 @@ import React from 'react';
|
|
|
4
4
|
import { useDropzone } from 'react-dropzone';
|
|
5
5
|
import { createMarkdownHelpKeyDownHandler } from './MarkdownHelpers/markdownHelpers';
|
|
6
6
|
export const MarkdownEditor = props => {
|
|
7
|
-
const { resize = 'none', width = '100%', autoResize = true, showLengthCounter: propsShowLengthCounter = true, textareaRef, validationInfo, renderMessage, withValidationWrapper, ...rest } = props;
|
|
7
|
+
const { resize = 'none', width = '100%', autoResize = true, showLengthCounter: propsShowLengthCounter = true, textareaRef, validationInfo, renderMessage, withValidationWrapper, onTrackEvent, ...rest } = props;
|
|
8
8
|
const { getInputProps } = useDropzone();
|
|
9
9
|
return withValidationWrapper ? (React.createElement(ValidationWrapper, { validationInfo: validationInfo, renderMessage: renderMessage }, renderTextarea())) : (renderTextarea());
|
|
10
10
|
function renderTextarea() {
|
|
11
|
-
return (React.createElement(Textarea, { ref: textareaRef, id: "MarkdownTextArea", ...rest, ...getInputProps, resize: resize, width: width, showLengthCounter: showLengthCounter(), autoResize: autoResize, onKeyDown: createMarkdownHelpKeyDownHandler(props.value || '', textareaRef, props.onKeyDown) }));
|
|
11
|
+
return (React.createElement(Textarea, { ref: textareaRef, id: "MarkdownTextArea", ...rest, ...getInputProps, resize: resize, width: width, showLengthCounter: showLengthCounter(), autoResize: autoResize, onKeyDown: createMarkdownHelpKeyDownHandler(props.value || '', textareaRef, props.onKeyDown, (category, label) => onTrackEvent?.(category, 'hotkey', label)) }));
|
|
12
12
|
}
|
|
13
13
|
function showLengthCounter() {
|
|
14
14
|
if (propsShowLengthCounter) {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { Textarea } from '@skbkontur/react-ui';
|
|
2
2
|
import { KeyboardEvent as ReactKeyboardEvent, RefObject } from 'react';
|
|
3
3
|
import { MarkdownFormat } from '../MarkdownFormat';
|
|
4
|
-
import { Nullable, RefItem } from '../types';
|
|
4
|
+
import { TrackEventCategory, Nullable, RefItem } from '../types';
|
|
5
5
|
export { handleMarkdownListEnter } from './markdownListEnterHelpers';
|
|
6
6
|
export declare function setMarkdown(textareaNode: HTMLTextAreaElement, text: string, format: MarkdownFormat, selectionStart: number, selectionEnd?: number | null): void;
|
|
7
7
|
export declare function setMarkdownFiles(file: RefItem, textarea: Textarea, format: MarkdownFormat, cursorPosition?: number | null, fileApiUrl?: string): void;
|
|
8
8
|
export declare function setMarkdownPastedHtml(text: string, textareaNode: HTMLTextAreaElement): void;
|
|
9
9
|
export declare function setTextareaCursor(format: MarkdownFormat, prevCommentPartLength: number, nextCommentPartLength: number, textareaNode: HTMLTextAreaElement, selectionEnd: number): void;
|
|
10
|
-
export declare function createMarkdownHelpKeyDownHandler(text: string, ref?: RefObject<Textarea> | null, callback?: (event: ReactKeyboardEvent<HTMLTextAreaElement>) => void): (event: ReactKeyboardEvent<HTMLTextAreaElement>) => void;
|
|
10
|
+
export declare function createMarkdownHelpKeyDownHandler(text: string, ref?: RefObject<Textarea> | null, callback?: (event: ReactKeyboardEvent<HTMLTextAreaElement>) => void, onTrackHotkey?: (category: TrackEventCategory, label?: string) => void): (event: ReactKeyboardEvent<HTMLTextAreaElement>) => void;
|
|
11
11
|
export declare const usePasteFromClipboard: (textarea: Nullable<Textarea>, uploadFileApi?: ((file: File) => Promise<RefItem>) | undefined, downloadFileApi?: ((id: string) => Promise<File>) | undefined, fileApiUrl?: string) => void;
|
|
@@ -57,7 +57,7 @@ export function setTextareaCursor(format, prevCommentPartLength, nextCommentPart
|
|
|
57
57
|
textareaNode.selectionEnd = selectionEnd - prevCommentPartLength + nextCommentCursorPosition;
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
|
-
export function createMarkdownHelpKeyDownHandler(text, ref, callback) {
|
|
60
|
+
export function createMarkdownHelpKeyDownHandler(text, ref, callback, onTrackHotkey) {
|
|
61
61
|
return (event) => {
|
|
62
62
|
if (!ref?.current)
|
|
63
63
|
return;
|
|
@@ -74,9 +74,10 @@ export function createMarkdownHelpKeyDownHandler(text, ref, callback) {
|
|
|
74
74
|
const nextCommentPart = markdownHelpItem.wrapContent(prevCommentPart);
|
|
75
75
|
onInsertText(nextCommentPart);
|
|
76
76
|
setTextareaCursor(format, prevCommentPart.length, nextCommentPart.length, textareaNode, end);
|
|
77
|
+
onTrackHotkey?.(format);
|
|
77
78
|
}
|
|
78
79
|
}
|
|
79
|
-
handleMarkdownListEnter(event, textareaNode);
|
|
80
|
+
handleMarkdownListEnter(event, textareaNode, label => onTrackHotkey?.('continueList', label));
|
|
80
81
|
callback?.(event);
|
|
81
82
|
};
|
|
82
83
|
}
|
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
import { KeyboardEvent as ReactKeyboardEvent } from 'react';
|
|
2
|
-
|
|
2
|
+
import { MarkdownFormat } from '../MarkdownFormat';
|
|
3
|
+
export declare function handleMarkdownListEnter(event: ReactKeyboardEvent<HTMLTextAreaElement>, textareaNode: HTMLTextAreaElement, onTrackEvent?: (label: MarkdownFormat) => void): boolean | void;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { MarkdownFormat } from '../MarkdownFormat';
|
|
1
2
|
import { onInsertText } from '../utils/onInsertText';
|
|
2
3
|
/**
|
|
3
4
|
* RegExp для парсинга markdown-like list item.
|
|
@@ -16,7 +17,7 @@ import { onInsertText } from '../utils/onInsertText';
|
|
|
16
17
|
* - text: текст элемента списка, может быть пустым.
|
|
17
18
|
*/
|
|
18
19
|
const listItemRegExp = /^(?<spacesBeforeMarker> *)(?:(?<orderedListNumber>\d+)(?<orderedListDelimiter>[.)])|(?<unorderedListMarker>[*+-])(?: +(?<checkboxListMarker>\[[ xX]]))?)(?: +(?<text>.*)|$)$/;
|
|
19
|
-
export function handleMarkdownListEnter(event, textareaNode) {
|
|
20
|
+
export function handleMarkdownListEnter(event, textareaNode, onTrackEvent) {
|
|
20
21
|
const { selectionStart, selectionEnd, value } = textareaNode;
|
|
21
22
|
const isOnlyDownEnter = event.key === 'Enter' && !event.shiftKey && !event.altKey && !event.metaKey && !event.ctrlKey;
|
|
22
23
|
if (!isOnlyDownEnter || selectionStart !== selectionEnd)
|
|
@@ -42,6 +43,7 @@ export function handleMarkdownListEnter(event, textareaNode) {
|
|
|
42
43
|
return document.execCommand('delete');
|
|
43
44
|
}
|
|
44
45
|
if (orderedListNumber && orderedListDelimiter) {
|
|
46
|
+
onTrackEvent?.(MarkdownFormat.numberedList);
|
|
45
47
|
const nextOrderedListNumber = Number(orderedListNumber) + 1;
|
|
46
48
|
const newLine = `\n${spacesBeforeMarker}${nextOrderedListNumber}${orderedListDelimiter} `;
|
|
47
49
|
const { isChanged, listEndIndex, renumberedList } = getRenumberedListLines(value, currentLineEndIndex, spacesBeforeMarker, orderedListDelimiter, nextOrderedListNumber + 1);
|
|
@@ -54,10 +56,14 @@ export function handleMarkdownListEnter(event, textareaNode) {
|
|
|
54
56
|
}
|
|
55
57
|
return onInsertText(newLine);
|
|
56
58
|
}
|
|
57
|
-
if (checkboxListMarker)
|
|
59
|
+
if (checkboxListMarker) {
|
|
60
|
+
onTrackEvent?.(MarkdownFormat.checkedList);
|
|
58
61
|
return onInsertText(`\n${spacesBeforeMarker}${unorderedListMarker} ${checkboxListMarker} `);
|
|
59
|
-
|
|
62
|
+
}
|
|
63
|
+
if (unorderedListMarker) {
|
|
64
|
+
onTrackEvent?.(MarkdownFormat.list);
|
|
60
65
|
return onInsertText(`\n${spacesBeforeMarker}${unorderedListMarker} `);
|
|
66
|
+
}
|
|
61
67
|
}
|
|
62
68
|
function getCurrentLineEndIndex(text, cursorPosition) {
|
|
63
69
|
/* Ищем ближайший перенос строки после курсора */
|
package/src/Markdown/types.d.ts
CHANGED
|
@@ -51,4 +51,5 @@ export interface TestCase<V, E> {
|
|
|
51
51
|
values: V;
|
|
52
52
|
}
|
|
53
53
|
export type ReactUIThemeType = Partial<typeof THEME_2022>;
|
|
54
|
-
export type
|
|
54
|
+
export type ActionsOptions = Partial<Record<MarkdownFormat | 'heading' | 'emoji' | 'viewMode' | 'screenMode' | 'help' | 'allActions' | 'AI', boolean>>;
|
|
55
|
+
export type TrackEventCategory = keyof ActionsOptions | 'continueList';
|