@gravity-ui/markdown-editor 13.6.1 → 13.8.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/README.md CHANGED
@@ -46,9 +46,16 @@ function Editor({onSubmit}) {
46
46
  };
47
47
  }, [onSubmit]);
48
48
 
49
- return <MarkdownEditorView autofocus toaster={toaster} editor={editor} />;
49
+ return <MarkdownEditorView stickyToolbar autofocus toaster={toaster} editor={editor} />;
50
50
  }
51
51
  ```
52
+ Read more:
53
+ - [How to connect the editor in the Create React App](docs/how-to-add-editor-with-create-react-app.md)
54
+ - [How to add preview for markup mode](docs/how-to-add-preview.md)
55
+ - [How to add HTML extension](docs/how-to-connect-html-extension.md)
56
+ - [How to add Latex extension](docs/how-to-connect-latex-extension.md)
57
+ - [How to add Mermaid extension](docs/how-to-connect-mermaid-extension.md)
58
+ - [How to write extension](docs/how-to-create-extension.md)
52
59
 
53
60
  ### i18n
54
61
 
@@ -3,6 +3,8 @@ import type { Extension as CodemirrorExtension } from '@codemirror/state';
3
3
  import { CommonEditor, MarkupString } from '../common';
4
4
  import { WysiwygEditorOptions } from '../core';
5
5
  import { ReactRenderStorage } from '../extensions';
6
+ import { type CreateCodemirrorParams } from '../markup/codemirror';
7
+ import type { YfmLangOptions } from '../markup/codemirror/yfm';
6
8
  import { Receiver } from '../utils/event-emitter';
7
9
  import type { FileUploadHandler } from '../utils/upload';
8
10
  export declare type EditorMode = 'wysiwyg' | 'markup';
@@ -42,6 +44,16 @@ export interface Editor extends Receiver<EventMap>, CommonEditor {
42
44
  line: number;
43
45
  }): void;
44
46
  }
47
+ export declare type MarkupConfig = {
48
+ /** Additional extensions for codemirror instance. */
49
+ extensions?: CreateCodemirrorParams['extensions'];
50
+ /**
51
+ * Additional language data for markdown language in codemirror.
52
+ * Can be used to configure additional autocompletions and others.
53
+ * See more https://codemirror.net/docs/ref/#state.EditorState.languageDataAt
54
+ */
55
+ languageData?: YfmLangOptions['languageData'];
56
+ };
45
57
  export declare type EditorOptions = Pick<WysiwygEditorOptions, 'allowHTML' | 'linkify' | 'linkifyTlds' | 'extensions'> & {
46
58
  initialMarkup?: MarkupString;
47
59
  /** @default 'wysiwyg' */
@@ -68,5 +80,7 @@ export declare type EditorOptions = Pick<WysiwygEditorOptions, 'allowHTML' | 'li
68
80
  splitMode?: SplitMode;
69
81
  renderPreview?: RenderPreview;
70
82
  preset: EditorPreset;
83
+ /** @deprecated Put extra extensions via MarkdownEditorMarkupConfig */
71
84
  extraMarkupExtensions?: CodemirrorExtension[];
85
+ markupConfig?: MarkupConfig;
72
86
  };
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
- var _EditorImpl_markup, _EditorImpl_editorMode, _EditorImpl_toolbarVisible, _EditorImpl_splitModeEnabled, _EditorImpl_splitMode, _EditorImpl_renderPreview, _EditorImpl_wysiwygEditor, _EditorImpl_markupEditor, _EditorImpl_extraMarkupExtensions, _EditorImpl_preset, _EditorImpl_allowHTML, _EditorImpl_linkify, _EditorImpl_linkifyTlds, _EditorImpl_extensions, _EditorImpl_renderStorage, _EditorImpl_fileUploadHandler, _EditorImpl_needToSetDimensionsForUploadedImages, _EditorImpl_prepareRawMarkup;
2
+ var _EditorImpl_markup, _EditorImpl_editorMode, _EditorImpl_toolbarVisible, _EditorImpl_splitModeEnabled, _EditorImpl_splitMode, _EditorImpl_renderPreview, _EditorImpl_wysiwygEditor, _EditorImpl_markupEditor, _EditorImpl_markupConfig, _EditorImpl_preset, _EditorImpl_allowHTML, _EditorImpl_linkify, _EditorImpl_linkifyTlds, _EditorImpl_extensions, _EditorImpl_renderStorage, _EditorImpl_fileUploadHandler, _EditorImpl_needToSetDimensionsForUploadedImages, _EditorImpl_prepareRawMarkup;
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.EditorImpl = void 0;
5
5
  const tslib_1 = require("tslib");
@@ -13,7 +13,8 @@ const event_emitter_1 = require("../utils/event-emitter");
13
13
  /** @internal */
14
14
  class EditorImpl extends event_emitter_1.SafeEventEmitter {
15
15
  constructor(opts) {
16
- var _a, _b, _c, _d, _e;
16
+ var _a, _b, _c, _d, _e, _f;
17
+ var _g;
17
18
  super({ onError: logger_1.logger.error.bind(logger_1.logger) });
18
19
  _EditorImpl_markup.set(this, void 0);
19
20
  _EditorImpl_editorMode.set(this, void 0);
@@ -23,7 +24,7 @@ class EditorImpl extends event_emitter_1.SafeEventEmitter {
23
24
  _EditorImpl_renderPreview.set(this, void 0);
24
25
  _EditorImpl_wysiwygEditor.set(this, void 0);
25
26
  _EditorImpl_markupEditor.set(this, void 0);
26
- _EditorImpl_extraMarkupExtensions.set(this, void 0);
27
+ _EditorImpl_markupConfig.set(this, void 0);
27
28
  _EditorImpl_preset.set(this, void 0);
28
29
  _EditorImpl_allowHTML.set(this, void 0);
29
30
  _EditorImpl_linkify.set(this, void 0);
@@ -45,7 +46,8 @@ class EditorImpl extends event_emitter_1.SafeEventEmitter {
45
46
  tslib_1.__classPrivateFieldSet(this, _EditorImpl_linkifyTlds, opts.linkifyTlds, "f");
46
47
  tslib_1.__classPrivateFieldSet(this, _EditorImpl_allowHTML, opts.allowHTML, "f");
47
48
  tslib_1.__classPrivateFieldSet(this, _EditorImpl_extensions, opts.extensions, "f");
48
- tslib_1.__classPrivateFieldSet(this, _EditorImpl_extraMarkupExtensions, opts.extraMarkupExtensions, "f");
49
+ tslib_1.__classPrivateFieldSet(this, _EditorImpl_markupConfig, Object.assign({}, opts.markupConfig), "f");
50
+ (_f = (_g = tslib_1.__classPrivateFieldGet(this, _EditorImpl_markupConfig, "f")).extensions) !== null && _f !== void 0 ? _f : (_g.extensions = opts.extraMarkupExtensions);
49
51
  tslib_1.__classPrivateFieldSet(this, _EditorImpl_renderStorage, opts.renderStorage, "f");
50
52
  tslib_1.__classPrivateFieldSet(this, _EditorImpl_fileUploadHandler, opts.fileUploadHandler, "f");
51
53
  tslib_1.__classPrivateFieldSet(this, _EditorImpl_needToSetDimensionsForUploadedImages, Boolean(opts.needToSetDimensionsForUploadedImages), "f");
@@ -154,7 +156,8 @@ class EditorImpl extends event_emitter_1.SafeEventEmitter {
154
156
  reactRenderer: tslib_1.__classPrivateFieldGet(this, _EditorImpl_renderStorage, "f"),
155
157
  uploadHandler: this.fileUploadHandler,
156
158
  needImgDimms: this.needToSetDimensionsForUploadedImages,
157
- extraMarkupExtensions: tslib_1.__classPrivateFieldGet(this, _EditorImpl_extraMarkupExtensions, "f"),
159
+ extensions: tslib_1.__classPrivateFieldGet(this, _EditorImpl_markupConfig, "f").extensions,
160
+ yfmLangOptions: { languageData: tslib_1.__classPrivateFieldGet(this, _EditorImpl_markupConfig, "f").languageData },
158
161
  receiver: this,
159
162
  })), "f");
160
163
  }
@@ -275,4 +278,4 @@ class EditorImpl extends event_emitter_1.SafeEventEmitter {
275
278
  }
276
279
  }
277
280
  exports.EditorImpl = EditorImpl;
278
- _EditorImpl_markup = new WeakMap(), _EditorImpl_editorMode = new WeakMap(), _EditorImpl_toolbarVisible = new WeakMap(), _EditorImpl_splitModeEnabled = new WeakMap(), _EditorImpl_splitMode = new WeakMap(), _EditorImpl_renderPreview = new WeakMap(), _EditorImpl_wysiwygEditor = new WeakMap(), _EditorImpl_markupEditor = new WeakMap(), _EditorImpl_extraMarkupExtensions = new WeakMap(), _EditorImpl_preset = new WeakMap(), _EditorImpl_allowHTML = new WeakMap(), _EditorImpl_linkify = new WeakMap(), _EditorImpl_linkifyTlds = new WeakMap(), _EditorImpl_extensions = new WeakMap(), _EditorImpl_renderStorage = new WeakMap(), _EditorImpl_fileUploadHandler = new WeakMap(), _EditorImpl_needToSetDimensionsForUploadedImages = new WeakMap(), _EditorImpl_prepareRawMarkup = new WeakMap();
281
+ _EditorImpl_markup = new WeakMap(), _EditorImpl_editorMode = new WeakMap(), _EditorImpl_toolbarVisible = new WeakMap(), _EditorImpl_splitModeEnabled = new WeakMap(), _EditorImpl_splitMode = new WeakMap(), _EditorImpl_renderPreview = new WeakMap(), _EditorImpl_wysiwygEditor = new WeakMap(), _EditorImpl_markupEditor = new WeakMap(), _EditorImpl_markupConfig = new WeakMap(), _EditorImpl_preset = new WeakMap(), _EditorImpl_allowHTML = new WeakMap(), _EditorImpl_linkify = new WeakMap(), _EditorImpl_linkifyTlds = new WeakMap(), _EditorImpl_extensions = new WeakMap(), _EditorImpl_renderStorage = new WeakMap(), _EditorImpl_fileUploadHandler = new WeakMap(), _EditorImpl_needToSetDimensionsForUploadedImages = new WeakMap(), _EditorImpl_prepareRawMarkup = new WeakMap();
@@ -1,4 +1,4 @@
1
- export type { Editor as MarkdownEditorInstance, EditorMode as MarkdownEditorMode, EditorPreset as MarkdownEditorPreset, RenderPreview, SplitMode, } from './Editor';
1
+ export type { Editor as MarkdownEditorInstance, EditorMode as MarkdownEditorMode, EditorPreset as MarkdownEditorPreset, MarkupConfig as MarkdownEditorMarkupConfig, RenderPreview, SplitMode, } from './Editor';
2
2
  export { MarkdownEditorProvider, useMarkdownEditorContext } from './context';
3
3
  export { useMarkdownEditor } from './useMarkdownEditor';
4
4
  export type { UseMarkdownEditorProps } from './useMarkdownEditor';
@@ -106,7 +106,8 @@ function buildDecosSet(doc) {
106
106
  while ((child = parent.maybeChild(++idx))) {
107
107
  const childPos = $pos.posAtIndex(idx, depth);
108
108
  if ((0, utils_1.isHeading)(child)) {
109
- if ((0, utils_1.isFoldingHeading)(child)) {
109
+ const hasFolding = (0, utils_1.isFoldingHeading)(child);
110
+ if (hasFolding) {
110
111
  nextFoldingHeadingFound = true;
111
112
  }
112
113
  const level = (0, utils_1.parseLevel)(child);
@@ -115,12 +116,14 @@ function buildDecosSet(doc) {
115
116
  if (!hidden) {
116
117
  lastNonHiddenChild = { node: child, pos: childPos, level: hLevel };
117
118
  }
118
- if ((0, utils_1.isUnfoldedHeading)(child)) {
119
- hLevel = level;
120
- hidden = false;
121
- }
122
- else {
123
- hidden = true;
119
+ if (hasFolding) {
120
+ if ((0, utils_1.isUnfoldedHeading)(child)) {
121
+ hLevel = level;
122
+ hidden = false;
123
+ }
124
+ else {
125
+ hidden = true;
126
+ }
124
127
  }
125
128
  }
126
129
  if (!hidden) {
@@ -4,6 +4,8 @@ import { EventMap } from '../../bundle/Editor';
4
4
  import { ReactRenderStorage } from '../../extensions';
5
5
  import { Receiver } from '../../utils';
6
6
  import { FileUploadHandler } from './files-upload-facet';
7
+ import { type YfmLangOptions } from './yfm';
8
+ export type { YfmLangOptions };
7
9
  export declare type CreateCodemirrorParams = {
8
10
  doc: EditorViewConfig['doc'];
9
11
  placeholderText: string;
@@ -15,8 +17,9 @@ export declare type CreateCodemirrorParams = {
15
17
  reactRenderer: ReactRenderStorage;
16
18
  uploadHandler?: FileUploadHandler;
17
19
  needImgDimms?: boolean;
18
- extraMarkupExtensions?: Extension[];
20
+ extensions?: Extension[];
19
21
  receiver?: Receiver<EventMap>;
22
+ yfmLangOptions?: YfmLangOptions;
20
23
  };
21
24
  export declare function createCodemirror(params: CreateCodemirrorParams): EditorView;
22
25
  export declare function withLogger(action: string, command: StateCommand): StateCommand;
@@ -16,7 +16,7 @@ const react_facet_1 = require("./react-facet");
16
16
  const plugin_1 = require("./search-plugin/plugin");
17
17
  const yfm_1 = require("./yfm");
18
18
  function createCodemirror(params) {
19
- const { doc, placeholderText, reactRenderer, onCancel, onScroll, onSubmit, onChange, onDocChange, extraMarkupExtensions, receiver, } = params;
19
+ const { doc, placeholderText, reactRenderer, onCancel, onScroll, onSubmit, onChange, onDocChange, extensions: extraExtensions, receiver, yfmLangOptions, } = params;
20
20
  const extensions = [
21
21
  gravity_1.gravityTheme,
22
22
  (0, view_1.placeholder)(placeholderText),
@@ -60,7 +60,7 @@ function createCodemirror(params) {
60
60
  ...commands_1.historyKeymap,
61
61
  ]),
62
62
  (0, autocomplete_1.autocompletion)(),
63
- (0, yfm_1.yfmLang)(),
63
+ (0, yfm_1.yfmLang)(yfmLangOptions),
64
64
  react_facet_1.ReactRendererFacet.of(reactRenderer),
65
65
  pairing_chars_1.PairingCharactersExtension,
66
66
  view_1.EditorView.lineWrapping,
@@ -81,8 +81,8 @@ function createCodemirror(params) {
81
81
  imgWithDimms: params.needImgDimms,
82
82
  }));
83
83
  }
84
- if (extraMarkupExtensions) {
85
- extensions.push(...extraMarkupExtensions);
84
+ if (extraExtensions) {
85
+ extensions.push(...extraExtensions);
86
86
  }
87
87
  return new view_1.EditorView({
88
88
  doc,
@@ -1,4 +1,4 @@
1
- import { Completion, snippet } from '@codemirror/autocomplete';
1
+ import { Completion, CompletionSource, snippet } from '@codemirror/autocomplete';
2
2
  import type { Extension } from '@codemirror/state';
3
3
  import { Tag } from '@lezer/highlight';
4
4
  export declare const customTags: {
@@ -15,4 +15,11 @@ export declare const yfmCutSnippet: (editor: {
15
15
  state: import("@codemirror/state").EditorState;
16
16
  dispatch: (tr: import("@codemirror/state").Transaction) => void;
17
17
  }, completion: Completion | null, from: number, to: number) => void;
18
- export declare function yfmLang(): Extension;
18
+ export interface LanguageData {
19
+ autocomplete: CompletionSource;
20
+ [key: string]: any;
21
+ }
22
+ export interface YfmLangOptions {
23
+ languageData?: LanguageData[];
24
+ }
25
+ export declare function yfmLang({ languageData }?: YfmLangOptions): Extension;
@@ -58,7 +58,56 @@ exports.yfmNoteSnippets = {
58
58
  };
59
59
  exports.yfmCutSnippetTemplate = '{% cut "#{title}" %}\n\n#{}\n\n{% endcut %}\n\n';
60
60
  exports.yfmCutSnippet = (0, autocomplete_1.snippet)(exports.yfmCutSnippetTemplate);
61
- function yfmLang() {
61
+ const mdAutocomplete = {
62
+ autocomplete: (context) => {
63
+ // TODO: add more actions and re-enable
64
+ // let word = context.matchBefore(/\/.*/);
65
+ // if (word) {
66
+ // return {
67
+ // from: word.from,
68
+ // options: [
69
+ // ...yfmNoteTypes.map<Completion>((type, index) => ({
70
+ // label: `/yfm note ${type}`,
71
+ // displayLabel: `YFM Note ${capitalize(type)}`,
72
+ // type: 'text',
73
+ // apply: yfmNoteSnippets[type],
74
+ // boost: -index,
75
+ // })),
76
+ // {
77
+ // label: '/yfm cut',
78
+ // displayLabel: 'YFM Cut',
79
+ // type: 'text',
80
+ // apply: yfmCutSnippet,
81
+ // },
82
+ // ],
83
+ // };
84
+ // }
85
+ const word = context.matchBefore(/^.*/);
86
+ if (word === null || word === void 0 ? void 0 : word.text.startsWith('{%')) {
87
+ return {
88
+ from: word.from,
89
+ options: [
90
+ ...exports.yfmNoteTypes.map((type, index) => ({
91
+ label: `{% note ${type}`,
92
+ displayLabel: (0, lodash_1.capitalize)(type),
93
+ type: 'text',
94
+ section: 'YFM Note',
95
+ apply: exports.yfmNoteSnippets[type],
96
+ boost: -index,
97
+ })),
98
+ {
99
+ label: '{% cut',
100
+ displayLabel: 'YFM Cut',
101
+ type: 'text',
102
+ apply: exports.yfmCutSnippet,
103
+ },
104
+ ],
105
+ };
106
+ }
107
+ return null;
108
+ },
109
+ };
110
+ function yfmLang({ languageData = [] } = {}) {
62
111
  const mdSupport = (0, lang_markdown_1.markdown)({
63
112
  // defaultCodeLanguage: markdownLanguage,
64
113
  base: lang_markdown_1.markdownLanguage,
@@ -66,55 +115,10 @@ function yfmLang() {
66
115
  completeHTMLTags: false,
67
116
  extensions: [UnderlineExtension, MonospaceExtension, MarkedExtension],
68
117
  });
69
- const mdAutocomplete = {
70
- autocomplete: (context) => {
71
- // TODO: add more actions and re-enable
72
- // let word = context.matchBefore(/\/.*/);
73
- // if (word) {
74
- // return {
75
- // from: word.from,
76
- // options: [
77
- // ...yfmNoteTypes.map<Completion>((type, index) => ({
78
- // label: `/yfm note ${type}`,
79
- // displayLabel: `YFM Note ${capitalize(type)}`,
80
- // type: 'text',
81
- // apply: yfmNoteSnippets[type],
82
- // boost: -index,
83
- // })),
84
- // {
85
- // label: '/yfm cut',
86
- // displayLabel: 'YFM Cut',
87
- // type: 'text',
88
- // apply: yfmCutSnippet,
89
- // },
90
- // ],
91
- // };
92
- // }
93
- const word = context.matchBefore(/^.*/);
94
- if (word === null || word === void 0 ? void 0 : word.text.startsWith('{%')) {
95
- return {
96
- from: word.from,
97
- options: [
98
- ...exports.yfmNoteTypes.map((type, index) => ({
99
- label: `{% note ${type}`,
100
- displayLabel: (0, lodash_1.capitalize)(type),
101
- type: 'text',
102
- section: 'YFM Note',
103
- apply: exports.yfmNoteSnippets[type],
104
- boost: -index,
105
- })),
106
- {
107
- label: '{% cut',
108
- displayLabel: 'YFM Cut',
109
- type: 'text',
110
- apply: exports.yfmCutSnippet,
111
- },
112
- ],
113
- };
114
- }
115
- return null;
116
- },
117
- };
118
- return [mdSupport, mdSupport.language.data.of(mdAutocomplete)];
118
+ return [
119
+ mdSupport,
120
+ mdSupport.language.data.of(mdAutocomplete),
121
+ languageData.map((item) => mdSupport.language.data.of(item)),
122
+ ];
119
123
  }
120
124
  exports.yfmLang = yfmLang;
@@ -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.6.1' !== 'undefined' ? '13.6.1' : 'unknown';
5
+ exports.VERSION = typeof '13.8.0' !== 'undefined' ? '13.8.0' : 'unknown';
@@ -1,2 +1,2 @@
1
- import { RefObject } from 'react';
1
+ import { type RefObject } from 'react';
2
2
  export declare function useYfmShowElemWithId(ref: RefObject<HTMLElement>, id: string): void;
@@ -13,6 +13,10 @@ const YfmTabsCN = {
13
13
  Tab: 'yfm-tab',
14
14
  TabPanel: 'yfm-tab-panel',
15
15
  };
16
+ const FoldingHeadingsCN = {
17
+ Open: 'open',
18
+ Section: 'heading-section',
19
+ };
16
20
  function useYfmShowElemWithId(ref, id) {
17
21
  (0, react_1.useEffect)(() => {
18
22
  const { current: containerDom } = ref;
@@ -23,7 +27,7 @@ function useYfmShowElemWithId(ref, id) {
23
27
  return;
24
28
  while (elem && elem !== containerDom) {
25
29
  // eslint-disable-next-line @typescript-eslint/no-unused-expressions
26
- openYfmCut(elem) || switchYfmTabs(elem);
30
+ openYfmCut(elem) || openFoldingHeadings(elem, id) || switchYfmTabs(elem);
27
31
  elem = elem.parentElement;
28
32
  }
29
33
  }, [id]);
@@ -66,3 +70,13 @@ function switchYfmTabs(tabPanelElem) {
66
70
  }
67
71
  return true;
68
72
  }
73
+ function openFoldingHeadings(elem, id) {
74
+ var _a;
75
+ if (elem.classList.contains(FoldingHeadingsCN.Section) &&
76
+ !elem.classList.contains(FoldingHeadingsCN.Open) &&
77
+ id !== ((_a = elem.firstElementChild) === null || _a === void 0 ? void 0 : _a.id)) {
78
+ elem.classList.add(FoldingHeadingsCN.Open);
79
+ return true;
80
+ }
81
+ return false;
82
+ }
@@ -3,6 +3,8 @@ import type { Extension as CodemirrorExtension } from '@codemirror/state';
3
3
  import { CommonEditor, MarkupString } from '../common';
4
4
  import { WysiwygEditorOptions } from '../core';
5
5
  import { ReactRenderStorage } from '../extensions';
6
+ import { type CreateCodemirrorParams } from '../markup/codemirror';
7
+ import type { YfmLangOptions } from '../markup/codemirror/yfm';
6
8
  import { Receiver } from '../utils/event-emitter';
7
9
  import type { FileUploadHandler } from '../utils/upload';
8
10
  export declare type EditorMode = 'wysiwyg' | 'markup';
@@ -42,6 +44,16 @@ export interface Editor extends Receiver<EventMap>, CommonEditor {
42
44
  line: number;
43
45
  }): void;
44
46
  }
47
+ export declare type MarkupConfig = {
48
+ /** Additional extensions for codemirror instance. */
49
+ extensions?: CreateCodemirrorParams['extensions'];
50
+ /**
51
+ * Additional language data for markdown language in codemirror.
52
+ * Can be used to configure additional autocompletions and others.
53
+ * See more https://codemirror.net/docs/ref/#state.EditorState.languageDataAt
54
+ */
55
+ languageData?: YfmLangOptions['languageData'];
56
+ };
45
57
  export declare type EditorOptions = Pick<WysiwygEditorOptions, 'allowHTML' | 'linkify' | 'linkifyTlds' | 'extensions'> & {
46
58
  initialMarkup?: MarkupString;
47
59
  /** @default 'wysiwyg' */
@@ -68,5 +80,7 @@ export declare type EditorOptions = Pick<WysiwygEditorOptions, 'allowHTML' | 'li
68
80
  splitMode?: SplitMode;
69
81
  renderPreview?: RenderPreview;
70
82
  preset: EditorPreset;
83
+ /** @deprecated Put extra extensions via MarkdownEditorMarkupConfig */
71
84
  extraMarkupExtensions?: CodemirrorExtension[];
85
+ markupConfig?: MarkupConfig;
72
86
  };
@@ -1,4 +1,4 @@
1
- var _EditorImpl_markup, _EditorImpl_editorMode, _EditorImpl_toolbarVisible, _EditorImpl_splitModeEnabled, _EditorImpl_splitMode, _EditorImpl_renderPreview, _EditorImpl_wysiwygEditor, _EditorImpl_markupEditor, _EditorImpl_extraMarkupExtensions, _EditorImpl_preset, _EditorImpl_allowHTML, _EditorImpl_linkify, _EditorImpl_linkifyTlds, _EditorImpl_extensions, _EditorImpl_renderStorage, _EditorImpl_fileUploadHandler, _EditorImpl_needToSetDimensionsForUploadedImages, _EditorImpl_prepareRawMarkup;
1
+ var _EditorImpl_markup, _EditorImpl_editorMode, _EditorImpl_toolbarVisible, _EditorImpl_splitModeEnabled, _EditorImpl_splitMode, _EditorImpl_renderPreview, _EditorImpl_wysiwygEditor, _EditorImpl_markupEditor, _EditorImpl_markupConfig, _EditorImpl_preset, _EditorImpl_allowHTML, _EditorImpl_linkify, _EditorImpl_linkifyTlds, _EditorImpl_extensions, _EditorImpl_renderStorage, _EditorImpl_fileUploadHandler, _EditorImpl_needToSetDimensionsForUploadedImages, _EditorImpl_prepareRawMarkup;
2
2
  import { __classPrivateFieldGet, __classPrivateFieldSet } from "tslib";
3
3
  import { TextSelection } from 'prosemirror-state';
4
4
  import { WysiwygEditor } from '../core';
@@ -10,7 +10,8 @@ import { SafeEventEmitter } from '../utils/event-emitter';
10
10
  /** @internal */
11
11
  export class EditorImpl extends SafeEventEmitter {
12
12
  constructor(opts) {
13
- var _a, _b, _c, _d, _e;
13
+ var _a, _b, _c, _d, _e, _f;
14
+ var _g;
14
15
  super({ onError: logger.error.bind(logger) });
15
16
  _EditorImpl_markup.set(this, void 0);
16
17
  _EditorImpl_editorMode.set(this, void 0);
@@ -20,7 +21,7 @@ export class EditorImpl extends SafeEventEmitter {
20
21
  _EditorImpl_renderPreview.set(this, void 0);
21
22
  _EditorImpl_wysiwygEditor.set(this, void 0);
22
23
  _EditorImpl_markupEditor.set(this, void 0);
23
- _EditorImpl_extraMarkupExtensions.set(this, void 0);
24
+ _EditorImpl_markupConfig.set(this, void 0);
24
25
  _EditorImpl_preset.set(this, void 0);
25
26
  _EditorImpl_allowHTML.set(this, void 0);
26
27
  _EditorImpl_linkify.set(this, void 0);
@@ -42,7 +43,8 @@ export class EditorImpl extends SafeEventEmitter {
42
43
  __classPrivateFieldSet(this, _EditorImpl_linkifyTlds, opts.linkifyTlds, "f");
43
44
  __classPrivateFieldSet(this, _EditorImpl_allowHTML, opts.allowHTML, "f");
44
45
  __classPrivateFieldSet(this, _EditorImpl_extensions, opts.extensions, "f");
45
- __classPrivateFieldSet(this, _EditorImpl_extraMarkupExtensions, opts.extraMarkupExtensions, "f");
46
+ __classPrivateFieldSet(this, _EditorImpl_markupConfig, Object.assign({}, opts.markupConfig), "f");
47
+ (_f = (_g = __classPrivateFieldGet(this, _EditorImpl_markupConfig, "f")).extensions) !== null && _f !== void 0 ? _f : (_g.extensions = opts.extraMarkupExtensions);
46
48
  __classPrivateFieldSet(this, _EditorImpl_renderStorage, opts.renderStorage, "f");
47
49
  __classPrivateFieldSet(this, _EditorImpl_fileUploadHandler, opts.fileUploadHandler, "f");
48
50
  __classPrivateFieldSet(this, _EditorImpl_needToSetDimensionsForUploadedImages, Boolean(opts.needToSetDimensionsForUploadedImages), "f");
@@ -151,7 +153,8 @@ export class EditorImpl extends SafeEventEmitter {
151
153
  reactRenderer: __classPrivateFieldGet(this, _EditorImpl_renderStorage, "f"),
152
154
  uploadHandler: this.fileUploadHandler,
153
155
  needImgDimms: this.needToSetDimensionsForUploadedImages,
154
- extraMarkupExtensions: __classPrivateFieldGet(this, _EditorImpl_extraMarkupExtensions, "f"),
156
+ extensions: __classPrivateFieldGet(this, _EditorImpl_markupConfig, "f").extensions,
157
+ yfmLangOptions: { languageData: __classPrivateFieldGet(this, _EditorImpl_markupConfig, "f").languageData },
155
158
  receiver: this,
156
159
  })), "f");
157
160
  }
@@ -271,4 +274,4 @@ export class EditorImpl extends SafeEventEmitter {
271
274
  return (serializedEditorMarkup === null || serializedEditorMarkup === void 0 ? void 0 : serializedEditorMarkup.trim()) !== wysiwygValue.trim();
272
275
  }
273
276
  }
274
- _EditorImpl_markup = new WeakMap(), _EditorImpl_editorMode = new WeakMap(), _EditorImpl_toolbarVisible = new WeakMap(), _EditorImpl_splitModeEnabled = new WeakMap(), _EditorImpl_splitMode = new WeakMap(), _EditorImpl_renderPreview = new WeakMap(), _EditorImpl_wysiwygEditor = new WeakMap(), _EditorImpl_markupEditor = new WeakMap(), _EditorImpl_extraMarkupExtensions = new WeakMap(), _EditorImpl_preset = new WeakMap(), _EditorImpl_allowHTML = new WeakMap(), _EditorImpl_linkify = new WeakMap(), _EditorImpl_linkifyTlds = new WeakMap(), _EditorImpl_extensions = new WeakMap(), _EditorImpl_renderStorage = new WeakMap(), _EditorImpl_fileUploadHandler = new WeakMap(), _EditorImpl_needToSetDimensionsForUploadedImages = new WeakMap(), _EditorImpl_prepareRawMarkup = new WeakMap();
277
+ _EditorImpl_markup = new WeakMap(), _EditorImpl_editorMode = new WeakMap(), _EditorImpl_toolbarVisible = new WeakMap(), _EditorImpl_splitModeEnabled = new WeakMap(), _EditorImpl_splitMode = new WeakMap(), _EditorImpl_renderPreview = new WeakMap(), _EditorImpl_wysiwygEditor = new WeakMap(), _EditorImpl_markupEditor = new WeakMap(), _EditorImpl_markupConfig = new WeakMap(), _EditorImpl_preset = new WeakMap(), _EditorImpl_allowHTML = new WeakMap(), _EditorImpl_linkify = new WeakMap(), _EditorImpl_linkifyTlds = new WeakMap(), _EditorImpl_extensions = new WeakMap(), _EditorImpl_renderStorage = new WeakMap(), _EditorImpl_fileUploadHandler = new WeakMap(), _EditorImpl_needToSetDimensionsForUploadedImages = new WeakMap(), _EditorImpl_prepareRawMarkup = new WeakMap();
@@ -1,4 +1,4 @@
1
- export type { Editor as MarkdownEditorInstance, EditorMode as MarkdownEditorMode, EditorPreset as MarkdownEditorPreset, RenderPreview, SplitMode, } from './Editor';
1
+ export type { Editor as MarkdownEditorInstance, EditorMode as MarkdownEditorMode, EditorPreset as MarkdownEditorPreset, MarkupConfig as MarkdownEditorMarkupConfig, RenderPreview, SplitMode, } from './Editor';
2
2
  export { MarkdownEditorProvider, useMarkdownEditorContext } from './context';
3
3
  export { useMarkdownEditor } from './useMarkdownEditor';
4
4
  export type { UseMarkdownEditorProps } from './useMarkdownEditor';
@@ -103,7 +103,8 @@ function buildDecosSet(doc) {
103
103
  while ((child = parent.maybeChild(++idx))) {
104
104
  const childPos = $pos.posAtIndex(idx, depth);
105
105
  if (isHeading(child)) {
106
- if (isFoldingHeading(child)) {
106
+ const hasFolding = isFoldingHeading(child);
107
+ if (hasFolding) {
107
108
  nextFoldingHeadingFound = true;
108
109
  }
109
110
  const level = parseLevel(child);
@@ -112,12 +113,14 @@ function buildDecosSet(doc) {
112
113
  if (!hidden) {
113
114
  lastNonHiddenChild = { node: child, pos: childPos, level: hLevel };
114
115
  }
115
- if (isUnfoldedHeading(child)) {
116
- hLevel = level;
117
- hidden = false;
118
- }
119
- else {
120
- hidden = true;
116
+ if (hasFolding) {
117
+ if (isUnfoldedHeading(child)) {
118
+ hLevel = level;
119
+ hidden = false;
120
+ }
121
+ else {
122
+ hidden = true;
123
+ }
121
124
  }
122
125
  }
123
126
  if (!hidden) {
@@ -4,6 +4,8 @@ import { EventMap } from '../../bundle/Editor';
4
4
  import { ReactRenderStorage } from '../../extensions';
5
5
  import { Receiver } from '../../utils';
6
6
  import { FileUploadHandler } from './files-upload-facet';
7
+ import { type YfmLangOptions } from './yfm';
8
+ export type { YfmLangOptions };
7
9
  export declare type CreateCodemirrorParams = {
8
10
  doc: EditorViewConfig['doc'];
9
11
  placeholderText: string;
@@ -15,8 +17,9 @@ export declare type CreateCodemirrorParams = {
15
17
  reactRenderer: ReactRenderStorage;
16
18
  uploadHandler?: FileUploadHandler;
17
19
  needImgDimms?: boolean;
18
- extraMarkupExtensions?: Extension[];
20
+ extensions?: Extension[];
19
21
  receiver?: Receiver<EventMap>;
22
+ yfmLangOptions?: YfmLangOptions;
20
23
  };
21
24
  export declare function createCodemirror(params: CreateCodemirrorParams): EditorView;
22
25
  export declare function withLogger(action: string, command: StateCommand): StateCommand;
@@ -13,7 +13,7 @@ import { ReactRendererFacet } from './react-facet';
13
13
  import { SearchPanelPlugin } from './search-plugin/plugin';
14
14
  import { yfmLang } from './yfm';
15
15
  export function createCodemirror(params) {
16
- const { doc, placeholderText, reactRenderer, onCancel, onScroll, onSubmit, onChange, onDocChange, extraMarkupExtensions, receiver, } = params;
16
+ const { doc, placeholderText, reactRenderer, onCancel, onScroll, onSubmit, onChange, onDocChange, extensions: extraExtensions, receiver, yfmLangOptions, } = params;
17
17
  const extensions = [
18
18
  gravityTheme,
19
19
  placeholder(placeholderText),
@@ -57,7 +57,7 @@ export function createCodemirror(params) {
57
57
  ...historyKeymap,
58
58
  ]),
59
59
  autocompletion(),
60
- yfmLang(),
60
+ yfmLang(yfmLangOptions),
61
61
  ReactRendererFacet.of(reactRenderer),
62
62
  PairingCharactersExtension,
63
63
  EditorView.lineWrapping,
@@ -78,8 +78,8 @@ export function createCodemirror(params) {
78
78
  imgWithDimms: params.needImgDimms,
79
79
  }));
80
80
  }
81
- if (extraMarkupExtensions) {
82
- extensions.push(...extraMarkupExtensions);
81
+ if (extraExtensions) {
82
+ extensions.push(...extraExtensions);
83
83
  }
84
84
  return new EditorView({
85
85
  doc,
@@ -1,4 +1,4 @@
1
- import { Completion, snippet } from '@codemirror/autocomplete';
1
+ import { Completion, CompletionSource, snippet } from '@codemirror/autocomplete';
2
2
  import type { Extension } from '@codemirror/state';
3
3
  import { Tag } from '@lezer/highlight';
4
4
  export declare const customTags: {
@@ -15,4 +15,11 @@ export declare const yfmCutSnippet: (editor: {
15
15
  state: import("@codemirror/state").EditorState;
16
16
  dispatch: (tr: import("@codemirror/state").Transaction) => void;
17
17
  }, completion: Completion | null, from: number, to: number) => void;
18
- export declare function yfmLang(): Extension;
18
+ export interface LanguageData {
19
+ autocomplete: CompletionSource;
20
+ [key: string]: any;
21
+ }
22
+ export interface YfmLangOptions {
23
+ languageData?: LanguageData[];
24
+ }
25
+ export declare function yfmLang({ languageData }?: YfmLangOptions): Extension;
@@ -54,7 +54,56 @@ export const yfmNoteSnippets = {
54
54
  };
55
55
  export const yfmCutSnippetTemplate = '{% cut "#{title}" %}\n\n#{}\n\n{% endcut %}\n\n';
56
56
  export const yfmCutSnippet = snippet(yfmCutSnippetTemplate);
57
- export function yfmLang() {
57
+ const mdAutocomplete = {
58
+ autocomplete: (context) => {
59
+ // TODO: add more actions and re-enable
60
+ // let word = context.matchBefore(/\/.*/);
61
+ // if (word) {
62
+ // return {
63
+ // from: word.from,
64
+ // options: [
65
+ // ...yfmNoteTypes.map<Completion>((type, index) => ({
66
+ // label: `/yfm note ${type}`,
67
+ // displayLabel: `YFM Note ${capitalize(type)}`,
68
+ // type: 'text',
69
+ // apply: yfmNoteSnippets[type],
70
+ // boost: -index,
71
+ // })),
72
+ // {
73
+ // label: '/yfm cut',
74
+ // displayLabel: 'YFM Cut',
75
+ // type: 'text',
76
+ // apply: yfmCutSnippet,
77
+ // },
78
+ // ],
79
+ // };
80
+ // }
81
+ const word = context.matchBefore(/^.*/);
82
+ if (word === null || word === void 0 ? void 0 : word.text.startsWith('{%')) {
83
+ return {
84
+ from: word.from,
85
+ options: [
86
+ ...yfmNoteTypes.map((type, index) => ({
87
+ label: `{% note ${type}`,
88
+ displayLabel: capitalize(type),
89
+ type: 'text',
90
+ section: 'YFM Note',
91
+ apply: yfmNoteSnippets[type],
92
+ boost: -index,
93
+ })),
94
+ {
95
+ label: '{% cut',
96
+ displayLabel: 'YFM Cut',
97
+ type: 'text',
98
+ apply: yfmCutSnippet,
99
+ },
100
+ ],
101
+ };
102
+ }
103
+ return null;
104
+ },
105
+ };
106
+ export function yfmLang({ languageData = [] } = {}) {
58
107
  const mdSupport = markdown({
59
108
  // defaultCodeLanguage: markdownLanguage,
60
109
  base: markdownLanguage,
@@ -62,54 +111,9 @@ export function yfmLang() {
62
111
  completeHTMLTags: false,
63
112
  extensions: [UnderlineExtension, MonospaceExtension, MarkedExtension],
64
113
  });
65
- const mdAutocomplete = {
66
- autocomplete: (context) => {
67
- // TODO: add more actions and re-enable
68
- // let word = context.matchBefore(/\/.*/);
69
- // if (word) {
70
- // return {
71
- // from: word.from,
72
- // options: [
73
- // ...yfmNoteTypes.map<Completion>((type, index) => ({
74
- // label: `/yfm note ${type}`,
75
- // displayLabel: `YFM Note ${capitalize(type)}`,
76
- // type: 'text',
77
- // apply: yfmNoteSnippets[type],
78
- // boost: -index,
79
- // })),
80
- // {
81
- // label: '/yfm cut',
82
- // displayLabel: 'YFM Cut',
83
- // type: 'text',
84
- // apply: yfmCutSnippet,
85
- // },
86
- // ],
87
- // };
88
- // }
89
- const word = context.matchBefore(/^.*/);
90
- if (word === null || word === void 0 ? void 0 : word.text.startsWith('{%')) {
91
- return {
92
- from: word.from,
93
- options: [
94
- ...yfmNoteTypes.map((type, index) => ({
95
- label: `{% note ${type}`,
96
- displayLabel: capitalize(type),
97
- type: 'text',
98
- section: 'YFM Note',
99
- apply: yfmNoteSnippets[type],
100
- boost: -index,
101
- })),
102
- {
103
- label: '{% cut',
104
- displayLabel: 'YFM Cut',
105
- type: 'text',
106
- apply: yfmCutSnippet,
107
- },
108
- ],
109
- };
110
- }
111
- return null;
112
- },
113
- };
114
- return [mdSupport, mdSupport.language.data.of(mdAutocomplete)];
114
+ return [
115
+ mdSupport,
116
+ mdSupport.language.data.of(mdAutocomplete),
117
+ languageData.map((item) => mdSupport.language.data.of(item)),
118
+ ];
115
119
  }
@@ -1,2 +1,2 @@
1
1
  /** During build process, the current version will be injected here */
2
- export const VERSION = typeof '13.6.1' !== 'undefined' ? '13.6.1' : 'unknown';
2
+ export const VERSION = typeof '13.8.0' !== 'undefined' ? '13.8.0' : 'unknown';
@@ -1,2 +1,2 @@
1
- import { RefObject } from 'react';
1
+ import { type RefObject } from 'react';
2
2
  export declare function useYfmShowElemWithId(ref: RefObject<HTMLElement>, id: string): void;
@@ -10,6 +10,10 @@ const YfmTabsCN = {
10
10
  Tab: 'yfm-tab',
11
11
  TabPanel: 'yfm-tab-panel',
12
12
  };
13
+ const FoldingHeadingsCN = {
14
+ Open: 'open',
15
+ Section: 'heading-section',
16
+ };
13
17
  export function useYfmShowElemWithId(ref, id) {
14
18
  useEffect(() => {
15
19
  const { current: containerDom } = ref;
@@ -20,7 +24,7 @@ export function useYfmShowElemWithId(ref, id) {
20
24
  return;
21
25
  while (elem && elem !== containerDom) {
22
26
  // eslint-disable-next-line @typescript-eslint/no-unused-expressions
23
- openYfmCut(elem) || switchYfmTabs(elem);
27
+ openYfmCut(elem) || openFoldingHeadings(elem, id) || switchYfmTabs(elem);
24
28
  elem = elem.parentElement;
25
29
  }
26
30
  }, [id]);
@@ -62,3 +66,13 @@ function switchYfmTabs(tabPanelElem) {
62
66
  }
63
67
  return true;
64
68
  }
69
+ function openFoldingHeadings(elem, id) {
70
+ var _a;
71
+ if (elem.classList.contains(FoldingHeadingsCN.Section) &&
72
+ !elem.classList.contains(FoldingHeadingsCN.Open) &&
73
+ id !== ((_a = elem.firstElementChild) === null || _a === void 0 ? void 0 : _a.id)) {
74
+ elem.classList.add(FoldingHeadingsCN.Open);
75
+ return true;
76
+ }
77
+ return false;
78
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gravity-ui/markdown-editor",
3
- "version": "13.6.1",
3
+ "version": "13.8.0",
4
4
  "description": "Markdown wysiwyg and markup editor",
5
5
  "license": "MIT",
6
6
  "repository": {