@gravity-ui/markdown-editor 13.4.2 → 13.5.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/build/cjs/bundle/Editor.d.ts +1 -2
- package/build/cjs/bundle/Editor.js +1 -0
- package/build/cjs/bundle/MarkdownEditorView.js +3 -1
- package/build/cjs/extensions/markdown/Link/plugins/LinkTooltipPlugin/TooltipView.js +2 -2
- package/build/cjs/extensions/yfm/ImgSize/plugins/ImgSizeNodeView/ImageForm/ImageForm.js +2 -2
- package/build/cjs/forms/FileForm.js +2 -2
- package/build/cjs/forms/ImageForm.js +2 -2
- package/build/cjs/forms/LinkForm.js +2 -2
- package/build/cjs/i18n/search/en.json +5 -0
- package/build/cjs/i18n/search/index.d.ts +7 -0
- package/build/cjs/i18n/search/index.js +9 -0
- package/build/cjs/i18n/search/ru.json +5 -0
- package/build/cjs/markup/codemirror/create.d.ts +3 -0
- package/build/cjs/markup/codemirror/create.js +6 -1
- package/build/cjs/markup/codemirror/search-plugin/plugin.d.ts +42 -0
- package/build/cjs/markup/codemirror/search-plugin/plugin.js +100 -0
- package/build/cjs/markup/codemirror/search-plugin/view/SearchPopup.css +9 -0
- package/build/cjs/markup/codemirror/search-plugin/view/SearchPopup.d.ts +29 -0
- package/build/cjs/markup/codemirror/search-plugin/view/SearchPopup.js +85 -0
- package/build/cjs/utils/handlers.d.ts +4 -0
- package/build/cjs/utils/handlers.js +29 -0
- package/build/cjs/version.js +1 -1
- package/build/esm/bundle/Editor.d.ts +1 -2
- package/build/esm/bundle/Editor.js +1 -0
- package/build/esm/bundle/MarkdownEditorView.js +3 -1
- package/build/esm/extensions/markdown/Link/plugins/LinkTooltipPlugin/TooltipView.js +1 -1
- package/build/esm/extensions/yfm/ImgSize/plugins/ImgSizeNodeView/ImageForm/ImageForm.js +1 -1
- package/build/esm/forms/FileForm.js +1 -1
- package/build/esm/forms/ImageForm.js +1 -1
- package/build/esm/forms/LinkForm.js +1 -1
- package/build/esm/i18n/search/en.json +5 -0
- package/build/esm/i18n/search/index.d.ts +7 -0
- package/build/esm/i18n/search/index.js +5 -0
- package/build/esm/i18n/search/ru.json +5 -0
- package/build/esm/markup/codemirror/create.d.ts +3 -0
- package/build/esm/markup/codemirror/create.js +6 -1
- package/build/esm/markup/codemirror/search-plugin/plugin.d.ts +42 -0
- package/build/esm/markup/codemirror/search-plugin/plugin.js +96 -0
- package/build/esm/markup/codemirror/search-plugin/view/SearchPopup.css +9 -0
- package/build/esm/markup/codemirror/search-plugin/view/SearchPopup.d.ts +30 -0
- package/build/esm/markup/codemirror/search-plugin/view/SearchPopup.js +80 -0
- package/build/esm/utils/handlers.d.ts +4 -0
- package/build/esm/utils/handlers.js +23 -0
- package/build/esm/version.js +1 -1
- package/build/styles.css +9 -0
- package/package.json +4 -3
- package/build/cjs/forms/utils.d.ts +0 -2
- package/build/cjs/forms/utils.js +0 -11
- package/build/esm/forms/utils.d.ts +0 -2
- package/build/esm/forms/utils.js +0 -7
|
@@ -19,7 +19,7 @@ export declare type ToolbarActionData = {
|
|
|
19
19
|
[key: string]: any;
|
|
20
20
|
};
|
|
21
21
|
};
|
|
22
|
-
interface EventMap {
|
|
22
|
+
export interface EventMap {
|
|
23
23
|
change: null;
|
|
24
24
|
cancel: null;
|
|
25
25
|
submit: null;
|
|
@@ -70,4 +70,3 @@ export declare type EditorOptions = Pick<WysiwygEditorOptions, 'allowHTML' | 'li
|
|
|
70
70
|
preset: EditorPreset;
|
|
71
71
|
extraMarkupExtensions?: CodemirrorExtension[];
|
|
72
72
|
};
|
|
73
|
-
export {};
|
|
@@ -155,6 +155,7 @@ class EditorImpl extends event_emitter_1.SafeEventEmitter {
|
|
|
155
155
|
uploadHandler: this.fileUploadHandler,
|
|
156
156
|
needImgDimms: this.needToSetDimensionsForUploadedImages,
|
|
157
157
|
extraMarkupExtensions: tslib_1.__classPrivateFieldGet(this, _EditorImpl_extraMarkupExtensions, "f"),
|
|
158
|
+
receiver: this,
|
|
158
159
|
})), "f");
|
|
159
160
|
}
|
|
160
161
|
return tslib_1.__classPrivateFieldGet(this, _EditorImpl_markupEditor, "f");
|
|
@@ -113,6 +113,7 @@ exports.MarkdownEditorView = react_1.default.forwardRef((props, ref) => {
|
|
|
113
113
|
react_1.default.createElement(SplitModeView_1.SplitModeView, { editor: editor, ref: splitModeViewWrapperRef })))))));
|
|
114
114
|
});
|
|
115
115
|
exports.MarkdownEditorView.displayName = 'MarkdownEditorView';
|
|
116
|
+
const MarkupSearchAnchor = ({ mode }) => (react_1.default.createElement(react_1.default.Fragment, null, mode === 'markup' && react_1.default.createElement("div", { className: "g-md-search-anchor" })));
|
|
116
117
|
function Settings(props) {
|
|
117
118
|
const wrapperRef = (0, react_1.useRef)(null);
|
|
118
119
|
const isSticky = (0, useSticky_1.useSticky)(wrapperRef) && props.toolbarVisibility && props.stickyToolbar;
|
|
@@ -121,7 +122,8 @@ function Settings(props) {
|
|
|
121
122
|
withToolbar: props.toolbarVisibility,
|
|
122
123
|
stickyActive: isSticky,
|
|
123
124
|
}) },
|
|
124
|
-
react_1.default.createElement(settings_1.EditorSettings, Object.assign({}, props))
|
|
125
|
+
react_1.default.createElement(settings_1.EditorSettings, Object.assign({}, props)),
|
|
126
|
+
react_1.default.createElement(MarkupSearchAnchor, Object.assign({}, props)))));
|
|
125
127
|
}
|
|
126
128
|
function isPreviewKeyDown(e) {
|
|
127
129
|
const modKey = (0, platform_1.isMac)() ? e.metaKey : e.ctrlKey;
|
|
@@ -6,14 +6,14 @@ const react_1 = tslib_1.__importDefault(require("react"));
|
|
|
6
6
|
const TextInput_1 = require("../../../../../forms/TextInput");
|
|
7
7
|
const UrlInputRow_1 = require("../../../../../forms/UrlInputRow");
|
|
8
8
|
const base_1 = tslib_1.__importDefault(require("../../../../../forms/base"));
|
|
9
|
-
const utils_1 = require("../../../../../forms/utils");
|
|
10
9
|
const forms_1 = require("../../../../../i18n/forms");
|
|
10
|
+
const handlers_1 = require("../../../../../utils/handlers");
|
|
11
11
|
exports.LinkForm = react_1.default.memo(function LinkForm({ href, autoFocus, onChange, onCancel, }) {
|
|
12
12
|
const [url, setUrl] = react_1.default.useState(href);
|
|
13
13
|
const handleSubmit = () => {
|
|
14
14
|
onChange === null || onChange === void 0 ? void 0 : onChange({ href: url });
|
|
15
15
|
};
|
|
16
|
-
const inputEnterKeyHandler = (0,
|
|
16
|
+
const inputEnterKeyHandler = (0, handlers_1.enterKeyHandler)(handleSubmit);
|
|
17
17
|
return (react_1.default.createElement(base_1.default.Form, null,
|
|
18
18
|
react_1.default.createElement(base_1.default.Layout, null,
|
|
19
19
|
react_1.default.createElement(base_1.default.Row, { label: (0, forms_1.i18n)('common_link'), help: (0, forms_1.i18n)('link_url_help'), control: react_1.default.createElement(UrlInputRow_1.UrlInputRow, { href: url, input: react_1.default.createElement(TextInput_1.TextInputFixed, { size: "s", hasClear: true, view: "normal", value: url, onUpdate: setUrl, placeholder: "https://", autoFocus: autoFocus, onKeyPress: inputEnterKeyHandler }) }) })),
|
|
@@ -8,9 +8,9 @@ const is_number_1 = tslib_1.__importDefault(require("is-number"));
|
|
|
8
8
|
const classname_1 = require("../../../../../../classname");
|
|
9
9
|
const base_1 = tslib_1.__importDefault(require("../../../../../../forms/base"));
|
|
10
10
|
const components_1 = require("../../../../../../forms/components");
|
|
11
|
-
const utils_1 = require("../../../../../../forms/utils");
|
|
12
11
|
const forms_1 = require("../../../../../../i18n/forms");
|
|
13
12
|
const useAutoFocus_1 = require("../../../../../../react-utils/useAutoFocus");
|
|
13
|
+
const handlers_1 = require("../../../../../../utils/handlers");
|
|
14
14
|
const markdown_1 = require("../../../../../markdown");
|
|
15
15
|
const specs_1 = require("../../../../../specs");
|
|
16
16
|
const b = (0, classname_1.cn)('image-tooltip-form');
|
|
@@ -38,7 +38,7 @@ const ImageForm = ({ node, updateAttributes, view, unsetEdit, dom }) => {
|
|
|
38
38
|
const linkRef = react_1.default.useRef(null);
|
|
39
39
|
const imageNameRef = react_1.default.useRef(null);
|
|
40
40
|
(0, useAutoFocus_1.useAutoFocus)(link ? linkRef : imageNameRef);
|
|
41
|
-
const inputEnterKeyHandler = (0,
|
|
41
|
+
const inputEnterKeyHandler = (0, handlers_1.enterKeyHandler)(handleSubmit);
|
|
42
42
|
return (react_1.default.createElement(uikit_1.Popup, { open: true, anchorRef: dom, placement: ['bottom-start', 'top-start', 'bottom-end', 'top-end'], onOutsideClick: unsetEdit },
|
|
43
43
|
react_1.default.createElement(base_1.default.Form, { className: b() },
|
|
44
44
|
react_1.default.createElement(base_1.default.Layout, null,
|
|
@@ -7,10 +7,10 @@ const uikit_1 = require("@gravity-ui/uikit");
|
|
|
7
7
|
const classname_1 = require("../classname");
|
|
8
8
|
const forms_1 = require("../i18n/forms");
|
|
9
9
|
const lodash_1 = require("../lodash");
|
|
10
|
+
const handlers_1 = require("../utils/handlers");
|
|
10
11
|
const TextInput_1 = require("./TextInput");
|
|
11
12
|
const base_1 = tslib_1.__importDefault(require("./base"));
|
|
12
13
|
const components_1 = require("./components");
|
|
13
|
-
const utils_1 = require("./utils");
|
|
14
14
|
const b = (0, classname_1.cn)('file-form');
|
|
15
15
|
const FileForm = ({ className, autoFocus, onCancel, onSubmit, onAttach, loading, }) => {
|
|
16
16
|
const [tabId, setTabId] = react_1.default.useState(() => (0, lodash_1.isFunction)(onAttach) ? "attach" /* TabId.Attach */ : "link" /* TabId.Link */);
|
|
@@ -28,7 +28,7 @@ const FileForm = ({ className, autoFocus, onCancel, onSubmit, onAttach, loading,
|
|
|
28
28
|
name: name.trim(),
|
|
29
29
|
});
|
|
30
30
|
};
|
|
31
|
-
const inputEnterKeyHandler = (0,
|
|
31
|
+
const inputEnterKeyHandler = (0, handlers_1.enterKeyHandler)(handleSubmit);
|
|
32
32
|
return (react_1.default.createElement(base_1.default.Form, { className: b(null, [className]) },
|
|
33
33
|
shouldRenderTabs && (react_1.default.createElement(uikit_1.Tabs, { activeTab: tabId, onSelectTab: setTabId, items: [
|
|
34
34
|
{ id: "attach" /* TabId.Attach */, title: (0, forms_1.i18n)('common_tab_attach') },
|
|
@@ -7,10 +7,10 @@ const uikit_1 = require("@gravity-ui/uikit");
|
|
|
7
7
|
const classname_1 = require("../classname");
|
|
8
8
|
const forms_1 = require("../i18n/forms");
|
|
9
9
|
const lodash_1 = require("../lodash");
|
|
10
|
+
const handlers_1 = require("../utils/handlers");
|
|
10
11
|
const TextInput_1 = require("./TextInput");
|
|
11
12
|
const base_1 = tslib_1.__importDefault(require("./base"));
|
|
12
13
|
const components_1 = require("./components");
|
|
13
|
-
const utils_1 = require("./utils");
|
|
14
14
|
const b = (0, classname_1.cn)('image-form');
|
|
15
15
|
const ImageForm = ({ className, autoFocus, onCancel, onSubmit, onAttach, loading, }) => {
|
|
16
16
|
const [tabId, setTabId] = react_1.default.useState(() => (0, lodash_1.isFunction)(onAttach) ? "attach" /* ImageTabId.Attach */ : "link" /* ImageTabId.Link */);
|
|
@@ -37,7 +37,7 @@ const ImageForm = ({ className, autoFocus, onCancel, onSubmit, onAttach, loading
|
|
|
37
37
|
data.height = height;
|
|
38
38
|
onSubmit(data);
|
|
39
39
|
};
|
|
40
|
-
const inputEnterKeyHandler = (0,
|
|
40
|
+
const inputEnterKeyHandler = (0, handlers_1.enterKeyHandler)(handleSubmit);
|
|
41
41
|
return (react_1.default.createElement(base_1.default.Form, { className: b(null, [className]) },
|
|
42
42
|
shouldRenderTabs && (react_1.default.createElement(uikit_1.Tabs, { activeTab: tabId, onSelectTab: setTabId, items: [
|
|
43
43
|
{ id: "attach" /* ImageTabId.Attach */, title: (0, forms_1.i18n)('common_tab_attach') },
|
|
@@ -5,17 +5,17 @@ const tslib_1 = require("tslib");
|
|
|
5
5
|
const react_1 = tslib_1.__importDefault(require("react"));
|
|
6
6
|
const uikit_1 = require("@gravity-ui/uikit");
|
|
7
7
|
const forms_1 = require("../i18n/forms");
|
|
8
|
+
const handlers_1 = require("../utils/handlers");
|
|
8
9
|
const TextInput_1 = require("./TextInput");
|
|
9
10
|
const UrlInputRow_1 = require("./UrlInputRow");
|
|
10
11
|
const base_1 = tslib_1.__importDefault(require("./base"));
|
|
11
|
-
const utils_1 = require("./utils");
|
|
12
12
|
exports.LinkForm = react_1.default.memo(function LinkForm({ className, autoFocus, initialUrl, initialText, readOnlyText, onSubmit, onCancel, }) {
|
|
13
13
|
const [url, setUrl] = react_1.default.useState(initialUrl !== null && initialUrl !== void 0 ? initialUrl : '');
|
|
14
14
|
const [text, setText] = react_1.default.useState(initialText !== null && initialText !== void 0 ? initialText : '');
|
|
15
15
|
const handleSubmit = () => {
|
|
16
16
|
onSubmit({ url, text });
|
|
17
17
|
};
|
|
18
|
-
const inputEnterKeyHandler = (0,
|
|
18
|
+
const inputEnterKeyHandler = (0, handlers_1.enterKeyHandler)(handleSubmit);
|
|
19
19
|
return (react_1.default.createElement(base_1.default.Form, { className: className },
|
|
20
20
|
react_1.default.createElement(base_1.default.Layout, null,
|
|
21
21
|
react_1.default.createElement(base_1.default.Row, { label: (0, forms_1.i18n)('common_link'), help: (0, forms_1.i18n)('link_url_help'), control: react_1.default.createElement(UrlInputRow_1.UrlInputRow, { href: url, input: react_1.default.createElement(TextInput_1.TextInputFixed, { size: "s", view: "normal", value: url, onUpdate: setUrl, autoFocus: autoFocus, placeholder: "https://", onKeyPress: inputEnterKeyHandler }) }) }),
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const i18n: <G extends "title" | "label_case-sensitive" | "label_whole-word", S extends string>(key: G | (string extends S ? S : never), params?: {
|
|
2
|
+
[key: string]: any;
|
|
3
|
+
} | undefined) => S extends G ? {
|
|
4
|
+
"label_case-sensitive": string;
|
|
5
|
+
"label_whole-word": string;
|
|
6
|
+
title: string;
|
|
7
|
+
}[G] : string;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.i18n = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const i18n_1 = require("../i18n");
|
|
6
|
+
const en_json_1 = tslib_1.__importDefault(require("./en.json"));
|
|
7
|
+
const ru_json_1 = tslib_1.__importDefault(require("./ru.json"));
|
|
8
|
+
const KEYSET = 'search';
|
|
9
|
+
exports.i18n = (0, i18n_1.registerKeyset)(KEYSET, { en: en_json_1.default, ru: ru_json_1.default });
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { Extension, StateCommand } from '@codemirror/state';
|
|
2
2
|
import { EditorView, EditorViewConfig } from '@codemirror/view';
|
|
3
|
+
import { EventMap } from '../../bundle/Editor';
|
|
3
4
|
import { ReactRenderStorage } from '../../extensions';
|
|
5
|
+
import { Receiver } from '../../utils';
|
|
4
6
|
import { FileUploadHandler } from './files-upload-facet';
|
|
5
7
|
export declare type CreateCodemirrorParams = {
|
|
6
8
|
doc: EditorViewConfig['doc'];
|
|
@@ -14,6 +16,7 @@ export declare type CreateCodemirrorParams = {
|
|
|
14
16
|
uploadHandler?: FileUploadHandler;
|
|
15
17
|
needImgDimms?: boolean;
|
|
16
18
|
extraMarkupExtensions?: Extension[];
|
|
19
|
+
receiver?: Receiver<EventMap>;
|
|
17
20
|
};
|
|
18
21
|
export declare function createCodemirror(params: CreateCodemirrorParams): EditorView;
|
|
19
22
|
export declare function withLogger(action: string, command: StateCommand): StateCommand;
|
|
@@ -13,9 +13,10 @@ const files_upload_facet_1 = require("./files-upload-facet");
|
|
|
13
13
|
const gravity_1 = require("./gravity");
|
|
14
14
|
const pairing_chars_1 = require("./pairing-chars");
|
|
15
15
|
const react_facet_1 = require("./react-facet");
|
|
16
|
+
const plugin_1 = require("./search-plugin/plugin");
|
|
16
17
|
const yfm_1 = require("./yfm");
|
|
17
18
|
function createCodemirror(params) {
|
|
18
|
-
const { doc, placeholderText, reactRenderer, onCancel, onScroll, onSubmit, onChange, onDocChange, extraMarkupExtensions, } = params;
|
|
19
|
+
const { doc, placeholderText, reactRenderer, onCancel, onScroll, onSubmit, onChange, onDocChange, extraMarkupExtensions, receiver, } = params;
|
|
19
20
|
const extensions = [
|
|
20
21
|
gravity_1.gravityTheme,
|
|
21
22
|
(0, view_1.placeholder)(placeholderText),
|
|
@@ -69,6 +70,10 @@ function createCodemirror(params) {
|
|
|
69
70
|
onScroll(event);
|
|
70
71
|
},
|
|
71
72
|
}),
|
|
73
|
+
(0, plugin_1.SearchPanelPlugin)({
|
|
74
|
+
anchorSelector: '.g-md-search-anchor',
|
|
75
|
+
receiver,
|
|
76
|
+
}),
|
|
72
77
|
];
|
|
73
78
|
if (params.uploadHandler) {
|
|
74
79
|
extensions.push(files_upload_facet_1.FileUploadHandlerFacet.of({
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { EditorView, ViewPlugin, ViewUpdate } from '@codemirror/view';
|
|
2
|
+
import { EditorMode, EventMap } from '../../../bundle/Editor';
|
|
3
|
+
import type { RendererItem } from '../../../extensions';
|
|
4
|
+
import { Receiver } from '../../../utils';
|
|
5
|
+
interface SearchQueryParams {
|
|
6
|
+
search: string;
|
|
7
|
+
caseSensitive?: boolean;
|
|
8
|
+
literal?: boolean;
|
|
9
|
+
regexp?: boolean;
|
|
10
|
+
replace?: string;
|
|
11
|
+
valid?: boolean;
|
|
12
|
+
wholeWord?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export interface SearchPanelPluginParams {
|
|
15
|
+
anchorSelector: string;
|
|
16
|
+
inputDelay?: number;
|
|
17
|
+
receiver?: Receiver<EventMap>;
|
|
18
|
+
}
|
|
19
|
+
export declare const SearchPanelPlugin: (params: SearchPanelPluginParams) => ViewPlugin<{
|
|
20
|
+
readonly view: EditorView;
|
|
21
|
+
readonly params: SearchPanelPluginParams;
|
|
22
|
+
anchor: HTMLElement | null;
|
|
23
|
+
renderer: RendererItem | null;
|
|
24
|
+
searchQuery: SearchQueryParams;
|
|
25
|
+
receiver: Receiver<EventMap> | undefined;
|
|
26
|
+
setViewSearchWithDelay: (config: Partial<SearchQueryParams>) => void;
|
|
27
|
+
update(update: ViewUpdate): void;
|
|
28
|
+
destroy(): void;
|
|
29
|
+
setViewSearch(config: Partial<SearchQueryParams>): void;
|
|
30
|
+
handleEditorModeChange({ mode }: {
|
|
31
|
+
mode: EditorMode;
|
|
32
|
+
}): void;
|
|
33
|
+
handleChange(search: string): void;
|
|
34
|
+
handleClose(): void;
|
|
35
|
+
handleSearchNext(): void;
|
|
36
|
+
handleSearchPrev(): void;
|
|
37
|
+
handleSearchConfigChange({ isCaseSensitive, isWholeWord, }: {
|
|
38
|
+
isCaseSensitive?: boolean | undefined;
|
|
39
|
+
isWholeWord?: boolean | undefined;
|
|
40
|
+
}): void;
|
|
41
|
+
}>;
|
|
42
|
+
export {};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SearchPanelPlugin = void 0;
|
|
4
|
+
const search_1 = require("@codemirror/search");
|
|
5
|
+
const view_1 = require("@codemirror/view");
|
|
6
|
+
const lodash_1 = require("../../../lodash");
|
|
7
|
+
const react_facet_1 = require("../react-facet");
|
|
8
|
+
const SearchPopup_1 = require("./view/SearchPopup");
|
|
9
|
+
const INPUT_DELAY = 200;
|
|
10
|
+
const SearchPanelPlugin = (params) => view_1.ViewPlugin.fromClass(class {
|
|
11
|
+
constructor(view) {
|
|
12
|
+
var _a, _b;
|
|
13
|
+
this.searchQuery = {
|
|
14
|
+
search: '',
|
|
15
|
+
caseSensitive: false,
|
|
16
|
+
wholeWord: false,
|
|
17
|
+
};
|
|
18
|
+
this.view = view;
|
|
19
|
+
this.anchor = null;
|
|
20
|
+
this.renderer = null;
|
|
21
|
+
this.params = params;
|
|
22
|
+
this.receiver = params.receiver;
|
|
23
|
+
this.handleClose = this.handleClose.bind(this);
|
|
24
|
+
this.handleChange = this.handleChange.bind(this);
|
|
25
|
+
this.handleSearchNext = this.handleSearchNext.bind(this);
|
|
26
|
+
this.handleSearchPrev = this.handleSearchPrev.bind(this);
|
|
27
|
+
this.handleSearchConfigChange = this.handleSearchConfigChange.bind(this);
|
|
28
|
+
this.handleEditorModeChange = this.handleEditorModeChange.bind(this);
|
|
29
|
+
this.setViewSearchWithDelay = (0, lodash_1.debounce)(this.setViewSearch, (_a = this.params.inputDelay) !== null && _a !== void 0 ? _a : INPUT_DELAY);
|
|
30
|
+
(_b = this.receiver) === null || _b === void 0 ? void 0 : _b.on('change-editor-mode', this.handleEditorModeChange);
|
|
31
|
+
}
|
|
32
|
+
update(update) {
|
|
33
|
+
var _a;
|
|
34
|
+
const isPanelOpen = (0, search_1.searchPanelOpen)(update.state);
|
|
35
|
+
if (isPanelOpen && !this.renderer) {
|
|
36
|
+
this.anchor = document.querySelector(this.params.anchorSelector);
|
|
37
|
+
this.renderer = this.view.state
|
|
38
|
+
.facet(react_facet_1.ReactRendererFacet)
|
|
39
|
+
.createItem('cm-search', () => (0, SearchPopup_1.renderSearchPopup)({
|
|
40
|
+
open: true,
|
|
41
|
+
anchor: this.anchor,
|
|
42
|
+
onChange: this.handleChange,
|
|
43
|
+
onClose: this.handleClose,
|
|
44
|
+
onSearchNext: this.handleSearchNext,
|
|
45
|
+
onSearchPrev: this.handleSearchPrev,
|
|
46
|
+
onConfigChange: this.handleSearchConfigChange,
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
49
|
+
else if (!isPanelOpen && this.renderer) {
|
|
50
|
+
(_a = this.renderer) === null || _a === void 0 ? void 0 : _a.remove();
|
|
51
|
+
this.renderer = null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
destroy() {
|
|
55
|
+
var _a, _b;
|
|
56
|
+
(_a = this.renderer) === null || _a === void 0 ? void 0 : _a.remove();
|
|
57
|
+
this.renderer = null;
|
|
58
|
+
(_b = this.receiver) === null || _b === void 0 ? void 0 : _b.off('change-editor-mode', this.handleEditorModeChange);
|
|
59
|
+
}
|
|
60
|
+
setViewSearch(config) {
|
|
61
|
+
this.searchQuery = Object.assign(Object.assign({}, this.searchQuery), config);
|
|
62
|
+
const searchQuery = new search_1.SearchQuery(Object.assign({}, this.searchQuery));
|
|
63
|
+
this.view.dispatch({ effects: search_1.setSearchQuery.of(searchQuery) });
|
|
64
|
+
}
|
|
65
|
+
handleEditorModeChange({ mode }) {
|
|
66
|
+
if (mode === 'wysiwyg') {
|
|
67
|
+
(0, search_1.closeSearchPanel)(this.view);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
handleChange(search) {
|
|
71
|
+
this.setViewSearchWithDelay({ search });
|
|
72
|
+
}
|
|
73
|
+
handleClose() {
|
|
74
|
+
this.setViewSearch({ search: '' });
|
|
75
|
+
(0, search_1.closeSearchPanel)(this.view);
|
|
76
|
+
}
|
|
77
|
+
handleSearchNext() {
|
|
78
|
+
(0, search_1.findNext)(this.view);
|
|
79
|
+
}
|
|
80
|
+
handleSearchPrev() {
|
|
81
|
+
(0, search_1.findPrevious)(this.view);
|
|
82
|
+
}
|
|
83
|
+
handleSearchConfigChange({ isCaseSensitive, isWholeWord, }) {
|
|
84
|
+
this.setViewSearch({
|
|
85
|
+
caseSensitive: isCaseSensitive,
|
|
86
|
+
wholeWord: isWholeWord,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}, {
|
|
90
|
+
provide: () => [
|
|
91
|
+
view_1.keymap.of(search_1.searchKeymap),
|
|
92
|
+
(0, search_1.search)({
|
|
93
|
+
createPanel: () => ({
|
|
94
|
+
// Create an empty search panel
|
|
95
|
+
dom: document.createElement('div'),
|
|
96
|
+
}),
|
|
97
|
+
}),
|
|
98
|
+
],
|
|
99
|
+
});
|
|
100
|
+
exports.SearchPanelPlugin = SearchPanelPlugin;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
interface SearchConfig {
|
|
3
|
+
isCaseSensitive: boolean;
|
|
4
|
+
isWholeWord: boolean;
|
|
5
|
+
}
|
|
6
|
+
interface SearchCardProps {
|
|
7
|
+
onSearchKeyDown?: (query: string) => void;
|
|
8
|
+
onChange?: (query: string) => void;
|
|
9
|
+
onClose?: (query: string) => void;
|
|
10
|
+
onSearchPrev?: (query: string) => void;
|
|
11
|
+
onSearchNext?: (query: string) => void;
|
|
12
|
+
onConfigChange?: (config: SearchConfig) => void;
|
|
13
|
+
}
|
|
14
|
+
export declare const SearchCard: React.FC<SearchCardProps>;
|
|
15
|
+
export interface SearchPopupProps {
|
|
16
|
+
anchor: HTMLElement;
|
|
17
|
+
onChange: (query: string) => void;
|
|
18
|
+
onClose: () => void;
|
|
19
|
+
onSearchNext: () => void;
|
|
20
|
+
onSearchPrev: () => void;
|
|
21
|
+
onConfigChange: (config: SearchConfig) => void;
|
|
22
|
+
open: boolean;
|
|
23
|
+
}
|
|
24
|
+
export declare const SearchPopup: React.FC<SearchPopupProps>;
|
|
25
|
+
interface SearchPopupWithRefProps extends Omit<SearchPopupProps, 'anchor'> {
|
|
26
|
+
anchor: HTMLElement | null;
|
|
27
|
+
}
|
|
28
|
+
export declare function renderSearchPopup({ anchor, open, onClose, ...props }: SearchPopupWithRefProps): JSX.Element;
|
|
29
|
+
export {};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.renderSearchPopup = exports.SearchPopup = exports.SearchCard = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const react_1 = tslib_1.__importStar(require("react"));
|
|
6
|
+
const icons_1 = require("@gravity-ui/icons");
|
|
7
|
+
const uikit_1 = require("@gravity-ui/uikit");
|
|
8
|
+
const classname_1 = require("../../../../classname");
|
|
9
|
+
const search_1 = require("../../../../i18n/search");
|
|
10
|
+
const handlers_1 = require("../../../../utils/handlers");
|
|
11
|
+
const b = (0, classname_1.cn)('search-card');
|
|
12
|
+
const noop = () => { };
|
|
13
|
+
const SearchCard = ({ onChange = noop, onClose = noop, onSearchPrev = noop, onSearchNext = noop, onConfigChange = noop, }) => {
|
|
14
|
+
const [query, setQuery] = (0, react_1.useState)('');
|
|
15
|
+
const [isCaseSensitive, setIsCaseSensitive] = (0, react_1.useState)(false);
|
|
16
|
+
const [isWholeWord, setIsWholeWord] = (0, react_1.useState)(false);
|
|
17
|
+
const textInputRef = (0, react_1.useRef)(null);
|
|
18
|
+
const setInputFocus = () => {
|
|
19
|
+
var _a;
|
|
20
|
+
(_a = textInputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
21
|
+
};
|
|
22
|
+
const handleInputChange = (event) => {
|
|
23
|
+
const { target: { value }, } = event;
|
|
24
|
+
setQuery(value);
|
|
25
|
+
onChange(value);
|
|
26
|
+
};
|
|
27
|
+
const handleClose = () => {
|
|
28
|
+
setQuery('');
|
|
29
|
+
onClose(query);
|
|
30
|
+
setInputFocus();
|
|
31
|
+
};
|
|
32
|
+
const handlePrev = () => {
|
|
33
|
+
onSearchPrev(query);
|
|
34
|
+
setInputFocus();
|
|
35
|
+
};
|
|
36
|
+
const handleNext = () => {
|
|
37
|
+
onSearchNext(query);
|
|
38
|
+
setInputFocus();
|
|
39
|
+
};
|
|
40
|
+
const handleIsCaseSensitive = () => {
|
|
41
|
+
onConfigChange({
|
|
42
|
+
isCaseSensitive: !isCaseSensitive,
|
|
43
|
+
isWholeWord,
|
|
44
|
+
});
|
|
45
|
+
setIsCaseSensitive((isCaseSensitive) => !isCaseSensitive);
|
|
46
|
+
setInputFocus();
|
|
47
|
+
};
|
|
48
|
+
const handleIsWholeWord = () => {
|
|
49
|
+
onConfigChange({
|
|
50
|
+
isCaseSensitive,
|
|
51
|
+
isWholeWord: !isWholeWord,
|
|
52
|
+
});
|
|
53
|
+
setIsWholeWord((isWholeWord) => !isWholeWord);
|
|
54
|
+
setInputFocus();
|
|
55
|
+
};
|
|
56
|
+
const handleSearchKeyPress = (0, handlers_1.enterKeyHandler)(handleNext);
|
|
57
|
+
return (react_1.default.createElement(uikit_1.Card, { className: b() },
|
|
58
|
+
react_1.default.createElement("div", { className: b('header') },
|
|
59
|
+
react_1.default.createElement("span", { className: b('title') },
|
|
60
|
+
" ",
|
|
61
|
+
(0, search_1.i18n)('title')),
|
|
62
|
+
react_1.default.createElement(uikit_1.Button, { onClick: handleClose, size: "s", view: "flat" },
|
|
63
|
+
react_1.default.createElement(uikit_1.Icon, { data: icons_1.Xmark, size: 14 }))),
|
|
64
|
+
react_1.default.createElement(uikit_1.TextInput, { controlRef: textInputRef, className: (0, uikit_1.sp)({ mb: 2 }), size: "s", autoFocus: true, onKeyPress: handleSearchKeyPress, onChange: handleInputChange, value: query, endContent: react_1.default.createElement(react_1.default.Fragment, null,
|
|
65
|
+
react_1.default.createElement(uikit_1.Button, { onClick: handlePrev },
|
|
66
|
+
react_1.default.createElement(uikit_1.Icon, { data: icons_1.ChevronUp, size: 12 })),
|
|
67
|
+
react_1.default.createElement(uikit_1.Button, { onClick: handleNext },
|
|
68
|
+
react_1.default.createElement(uikit_1.Icon, { data: icons_1.ChevronDown, size: 12 }))) }),
|
|
69
|
+
react_1.default.createElement(uikit_1.Checkbox, { size: "m", onUpdate: handleIsCaseSensitive, checked: isCaseSensitive, className: (0, uikit_1.sp)({ mr: 4 }) }, (0, search_1.i18n)('label_case-sensitive')),
|
|
70
|
+
react_1.default.createElement(uikit_1.Checkbox, { size: "m", onUpdate: handleIsWholeWord, checked: isWholeWord }, (0, search_1.i18n)('label_whole-word'))));
|
|
71
|
+
};
|
|
72
|
+
exports.SearchCard = SearchCard;
|
|
73
|
+
const SearchPopup = (_a) => {
|
|
74
|
+
var { open, anchor, onClose } = _a, props = tslib_1.__rest(_a, ["open", "anchor", "onClose"]);
|
|
75
|
+
const anchorRef = (0, react_1.useRef)(anchor);
|
|
76
|
+
return (react_1.default.createElement(uikit_1.Popup, { onEscapeKeyDown: onClose, open: anchorRef.current && open, anchorRef: anchorRef, placement: "bottom-end" },
|
|
77
|
+
react_1.default.createElement(exports.SearchCard, Object.assign({ onClose: onClose }, props))));
|
|
78
|
+
};
|
|
79
|
+
exports.SearchPopup = SearchPopup;
|
|
80
|
+
exports.SearchPopup.displayName = 'SearchPopup';
|
|
81
|
+
function renderSearchPopup(_a) {
|
|
82
|
+
var { anchor, open, onClose } = _a, props = tslib_1.__rest(_a, ["anchor", "open", "onClose"]);
|
|
83
|
+
return (react_1.default.createElement(react_1.default.Fragment, null, anchor && react_1.default.createElement(exports.SearchPopup, Object.assign({ open: open, onClose: onClose, anchor: anchor }, props))));
|
|
84
|
+
}
|
|
85
|
+
exports.renderSearchPopup = renderSearchPopup;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
export declare function enterKeyHandler<T>(handler: React.KeyboardEventHandler<T>): React.KeyboardEventHandler<T>;
|
|
3
|
+
export declare function escapeKeyHandler<T>(handler: React.KeyboardEventHandler<T>): React.KeyboardEventHandler<T>;
|
|
4
|
+
export declare function combinedKeyHandler<T>(handlers: Record<string, React.KeyboardEventHandler<T>>): React.KeyboardEventHandler<T>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.combinedKeyHandler = exports.escapeKeyHandler = exports.enterKeyHandler = void 0;
|
|
4
|
+
const shortcuts_1 = require("../shortcuts");
|
|
5
|
+
function enterKeyHandler(handler) {
|
|
6
|
+
return (event) => {
|
|
7
|
+
if (event.key === shortcuts_1.Key.Enter) {
|
|
8
|
+
handler(event);
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
exports.enterKeyHandler = enterKeyHandler;
|
|
13
|
+
function escapeKeyHandler(handler) {
|
|
14
|
+
return (event) => {
|
|
15
|
+
if (event.key === shortcuts_1.Key.Esc) {
|
|
16
|
+
handler(event);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
exports.escapeKeyHandler = escapeKeyHandler;
|
|
21
|
+
function combinedKeyHandler(handlers) {
|
|
22
|
+
return (event) => {
|
|
23
|
+
const handler = handlers[event.key];
|
|
24
|
+
if (handler) {
|
|
25
|
+
handler(event);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
exports.combinedKeyHandler = combinedKeyHandler;
|
package/build/cjs/version.js
CHANGED
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.VERSION = void 0;
|
|
4
4
|
/** During build process, the current version will be injected here */
|
|
5
|
-
exports.VERSION = typeof '13.
|
|
5
|
+
exports.VERSION = typeof '13.5.0' !== 'undefined' ? '13.5.0' : 'unknown';
|
|
@@ -19,7 +19,7 @@ export declare type ToolbarActionData = {
|
|
|
19
19
|
[key: string]: any;
|
|
20
20
|
};
|
|
21
21
|
};
|
|
22
|
-
interface EventMap {
|
|
22
|
+
export interface EventMap {
|
|
23
23
|
change: null;
|
|
24
24
|
cancel: null;
|
|
25
25
|
submit: null;
|
|
@@ -70,4 +70,3 @@ export declare type EditorOptions = Pick<WysiwygEditorOptions, 'allowHTML' | 'li
|
|
|
70
70
|
preset: EditorPreset;
|
|
71
71
|
extraMarkupExtensions?: CodemirrorExtension[];
|
|
72
72
|
};
|
|
73
|
-
export {};
|
|
@@ -152,6 +152,7 @@ export class EditorImpl extends SafeEventEmitter {
|
|
|
152
152
|
uploadHandler: this.fileUploadHandler,
|
|
153
153
|
needImgDimms: this.needToSetDimensionsForUploadedImages,
|
|
154
154
|
extraMarkupExtensions: __classPrivateFieldGet(this, _EditorImpl_extraMarkupExtensions, "f"),
|
|
155
|
+
receiver: this,
|
|
155
156
|
})), "f");
|
|
156
157
|
}
|
|
157
158
|
return __classPrivateFieldGet(this, _EditorImpl_markupEditor, "f");
|
|
@@ -110,6 +110,7 @@ export const MarkdownEditorView = React.forwardRef((props, ref) => {
|
|
|
110
110
|
React.createElement(SplitModeView, { editor: editor, ref: splitModeViewWrapperRef })))))));
|
|
111
111
|
});
|
|
112
112
|
MarkdownEditorView.displayName = 'MarkdownEditorView';
|
|
113
|
+
const MarkupSearchAnchor = ({ mode }) => (React.createElement(React.Fragment, null, mode === 'markup' && React.createElement("div", { className: "g-md-search-anchor" })));
|
|
113
114
|
function Settings(props) {
|
|
114
115
|
const wrapperRef = useRef(null);
|
|
115
116
|
const isSticky = useSticky(wrapperRef) && props.toolbarVisibility && props.stickyToolbar;
|
|
@@ -118,7 +119,8 @@ function Settings(props) {
|
|
|
118
119
|
withToolbar: props.toolbarVisibility,
|
|
119
120
|
stickyActive: isSticky,
|
|
120
121
|
}) },
|
|
121
|
-
React.createElement(EditorSettings, Object.assign({}, props))
|
|
122
|
+
React.createElement(EditorSettings, Object.assign({}, props)),
|
|
123
|
+
React.createElement(MarkupSearchAnchor, Object.assign({}, props)))));
|
|
122
124
|
}
|
|
123
125
|
function isPreviewKeyDown(e) {
|
|
124
126
|
const modKey = isMac() ? e.metaKey : e.ctrlKey;
|
|
@@ -2,8 +2,8 @@ import React from 'react';
|
|
|
2
2
|
import { TextInputFixed } from '../../../../../forms/TextInput';
|
|
3
3
|
import { UrlInputRow } from '../../../../../forms/UrlInputRow';
|
|
4
4
|
import Form from '../../../../../forms/base';
|
|
5
|
-
import { enterKeyHandler } from '../../../../../forms/utils';
|
|
6
5
|
import { i18n } from '../../../../../i18n/forms';
|
|
6
|
+
import { enterKeyHandler } from '../../../../../utils/handlers';
|
|
7
7
|
export const LinkForm = React.memo(function LinkForm({ href, autoFocus, onChange, onCancel, }) {
|
|
8
8
|
const [url, setUrl] = React.useState(href);
|
|
9
9
|
const handleSubmit = () => {
|
|
@@ -4,9 +4,9 @@ import isNumber from 'is-number';
|
|
|
4
4
|
import { cn } from '../../../../../../classname';
|
|
5
5
|
import Form from '../../../../../../forms/base';
|
|
6
6
|
import { NumberInput } from '../../../../../../forms/components';
|
|
7
|
-
import { enterKeyHandler } from '../../../../../../forms/utils';
|
|
8
7
|
import { i18n } from '../../../../../../i18n/forms';
|
|
9
8
|
import { useAutoFocus } from '../../../../../../react-utils/useAutoFocus';
|
|
9
|
+
import { enterKeyHandler } from '../../../../../../utils/handlers';
|
|
10
10
|
import { LinkAttr, linkType } from '../../../../../markdown';
|
|
11
11
|
import { ImgSizeAttr } from '../../../../../specs';
|
|
12
12
|
import './ImageForm.css';
|
|
@@ -3,10 +3,10 @@ import { Tabs, TextInput } from '@gravity-ui/uikit';
|
|
|
3
3
|
import { cn } from '../classname';
|
|
4
4
|
import { i18n } from '../i18n/forms';
|
|
5
5
|
import { isFunction } from '../lodash';
|
|
6
|
+
import { enterKeyHandler } from '../utils/handlers';
|
|
6
7
|
import { TextInputFixed } from './TextInput';
|
|
7
8
|
import Form from './base';
|
|
8
9
|
import { ButtonAttach } from './components';
|
|
9
|
-
import { enterKeyHandler } from './utils';
|
|
10
10
|
const b = cn('file-form');
|
|
11
11
|
export const FileForm = ({ className, autoFocus, onCancel, onSubmit, onAttach, loading, }) => {
|
|
12
12
|
const [tabId, setTabId] = React.useState(() => isFunction(onAttach) ? "attach" /* TabId.Attach */ : "link" /* TabId.Link */);
|
|
@@ -3,10 +3,10 @@ import { Tabs, TextInput } from '@gravity-ui/uikit';
|
|
|
3
3
|
import { cn } from '../classname';
|
|
4
4
|
import { i18n } from '../i18n/forms';
|
|
5
5
|
import { isFunction } from '../lodash';
|
|
6
|
+
import { enterKeyHandler } from '../utils/handlers';
|
|
6
7
|
import { TextInputFixed } from './TextInput';
|
|
7
8
|
import Form from './base';
|
|
8
9
|
import { ButtonAttach, NumberInput } from './components';
|
|
9
|
-
import { enterKeyHandler } from './utils';
|
|
10
10
|
import './ImageForm.css';
|
|
11
11
|
const b = cn('image-form');
|
|
12
12
|
export const ImageForm = ({ className, autoFocus, onCancel, onSubmit, onAttach, loading, }) => {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { TextInput } from '@gravity-ui/uikit';
|
|
3
3
|
import { i18n } from '../i18n/forms';
|
|
4
|
+
import { enterKeyHandler } from '../utils/handlers';
|
|
4
5
|
import { TextInputFixed } from './TextInput';
|
|
5
6
|
import { UrlInputRow } from './UrlInputRow';
|
|
6
7
|
import Form from './base';
|
|
7
|
-
import { enterKeyHandler } from './utils';
|
|
8
8
|
export const LinkForm = React.memo(function LinkForm({ className, autoFocus, initialUrl, initialText, readOnlyText, onSubmit, onCancel, }) {
|
|
9
9
|
const [url, setUrl] = React.useState(initialUrl !== null && initialUrl !== void 0 ? initialUrl : '');
|
|
10
10
|
const [text, setText] = React.useState(initialText !== null && initialText !== void 0 ? initialText : '');
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const i18n: <G extends "title" | "label_case-sensitive" | "label_whole-word", S extends string>(key: G | (string extends S ? S : never), params?: {
|
|
2
|
+
[key: string]: any;
|
|
3
|
+
} | undefined) => S extends G ? {
|
|
4
|
+
"label_case-sensitive": string;
|
|
5
|
+
"label_whole-word": string;
|
|
6
|
+
title: string;
|
|
7
|
+
}[G] : string;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { Extension, StateCommand } from '@codemirror/state';
|
|
2
2
|
import { EditorView, EditorViewConfig } from '@codemirror/view';
|
|
3
|
+
import { EventMap } from '../../bundle/Editor';
|
|
3
4
|
import { ReactRenderStorage } from '../../extensions';
|
|
5
|
+
import { Receiver } from '../../utils';
|
|
4
6
|
import { FileUploadHandler } from './files-upload-facet';
|
|
5
7
|
export declare type CreateCodemirrorParams = {
|
|
6
8
|
doc: EditorViewConfig['doc'];
|
|
@@ -14,6 +16,7 @@ export declare type CreateCodemirrorParams = {
|
|
|
14
16
|
uploadHandler?: FileUploadHandler;
|
|
15
17
|
needImgDimms?: boolean;
|
|
16
18
|
extraMarkupExtensions?: Extension[];
|
|
19
|
+
receiver?: Receiver<EventMap>;
|
|
17
20
|
};
|
|
18
21
|
export declare function createCodemirror(params: CreateCodemirrorParams): EditorView;
|
|
19
22
|
export declare function withLogger(action: string, command: StateCommand): StateCommand;
|
|
@@ -10,9 +10,10 @@ import { FileUploadHandlerFacet } from './files-upload-facet';
|
|
|
10
10
|
import { gravityHighlightStyle, gravityTheme } from './gravity';
|
|
11
11
|
import { PairingCharactersExtension } from './pairing-chars';
|
|
12
12
|
import { ReactRendererFacet } from './react-facet';
|
|
13
|
+
import { SearchPanelPlugin } from './search-plugin/plugin';
|
|
13
14
|
import { yfmLang } from './yfm';
|
|
14
15
|
export function createCodemirror(params) {
|
|
15
|
-
const { doc, placeholderText, reactRenderer, onCancel, onScroll, onSubmit, onChange, onDocChange, extraMarkupExtensions, } = params;
|
|
16
|
+
const { doc, placeholderText, reactRenderer, onCancel, onScroll, onSubmit, onChange, onDocChange, extraMarkupExtensions, receiver, } = params;
|
|
16
17
|
const extensions = [
|
|
17
18
|
gravityTheme,
|
|
18
19
|
placeholder(placeholderText),
|
|
@@ -66,6 +67,10 @@ export function createCodemirror(params) {
|
|
|
66
67
|
onScroll(event);
|
|
67
68
|
},
|
|
68
69
|
}),
|
|
70
|
+
SearchPanelPlugin({
|
|
71
|
+
anchorSelector: '.g-md-search-anchor',
|
|
72
|
+
receiver,
|
|
73
|
+
}),
|
|
69
74
|
];
|
|
70
75
|
if (params.uploadHandler) {
|
|
71
76
|
extensions.push(FileUploadHandlerFacet.of({
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { EditorView, ViewPlugin, ViewUpdate } from '@codemirror/view';
|
|
2
|
+
import { EditorMode, EventMap } from '../../../bundle/Editor';
|
|
3
|
+
import type { RendererItem } from '../../../extensions';
|
|
4
|
+
import { Receiver } from '../../../utils';
|
|
5
|
+
interface SearchQueryParams {
|
|
6
|
+
search: string;
|
|
7
|
+
caseSensitive?: boolean;
|
|
8
|
+
literal?: boolean;
|
|
9
|
+
regexp?: boolean;
|
|
10
|
+
replace?: string;
|
|
11
|
+
valid?: boolean;
|
|
12
|
+
wholeWord?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export interface SearchPanelPluginParams {
|
|
15
|
+
anchorSelector: string;
|
|
16
|
+
inputDelay?: number;
|
|
17
|
+
receiver?: Receiver<EventMap>;
|
|
18
|
+
}
|
|
19
|
+
export declare const SearchPanelPlugin: (params: SearchPanelPluginParams) => ViewPlugin<{
|
|
20
|
+
readonly view: EditorView;
|
|
21
|
+
readonly params: SearchPanelPluginParams;
|
|
22
|
+
anchor: HTMLElement | null;
|
|
23
|
+
renderer: RendererItem | null;
|
|
24
|
+
searchQuery: SearchQueryParams;
|
|
25
|
+
receiver: Receiver<EventMap> | undefined;
|
|
26
|
+
setViewSearchWithDelay: (config: Partial<SearchQueryParams>) => void;
|
|
27
|
+
update(update: ViewUpdate): void;
|
|
28
|
+
destroy(): void;
|
|
29
|
+
setViewSearch(config: Partial<SearchQueryParams>): void;
|
|
30
|
+
handleEditorModeChange({ mode }: {
|
|
31
|
+
mode: EditorMode;
|
|
32
|
+
}): void;
|
|
33
|
+
handleChange(search: string): void;
|
|
34
|
+
handleClose(): void;
|
|
35
|
+
handleSearchNext(): void;
|
|
36
|
+
handleSearchPrev(): void;
|
|
37
|
+
handleSearchConfigChange({ isCaseSensitive, isWholeWord, }: {
|
|
38
|
+
isCaseSensitive?: boolean | undefined;
|
|
39
|
+
isWholeWord?: boolean | undefined;
|
|
40
|
+
}): void;
|
|
41
|
+
}>;
|
|
42
|
+
export {};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { SearchQuery, closeSearchPanel, findNext, findPrevious, search, searchKeymap, searchPanelOpen, setSearchQuery, } from '@codemirror/search';
|
|
2
|
+
import { ViewPlugin, keymap } from '@codemirror/view';
|
|
3
|
+
import { debounce } from '../../../lodash';
|
|
4
|
+
import { ReactRendererFacet } from '../react-facet';
|
|
5
|
+
import { renderSearchPopup } from './view/SearchPopup';
|
|
6
|
+
const INPUT_DELAY = 200;
|
|
7
|
+
export const SearchPanelPlugin = (params) => ViewPlugin.fromClass(class {
|
|
8
|
+
constructor(view) {
|
|
9
|
+
var _a, _b;
|
|
10
|
+
this.searchQuery = {
|
|
11
|
+
search: '',
|
|
12
|
+
caseSensitive: false,
|
|
13
|
+
wholeWord: false,
|
|
14
|
+
};
|
|
15
|
+
this.view = view;
|
|
16
|
+
this.anchor = null;
|
|
17
|
+
this.renderer = null;
|
|
18
|
+
this.params = params;
|
|
19
|
+
this.receiver = params.receiver;
|
|
20
|
+
this.handleClose = this.handleClose.bind(this);
|
|
21
|
+
this.handleChange = this.handleChange.bind(this);
|
|
22
|
+
this.handleSearchNext = this.handleSearchNext.bind(this);
|
|
23
|
+
this.handleSearchPrev = this.handleSearchPrev.bind(this);
|
|
24
|
+
this.handleSearchConfigChange = this.handleSearchConfigChange.bind(this);
|
|
25
|
+
this.handleEditorModeChange = this.handleEditorModeChange.bind(this);
|
|
26
|
+
this.setViewSearchWithDelay = debounce(this.setViewSearch, (_a = this.params.inputDelay) !== null && _a !== void 0 ? _a : INPUT_DELAY);
|
|
27
|
+
(_b = this.receiver) === null || _b === void 0 ? void 0 : _b.on('change-editor-mode', this.handleEditorModeChange);
|
|
28
|
+
}
|
|
29
|
+
update(update) {
|
|
30
|
+
var _a;
|
|
31
|
+
const isPanelOpen = searchPanelOpen(update.state);
|
|
32
|
+
if (isPanelOpen && !this.renderer) {
|
|
33
|
+
this.anchor = document.querySelector(this.params.anchorSelector);
|
|
34
|
+
this.renderer = this.view.state
|
|
35
|
+
.facet(ReactRendererFacet)
|
|
36
|
+
.createItem('cm-search', () => renderSearchPopup({
|
|
37
|
+
open: true,
|
|
38
|
+
anchor: this.anchor,
|
|
39
|
+
onChange: this.handleChange,
|
|
40
|
+
onClose: this.handleClose,
|
|
41
|
+
onSearchNext: this.handleSearchNext,
|
|
42
|
+
onSearchPrev: this.handleSearchPrev,
|
|
43
|
+
onConfigChange: this.handleSearchConfigChange,
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
46
|
+
else if (!isPanelOpen && this.renderer) {
|
|
47
|
+
(_a = this.renderer) === null || _a === void 0 ? void 0 : _a.remove();
|
|
48
|
+
this.renderer = null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
destroy() {
|
|
52
|
+
var _a, _b;
|
|
53
|
+
(_a = this.renderer) === null || _a === void 0 ? void 0 : _a.remove();
|
|
54
|
+
this.renderer = null;
|
|
55
|
+
(_b = this.receiver) === null || _b === void 0 ? void 0 : _b.off('change-editor-mode', this.handleEditorModeChange);
|
|
56
|
+
}
|
|
57
|
+
setViewSearch(config) {
|
|
58
|
+
this.searchQuery = Object.assign(Object.assign({}, this.searchQuery), config);
|
|
59
|
+
const searchQuery = new SearchQuery(Object.assign({}, this.searchQuery));
|
|
60
|
+
this.view.dispatch({ effects: setSearchQuery.of(searchQuery) });
|
|
61
|
+
}
|
|
62
|
+
handleEditorModeChange({ mode }) {
|
|
63
|
+
if (mode === 'wysiwyg') {
|
|
64
|
+
closeSearchPanel(this.view);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
handleChange(search) {
|
|
68
|
+
this.setViewSearchWithDelay({ search });
|
|
69
|
+
}
|
|
70
|
+
handleClose() {
|
|
71
|
+
this.setViewSearch({ search: '' });
|
|
72
|
+
closeSearchPanel(this.view);
|
|
73
|
+
}
|
|
74
|
+
handleSearchNext() {
|
|
75
|
+
findNext(this.view);
|
|
76
|
+
}
|
|
77
|
+
handleSearchPrev() {
|
|
78
|
+
findPrevious(this.view);
|
|
79
|
+
}
|
|
80
|
+
handleSearchConfigChange({ isCaseSensitive, isWholeWord, }) {
|
|
81
|
+
this.setViewSearch({
|
|
82
|
+
caseSensitive: isCaseSensitive,
|
|
83
|
+
wholeWord: isWholeWord,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}, {
|
|
87
|
+
provide: () => [
|
|
88
|
+
keymap.of(searchKeymap),
|
|
89
|
+
search({
|
|
90
|
+
createPanel: () => ({
|
|
91
|
+
// Create an empty search panel
|
|
92
|
+
dom: document.createElement('div'),
|
|
93
|
+
}),
|
|
94
|
+
}),
|
|
95
|
+
],
|
|
96
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import './SearchPopup.css';
|
|
3
|
+
interface SearchConfig {
|
|
4
|
+
isCaseSensitive: boolean;
|
|
5
|
+
isWholeWord: boolean;
|
|
6
|
+
}
|
|
7
|
+
interface SearchCardProps {
|
|
8
|
+
onSearchKeyDown?: (query: string) => void;
|
|
9
|
+
onChange?: (query: string) => void;
|
|
10
|
+
onClose?: (query: string) => void;
|
|
11
|
+
onSearchPrev?: (query: string) => void;
|
|
12
|
+
onSearchNext?: (query: string) => void;
|
|
13
|
+
onConfigChange?: (config: SearchConfig) => void;
|
|
14
|
+
}
|
|
15
|
+
export declare const SearchCard: React.FC<SearchCardProps>;
|
|
16
|
+
export interface SearchPopupProps {
|
|
17
|
+
anchor: HTMLElement;
|
|
18
|
+
onChange: (query: string) => void;
|
|
19
|
+
onClose: () => void;
|
|
20
|
+
onSearchNext: () => void;
|
|
21
|
+
onSearchPrev: () => void;
|
|
22
|
+
onConfigChange: (config: SearchConfig) => void;
|
|
23
|
+
open: boolean;
|
|
24
|
+
}
|
|
25
|
+
export declare const SearchPopup: React.FC<SearchPopupProps>;
|
|
26
|
+
interface SearchPopupWithRefProps extends Omit<SearchPopupProps, 'anchor'> {
|
|
27
|
+
anchor: HTMLElement | null;
|
|
28
|
+
}
|
|
29
|
+
export declare function renderSearchPopup({ anchor, open, onClose, ...props }: SearchPopupWithRefProps): JSX.Element;
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { __rest } from "tslib";
|
|
2
|
+
import React, { useRef, useState } from 'react';
|
|
3
|
+
import { ChevronDown, ChevronUp, Xmark } from '@gravity-ui/icons';
|
|
4
|
+
import { Button, Card, Checkbox, Icon, Popup, TextInput, sp, } from '@gravity-ui/uikit';
|
|
5
|
+
import { cn } from '../../../../classname';
|
|
6
|
+
import { i18n } from '../../../../i18n/search';
|
|
7
|
+
import { enterKeyHandler } from '../../../../utils/handlers';
|
|
8
|
+
import './SearchPopup.css';
|
|
9
|
+
const b = cn('search-card');
|
|
10
|
+
const noop = () => { };
|
|
11
|
+
export const SearchCard = ({ onChange = noop, onClose = noop, onSearchPrev = noop, onSearchNext = noop, onConfigChange = noop, }) => {
|
|
12
|
+
const [query, setQuery] = useState('');
|
|
13
|
+
const [isCaseSensitive, setIsCaseSensitive] = useState(false);
|
|
14
|
+
const [isWholeWord, setIsWholeWord] = useState(false);
|
|
15
|
+
const textInputRef = useRef(null);
|
|
16
|
+
const setInputFocus = () => {
|
|
17
|
+
var _a;
|
|
18
|
+
(_a = textInputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
19
|
+
};
|
|
20
|
+
const handleInputChange = (event) => {
|
|
21
|
+
const { target: { value }, } = event;
|
|
22
|
+
setQuery(value);
|
|
23
|
+
onChange(value);
|
|
24
|
+
};
|
|
25
|
+
const handleClose = () => {
|
|
26
|
+
setQuery('');
|
|
27
|
+
onClose(query);
|
|
28
|
+
setInputFocus();
|
|
29
|
+
};
|
|
30
|
+
const handlePrev = () => {
|
|
31
|
+
onSearchPrev(query);
|
|
32
|
+
setInputFocus();
|
|
33
|
+
};
|
|
34
|
+
const handleNext = () => {
|
|
35
|
+
onSearchNext(query);
|
|
36
|
+
setInputFocus();
|
|
37
|
+
};
|
|
38
|
+
const handleIsCaseSensitive = () => {
|
|
39
|
+
onConfigChange({
|
|
40
|
+
isCaseSensitive: !isCaseSensitive,
|
|
41
|
+
isWholeWord,
|
|
42
|
+
});
|
|
43
|
+
setIsCaseSensitive((isCaseSensitive) => !isCaseSensitive);
|
|
44
|
+
setInputFocus();
|
|
45
|
+
};
|
|
46
|
+
const handleIsWholeWord = () => {
|
|
47
|
+
onConfigChange({
|
|
48
|
+
isCaseSensitive,
|
|
49
|
+
isWholeWord: !isWholeWord,
|
|
50
|
+
});
|
|
51
|
+
setIsWholeWord((isWholeWord) => !isWholeWord);
|
|
52
|
+
setInputFocus();
|
|
53
|
+
};
|
|
54
|
+
const handleSearchKeyPress = enterKeyHandler(handleNext);
|
|
55
|
+
return (React.createElement(Card, { className: b() },
|
|
56
|
+
React.createElement("div", { className: b('header') },
|
|
57
|
+
React.createElement("span", { className: b('title') },
|
|
58
|
+
" ",
|
|
59
|
+
i18n('title')),
|
|
60
|
+
React.createElement(Button, { onClick: handleClose, size: "s", view: "flat" },
|
|
61
|
+
React.createElement(Icon, { data: Xmark, size: 14 }))),
|
|
62
|
+
React.createElement(TextInput, { controlRef: textInputRef, className: sp({ mb: 2 }), size: "s", autoFocus: true, onKeyPress: handleSearchKeyPress, onChange: handleInputChange, value: query, endContent: React.createElement(React.Fragment, null,
|
|
63
|
+
React.createElement(Button, { onClick: handlePrev },
|
|
64
|
+
React.createElement(Icon, { data: ChevronUp, size: 12 })),
|
|
65
|
+
React.createElement(Button, { onClick: handleNext },
|
|
66
|
+
React.createElement(Icon, { data: ChevronDown, size: 12 }))) }),
|
|
67
|
+
React.createElement(Checkbox, { size: "m", onUpdate: handleIsCaseSensitive, checked: isCaseSensitive, className: sp({ mr: 4 }) }, i18n('label_case-sensitive')),
|
|
68
|
+
React.createElement(Checkbox, { size: "m", onUpdate: handleIsWholeWord, checked: isWholeWord }, i18n('label_whole-word'))));
|
|
69
|
+
};
|
|
70
|
+
export const SearchPopup = (_a) => {
|
|
71
|
+
var { open, anchor, onClose } = _a, props = __rest(_a, ["open", "anchor", "onClose"]);
|
|
72
|
+
const anchorRef = useRef(anchor);
|
|
73
|
+
return (React.createElement(Popup, { onEscapeKeyDown: onClose, open: anchorRef.current && open, anchorRef: anchorRef, placement: "bottom-end" },
|
|
74
|
+
React.createElement(SearchCard, Object.assign({ onClose: onClose }, props))));
|
|
75
|
+
};
|
|
76
|
+
SearchPopup.displayName = 'SearchPopup';
|
|
77
|
+
export function renderSearchPopup(_a) {
|
|
78
|
+
var { anchor, open, onClose } = _a, props = __rest(_a, ["anchor", "open", "onClose"]);
|
|
79
|
+
return (React.createElement(React.Fragment, null, anchor && React.createElement(SearchPopup, Object.assign({ open: open, onClose: onClose, anchor: anchor }, props))));
|
|
80
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
export declare function enterKeyHandler<T>(handler: React.KeyboardEventHandler<T>): React.KeyboardEventHandler<T>;
|
|
3
|
+
export declare function escapeKeyHandler<T>(handler: React.KeyboardEventHandler<T>): React.KeyboardEventHandler<T>;
|
|
4
|
+
export declare function combinedKeyHandler<T>(handlers: Record<string, React.KeyboardEventHandler<T>>): React.KeyboardEventHandler<T>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Key } from '../shortcuts';
|
|
2
|
+
export function enterKeyHandler(handler) {
|
|
3
|
+
return (event) => {
|
|
4
|
+
if (event.key === Key.Enter) {
|
|
5
|
+
handler(event);
|
|
6
|
+
}
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
export function escapeKeyHandler(handler) {
|
|
10
|
+
return (event) => {
|
|
11
|
+
if (event.key === Key.Esc) {
|
|
12
|
+
handler(event);
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export function combinedKeyHandler(handlers) {
|
|
17
|
+
return (event) => {
|
|
18
|
+
const handler = handlers[event.key];
|
|
19
|
+
if (handler) {
|
|
20
|
+
handler(event);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
}
|
package/build/esm/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** During build process, the current version will be injected here */
|
|
2
|
-
export const VERSION = typeof '13.
|
|
2
|
+
export const VERSION = typeof '13.5.0' !== 'undefined' ? '13.5.0' : 'unknown';
|
package/build/styles.css
CHANGED
|
@@ -1290,6 +1290,15 @@ img.ProseMirror-separator {
|
|
|
1290
1290
|
.g-root_theme_dark .g-md-yfm-html-block_editing .g-text-area__content {
|
|
1291
1291
|
color: var(--g-color-text-primary);
|
|
1292
1292
|
}
|
|
1293
|
+
.g-md-search-card {
|
|
1294
|
+
padding: var(--g-spacing-2) var(--g-spacing-2) var(--g-spacing-3) var(--g-spacing-4);
|
|
1295
|
+
}
|
|
1296
|
+
.g-md-search-card__header {
|
|
1297
|
+
display: flex;
|
|
1298
|
+
justify-content: space-between;
|
|
1299
|
+
align-items: center;
|
|
1300
|
+
margin-bottom: var(--g-spacing-1);
|
|
1301
|
+
}
|
|
1293
1302
|
.g-md-code-block-toolbar {
|
|
1294
1303
|
margin: 2px 8px;
|
|
1295
1304
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gravity-ui/markdown-editor",
|
|
3
|
-
"version": "13.
|
|
3
|
+
"version": "13.5.0",
|
|
4
4
|
"description": "Markdown wysiwyg and markup editor",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -163,6 +163,7 @@
|
|
|
163
163
|
"@codemirror/commands": "6.5.0",
|
|
164
164
|
"@codemirror/lang-markdown": "6.2.5",
|
|
165
165
|
"@codemirror/language": "6.10.1",
|
|
166
|
+
"@codemirror/search": "6.5.6",
|
|
166
167
|
"@codemirror/state": "6.4.1",
|
|
167
168
|
"@codemirror/view": "6.26.3",
|
|
168
169
|
"@gravity-ui/i18n": "^1.1.0",
|
|
@@ -198,9 +199,9 @@
|
|
|
198
199
|
"tslib": "^2.3.1"
|
|
199
200
|
},
|
|
200
201
|
"devDependencies": {
|
|
202
|
+
"@diplodoc/html-extension": "1.2.7",
|
|
201
203
|
"@diplodoc/latex-extension": "1.0.3",
|
|
202
204
|
"@diplodoc/mermaid-extension": "1.2.1",
|
|
203
|
-
"@diplodoc/html-extension": "1.2.7",
|
|
204
205
|
"@diplodoc/transform": "4.5.0",
|
|
205
206
|
"@gravity-ui/components": "3.0.0",
|
|
206
207
|
"@gravity-ui/eslint-config": "3.1.1",
|
|
@@ -268,9 +269,9 @@
|
|
|
268
269
|
}
|
|
269
270
|
},
|
|
270
271
|
"peerDependencies": {
|
|
272
|
+
"@diplodoc/html-extension": "^1.2.7",
|
|
271
273
|
"@diplodoc/latex-extension": "^1.0.3",
|
|
272
274
|
"@diplodoc/mermaid-extension": "^1.0.0",
|
|
273
|
-
"@diplodoc/html-extension": "^1.2.7",
|
|
274
275
|
"@diplodoc/transform": "^4.5.0",
|
|
275
276
|
"@gravity-ui/components": "^3.0.0",
|
|
276
277
|
"@gravity-ui/uikit": "^6.11.0",
|
package/build/cjs/forms/utils.js
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.enterKeyHandler = void 0;
|
|
4
|
-
function enterKeyHandler(handler) {
|
|
5
|
-
return (event) => {
|
|
6
|
-
if (event.key === 'Enter') {
|
|
7
|
-
handler(event);
|
|
8
|
-
}
|
|
9
|
-
};
|
|
10
|
-
}
|
|
11
|
-
exports.enterKeyHandler = enterKeyHandler;
|