@gravity-ui/markdown-editor 13.18.2 → 13.20.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.
Files changed (40) hide show
  1. package/README.md +1 -1
  2. package/build/cjs/bundle/Editor.d.ts +2 -0
  3. package/build/cjs/bundle/Editor.js +2 -1
  4. package/build/cjs/core/markdown/MarkdownSerializer.js +7 -3
  5. package/build/cjs/extensions/yfm/YfmTable/YfmTableSpecs/serializer.js +17 -4
  6. package/build/cjs/extensions/yfm/YfmTabs/YfmTabsSpecs/const.d.ts +8 -3
  7. package/build/cjs/extensions/yfm/YfmTabs/YfmTabsSpecs/const.js +7 -1
  8. package/build/cjs/extensions/yfm/YfmTabs/YfmTabsSpecs/index.d.ts +2 -0
  9. package/build/cjs/extensions/yfm/YfmTabs/YfmTabsSpecs/index.js +37 -1
  10. package/build/cjs/extensions/yfm/YfmTabs/YfmTabsSpecs/md-plugin.d.ts +2 -0
  11. package/build/cjs/extensions/yfm/YfmTabs/YfmTabsSpecs/md-plugin.js +79 -0
  12. package/build/cjs/extensions/yfm/YfmTabs/YfmTabsSpecs/parser.js +24 -4
  13. package/build/cjs/extensions/yfm/YfmTabs/YfmTabsSpecs/schema.js +66 -1
  14. package/build/cjs/extensions/yfm/YfmTabs/YfmTabsSpecs/serializer.js +28 -0
  15. package/build/cjs/extensions/yfm/YfmTabs/index.js +2 -0
  16. package/build/cjs/extensions/yfm/YfmTabs/views.d.ts +2 -0
  17. package/build/cjs/extensions/yfm/YfmTabs/views.js +15 -3
  18. package/build/cjs/markup/codemirror/create.d.ts +2 -2
  19. package/build/cjs/markup/codemirror/create.js +2 -2
  20. package/build/cjs/version.js +1 -1
  21. package/build/esm/bundle/Editor.d.ts +2 -0
  22. package/build/esm/bundle/Editor.js +2 -1
  23. package/build/esm/core/markdown/MarkdownSerializer.js +7 -3
  24. package/build/esm/extensions/yfm/YfmTable/YfmTableSpecs/serializer.js +17 -4
  25. package/build/esm/extensions/yfm/YfmTabs/YfmTabsSpecs/const.d.ts +8 -3
  26. package/build/esm/extensions/yfm/YfmTabs/YfmTabsSpecs/const.js +7 -1
  27. package/build/esm/extensions/yfm/YfmTabs/YfmTabsSpecs/index.d.ts +2 -0
  28. package/build/esm/extensions/yfm/YfmTabs/YfmTabsSpecs/index.js +37 -1
  29. package/build/esm/extensions/yfm/YfmTabs/YfmTabsSpecs/md-plugin.d.ts +2 -0
  30. package/build/esm/extensions/yfm/YfmTabs/YfmTabsSpecs/md-plugin.js +75 -0
  31. package/build/esm/extensions/yfm/YfmTabs/YfmTabsSpecs/parser.js +24 -4
  32. package/build/esm/extensions/yfm/YfmTabs/YfmTabsSpecs/schema.js +66 -1
  33. package/build/esm/extensions/yfm/YfmTabs/YfmTabsSpecs/serializer.js +28 -0
  34. package/build/esm/extensions/yfm/YfmTabs/index.js +3 -1
  35. package/build/esm/extensions/yfm/YfmTabs/views.d.ts +2 -0
  36. package/build/esm/extensions/yfm/YfmTabs/views.js +12 -2
  37. package/build/esm/markup/codemirror/create.d.ts +2 -2
  38. package/build/esm/markup/codemirror/create.js +2 -2
  39. package/build/esm/version.js +1 -1
  40. package/package.json +1 -1
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- ![Markdown Editor](https://github.com/user-attachments/assets/40be2902-a683-4167-bd8d-ffd8bff26e69)
1
+ ![Markdown Editor](https://github.com/user-attachments/assets/0b4e5f65-54cf-475f-9c68-557a4e9edb46)
2
2
 
3
3
  # @gravity-ui/markdown-editor · [![npm package](https://img.shields.io/npm/v/@gravity-ui/markdown-editor)](https://www.npmjs.com/package/@gravity-ui/markdown-editor) [![CI](https://img.shields.io/github/actions/workflow/status/gravity-ui/markdown-editor/ci.yml?branch=main&label=CI)](https://github.com/gravity-ui/markdown-editor/actions/workflows/ci.yml?query=branch:main) [![Release](https://img.shields.io/github/actions/workflow/status/gravity-ui/markdown-editor/release.yml?branch=main&label=Release)](https://github.com/gravity-ui/markdown-editor/actions/workflows/release.yml?query=branch:main) [![storybook](https://img.shields.io/badge/Storybook-deployed-ff4685)](https://preview.gravity-ui.com/md-editor/)
4
4
 
@@ -57,6 +57,8 @@ export declare type MarkupConfig = {
57
57
  disabledExtensions?: CreateCodemirrorParams['disabledExtensions'];
58
58
  /** Additional keymaps for codemirror instance */
59
59
  keymaps?: CreateCodemirrorParams['keymaps'];
60
+ /** Overrides the default placeholder content. */
61
+ placeholder?: CreateCodemirrorParams['placeholder'];
60
62
  /**
61
63
  * Additional language data for markdown language in codemirror.
62
64
  * Can be used to configure additional autocompletions and others.
@@ -150,10 +150,11 @@ class EditorImpl extends event_emitter_1.SafeEventEmitter {
150
150
  return tslib_1.__classPrivateFieldGet(this, _EditorImpl_wysiwygEditor, "f");
151
151
  }
152
152
  get markupEditor() {
153
+ var _a;
153
154
  if (!tslib_1.__classPrivateFieldGet(this, _EditorImpl_markupEditor, "f")) {
154
155
  tslib_1.__classPrivateFieldSet(this, _EditorImpl_markupEditor, new editor_1.Editor((0, codemirror_1.createCodemirror)({
155
156
  doc: tslib_1.__classPrivateFieldGet(this, _EditorImpl_markup, "f"),
156
- placeholderText: (0, bundle_1.i18n)('markup_placeholder'),
157
+ placeholder: (_a = tslib_1.__classPrivateFieldGet(this, _EditorImpl_markupConfig, "f").placeholder) !== null && _a !== void 0 ? _a : (0, bundle_1.i18n)('markup_placeholder'),
157
158
  onCancel: () => this.emit('cancel', null),
158
159
  onSubmit: () => this.emit('submit', null),
159
160
  onChange: () => this.emit('rerender-toolbar', null),
@@ -195,15 +195,19 @@ class MarkdownSerializerState {
195
195
  let trailing = '';
196
196
  const progress = (node, _, index) => {
197
197
  let marks = node ? node.marks : [];
198
- // Remove marks from breaks (hard_break or soft_break) that are the last node inside
198
+ // Remove marks from breaks (hard_break or soft_break) that are the edge node inside
199
199
  // that mark to prevent parser edge cases with new lines just
200
- // before closing marks.
200
+ // before closing or after opening marks.
201
201
  if (node && node.type.spec.isBreak) {
202
202
  marks = marks.filter(m => {
203
+ if (index === 0)
204
+ return false;
203
205
  if (index + 1 == parent.childCount)
204
206
  return false;
207
+ const prev = parent.child(index - 1);
205
208
  const next = parent.child(index + 1);
206
- return m.isInSet(next.marks) && (!next.isText || /\S/.test(next.text));
209
+ return ((m.isInSet(prev.marks) && (!prev.isText || /\S/.test(prev.text))) &&
210
+ (m.isInSet(next.marks) && (!next.isText || /\S/.test(next.text))));
207
211
  });
208
212
  }
209
213
  let leading = trailing;
@@ -67,10 +67,23 @@ exports.serializerTokens = {
67
67
  state.ensureNewLine();
68
68
  });
69
69
  },
70
- [const_1.YfmTableNode.Row]: (_state, node) => {
71
- throw new Error(`Should not serialize ${node.type.name} node via serialize-token`);
70
+ [const_1.YfmTableNode.Row]: (state, node) => {
71
+ console.warn(`Should not serialize ${node.type.name} node via serialize-token`);
72
+ state.write('||');
73
+ state.ensureNewLine();
74
+ state.write('\n');
75
+ state.renderContent(node);
76
+ state.write('||');
77
+ state.ensureNewLine();
72
78
  },
73
- [const_1.YfmTableNode.Cell]: (_state, node) => {
74
- throw new Error(`Should not serialize ${node.type.name} node via serialize-token`);
79
+ [const_1.YfmTableNode.Cell]: (state, node, parent) => {
80
+ console.warn(`Should not serialize ${node.type.name} node via serialize-token`);
81
+ state.renderContent(node);
82
+ const isLastCellInRow = parent.lastChild === node;
83
+ if (!isLastCellInRow) {
84
+ state.write('|');
85
+ state.ensureNewLine();
86
+ state.write('\n');
87
+ }
75
88
  },
76
89
  };
@@ -1,8 +1,12 @@
1
1
  export declare enum TabsNode {
2
+ TabPanel = "yfm_tab_panel",
2
3
  Tab = "yfm_tab",
3
4
  TabsList = "yfm_tabs_list",
4
- TabPanel = "yfm_tab_panel",
5
- Tabs = "yfm_tabs"
5
+ Tabs = "yfm_tabs",
6
+ RadioTabs = "yfm_radio_tabs",
7
+ RadioTab = "yfm_radio_tab",
8
+ RadioTabInput = "yfm_radio_tab_input",
9
+ RadioTabLabel = "yfm_radio_tab_label"
6
10
  }
7
11
  export declare enum TabsAttrs {
8
12
  class = "class",
@@ -21,7 +25,8 @@ export declare enum TabAttrs {
21
25
  tabindex = "tabindex",
22
26
  dataDiplodocKey = "data-diplodoc-key",
23
27
  dataDiplodocid = "data-diplodoc-id",
24
- dataDiplodocIsActive = "data-diplodoc-is-active"
28
+ dataDiplodocIsActive = "data-diplodoc-is-active",
29
+ dataDiplodocVerticalTab = "data-diplodoc-vertical-tab"
25
30
  }
26
31
  export declare enum TabPanelAttrs {
27
32
  id = "id",
@@ -3,10 +3,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.TabPanelAttrs = exports.TabAttrs = exports.TabsListAttrs = exports.TabsAttrs = exports.TabsNode = void 0;
4
4
  var TabsNode;
5
5
  (function (TabsNode) {
6
+ // tabs panel is common for tabs and radio tabs
7
+ TabsNode["TabPanel"] = "yfm_tab_panel";
6
8
  TabsNode["Tab"] = "yfm_tab";
7
9
  TabsNode["TabsList"] = "yfm_tabs_list";
8
- TabsNode["TabPanel"] = "yfm_tab_panel";
9
10
  TabsNode["Tabs"] = "yfm_tabs";
11
+ TabsNode["RadioTabs"] = "yfm_radio_tabs";
12
+ TabsNode["RadioTab"] = "yfm_radio_tab";
13
+ TabsNode["RadioTabInput"] = "yfm_radio_tab_input";
14
+ TabsNode["RadioTabLabel"] = "yfm_radio_tab_label";
10
15
  })(TabsNode = exports.TabsNode || (exports.TabsNode = {}));
11
16
  var TabsAttrs;
12
17
  (function (TabsAttrs) {
@@ -29,6 +34,7 @@ var TabAttrs;
29
34
  TabAttrs["dataDiplodocKey"] = "data-diplodoc-key";
30
35
  TabAttrs["dataDiplodocid"] = "data-diplodoc-id";
31
36
  TabAttrs["dataDiplodocIsActive"] = "data-diplodoc-is-active";
37
+ TabAttrs["dataDiplodocVerticalTab"] = "data-diplodoc-vertical-tab";
32
38
  })(TabAttrs = exports.TabAttrs || (exports.TabAttrs = {}));
33
39
  var TabPanelAttrs;
34
40
  (function (TabPanelAttrs) {
@@ -14,5 +14,7 @@ export declare type YfmTabsSpecsOptions = {
14
14
  tabsListView?: ExtensionNodeSpec['view'];
15
15
  tabPanelView?: ExtensionNodeSpec['view'];
16
16
  tabsView?: ExtensionNodeSpec['view'];
17
+ vtabView?: ExtensionNodeSpec['view'];
18
+ vtabInputView?: ExtensionNodeSpec['view'];
17
19
  };
18
20
  export declare const YfmTabsSpecs: ExtensionAuto<YfmTabsSpecsOptions>;
@@ -6,6 +6,7 @@ const log_1 = tslib_1.__importDefault(require("@diplodoc/transform/lib/log"));
6
6
  const tabs_1 = tslib_1.__importDefault(require("@diplodoc/transform/lib/plugins/tabs"));
7
7
  const schema_1 = require("../../../../utils/schema");
8
8
  const const_1 = require("./const");
9
+ const md_plugin_1 = require("./md-plugin");
9
10
  const parser_1 = require("./parser");
10
11
  const schema_2 = require("./schema");
11
12
  const serializer_1 = require("./serializer");
@@ -18,7 +19,7 @@ exports.tabsListType = (0, schema_1.nodeTypeFactory)(const_1.TabsNode.TabsList);
18
19
  const YfmTabsSpecs = (builder, opts) => {
19
20
  const schemaSpecs = (0, schema_2.getSchemaSpecs)(opts);
20
21
  builder
21
- .configureMd((md) => md.use(tabs_1.default, { log: log_1.default }))
22
+ .configureMd((md) => md.use(tabs_1.default, { log: log_1.default }).use(md_plugin_1.tabsPostPlugin))
22
23
  .addNode(const_1.TabsNode.Tab, () => ({
23
24
  spec: schemaSpecs[const_1.TabsNode.Tab],
24
25
  toMd: serializer_1.serializerTokens[const_1.TabsNode.Tab],
@@ -55,5 +56,40 @@ const YfmTabsSpecs = (builder, opts) => {
55
56
  },
56
57
  view: opts.tabsView,
57
58
  }));
59
+ builder
60
+ .addNode(const_1.TabsNode.RadioTabs, () => ({
61
+ spec: schemaSpecs[const_1.TabsNode.RadioTabs],
62
+ toMd: serializer_1.serializerTokens[const_1.TabsNode.RadioTabs],
63
+ fromMd: {
64
+ tokenSpec: parser_1.parserTokens[const_1.TabsNode.RadioTabs],
65
+ tokenName: 'r-tabs',
66
+ },
67
+ }))
68
+ .addNode(const_1.TabsNode.RadioTab, () => ({
69
+ spec: schemaSpecs[const_1.TabsNode.RadioTab],
70
+ toMd: serializer_1.serializerTokens[const_1.TabsNode.RadioTab],
71
+ fromMd: {
72
+ tokenSpec: parser_1.parserTokens[const_1.TabsNode.RadioTab],
73
+ tokenName: 'r-tab',
74
+ },
75
+ view: opts.vtabView,
76
+ }))
77
+ .addNode(const_1.TabsNode.RadioTabInput, () => ({
78
+ spec: schemaSpecs[const_1.TabsNode.RadioTabInput],
79
+ toMd: serializer_1.serializerTokens[const_1.TabsNode.RadioTabInput],
80
+ fromMd: {
81
+ tokenSpec: parser_1.parserTokens[const_1.TabsNode.RadioTabInput],
82
+ tokenName: 'r-tab-input',
83
+ },
84
+ view: opts.vtabInputView,
85
+ }))
86
+ .addNode(const_1.TabsNode.RadioTabLabel, () => ({
87
+ spec: schemaSpecs[const_1.TabsNode.RadioTabLabel],
88
+ toMd: serializer_1.serializerTokens[const_1.TabsNode.RadioTabLabel],
89
+ fromMd: {
90
+ tokenSpec: parser_1.parserTokens[const_1.TabsNode.RadioTabLabel],
91
+ tokenName: 'r-tab-label',
92
+ },
93
+ }));
58
94
  };
59
95
  exports.YfmTabsSpecs = YfmTabsSpecs;
@@ -0,0 +1,2 @@
1
+ import type MarkdownIt from 'markdown-it';
2
+ export declare const tabsPostPlugin: MarkdownIt.PluginSimple;
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.tabsPostPlugin = void 0;
4
+ const prefix = 'r-';
5
+ // prefix is added to token types of radio tabs tokens in order to use other nodes in prosemirror
6
+ const tabsPostPlugin = (md) => {
7
+ md.core.ruler.push('me_tabs_after', (state) => {
8
+ var _a;
9
+ const stack = [];
10
+ const tabCloseIndexes = [];
11
+ for (const token of state.tokens) {
12
+ switch (token.type) {
13
+ case 'tabs_open': {
14
+ const vertical = (_a = token.attrGet('class')) === null || _a === void 0 ? void 0 : _a.includes('yfm-tabs-vertical');
15
+ stack.push({ vertical });
16
+ if (vertical)
17
+ token.type = prefix + token.type;
18
+ break;
19
+ }
20
+ case 'tabs_close': {
21
+ const item = stack.pop();
22
+ if (item === null || item === void 0 ? void 0 : item.vertical)
23
+ token.type = prefix + token.type;
24
+ break;
25
+ }
26
+ case 'tab_open': {
27
+ const item = stack.at(-1);
28
+ if (item === null || item === void 0 ? void 0 : item.vertical) {
29
+ // --> TODO: remove after updating to tabs-extension v^3.4.0
30
+ const isInput = token.tag === 'input';
31
+ if (isInput)
32
+ token.type = 'tab-input';
33
+ // <--
34
+ token.type = prefix + token.type;
35
+ }
36
+ break;
37
+ }
38
+ case 'tab_close': {
39
+ const item = stack.at(-1);
40
+ if (item === null || item === void 0 ? void 0 : item.vertical) {
41
+ token.type = prefix + token.type;
42
+ // --> TODO: remove after updating to tabs-extension v^3.4.0
43
+ tabCloseIndexes.push(state.tokens.indexOf(token));
44
+ // <--
45
+ }
46
+ break;
47
+ }
48
+ case 'tab-input':
49
+ case 'tab-label_open':
50
+ case 'tab-label_close': {
51
+ const item = stack.at(-1);
52
+ if (item === null || item === void 0 ? void 0 : item.vertical) {
53
+ token.type = prefix + token.type;
54
+ }
55
+ break;
56
+ }
57
+ // --> TODO: remove after updating to tabs-extension v^3.4.0
58
+ case 'label_open': {
59
+ const tokenIndex = state.tokens.indexOf(token);
60
+ const prevToken = tokenIndex > 0 && state.tokens.at(tokenIndex - 1);
61
+ if (prevToken && prevToken.type === 'r-tab-input') {
62
+ token.type = prefix + 'tab-' + token.type;
63
+ }
64
+ break;
65
+ }
66
+ // <--
67
+ }
68
+ }
69
+ // --> TODO: remove after updating to tabs-extension v^3.4.0
70
+ for (const index of tabCloseIndexes.reverse()) {
71
+ if (index === -1)
72
+ continue;
73
+ const labelCloseToken = new state.Token(prefix + 'tab-label_close', 'label', -1);
74
+ state.tokens.splice(index, 0, labelCloseToken);
75
+ }
76
+ // <--
77
+ });
78
+ };
79
+ exports.tabsPostPlugin = tabsPostPlugin;
@@ -4,13 +4,13 @@ exports.parserTokens = void 0;
4
4
  const const_1 = require("./const");
5
5
  const attrsFromEntries = (token) => (token.attrs ? Object.fromEntries(token.attrs) : {});
6
6
  exports.parserTokens = {
7
- [const_1.TabsNode.Tab]: {
8
- name: const_1.TabsNode.Tab,
7
+ [const_1.TabsNode.TabPanel]: {
8
+ name: const_1.TabsNode.TabPanel,
9
9
  type: 'block',
10
10
  getAttrs: attrsFromEntries,
11
11
  },
12
- [const_1.TabsNode.TabPanel]: {
13
- name: const_1.TabsNode.TabPanel,
12
+ [const_1.TabsNode.Tab]: {
13
+ name: const_1.TabsNode.Tab,
14
14
  type: 'block',
15
15
  getAttrs: attrsFromEntries,
16
16
  },
@@ -24,4 +24,24 @@ exports.parserTokens = {
24
24
  type: 'block',
25
25
  getAttrs: attrsFromEntries,
26
26
  },
27
+ [const_1.TabsNode.RadioTabs]: {
28
+ name: const_1.TabsNode.RadioTabs,
29
+ type: 'block',
30
+ getAttrs: attrsFromEntries,
31
+ },
32
+ [const_1.TabsNode.RadioTab]: {
33
+ name: const_1.TabsNode.RadioTab,
34
+ type: 'block',
35
+ getAttrs: attrsFromEntries,
36
+ },
37
+ [const_1.TabsNode.RadioTabInput]: {
38
+ name: const_1.TabsNode.RadioTabInput,
39
+ type: 'node',
40
+ getAttrs: attrsFromEntries,
41
+ },
42
+ [const_1.TabsNode.RadioTabLabel]: {
43
+ name: const_1.TabsNode.RadioTabLabel,
44
+ type: 'block',
45
+ getAttrs: attrsFromEntries,
46
+ },
27
47
  };
@@ -4,9 +4,10 @@ exports.getSchemaSpecs = void 0;
4
4
  const const_1 = require("./const");
5
5
  const DEFAULT_PLACEHOLDERS = {
6
6
  TabTitle: 'Tab title',
7
+ RadioTabLabelTitle: 'Radio title',
7
8
  };
8
9
  const getSchemaSpecs = (opts, placeholder) => {
9
- var _a, _b;
10
+ var _a, _b, _c;
10
11
  return ({
11
12
  [const_1.TabsNode.Tab]: {
12
13
  attrs: {
@@ -82,6 +83,70 @@ const getSchemaSpecs = (opts, placeholder) => {
82
83
  allowSelection: false,
83
84
  complex: 'inner',
84
85
  },
86
+ [const_1.TabsNode.RadioTabs]: {
87
+ attrs: {
88
+ [const_1.TabsAttrs.class]: { default: 'yfm-tabs yfm-tabs-vertical' },
89
+ [const_1.TabsAttrs.dataDiplodocGroup]: { default: 'unknown' },
90
+ },
91
+ content: `(${const_1.TabsNode.RadioTab} ${const_1.TabsNode.TabPanel})+`,
92
+ group: 'block',
93
+ toDOM(node) {
94
+ return ['div', node.attrs, 0];
95
+ },
96
+ complex: 'root',
97
+ },
98
+ [const_1.TabsNode.RadioTab]: {
99
+ attrs: {
100
+ [const_1.TabAttrs.id]: { default: null },
101
+ [const_1.TabAttrs.class]: { default: 'yfm-tab yfm-vertical-tab' },
102
+ [const_1.TabAttrs.role]: { default: 'unknown' },
103
+ [const_1.TabAttrs.ariaControls]: { default: 'unknown' },
104
+ [const_1.TabAttrs.ariaSelected]: { default: 'unknown' },
105
+ [const_1.TabAttrs.tabindex]: { default: 'unknown' },
106
+ [const_1.TabAttrs.dataDiplodocKey]: { default: 'unknown' },
107
+ [const_1.TabAttrs.dataDiplodocid]: { default: 'unknown' },
108
+ [const_1.TabAttrs.dataDiplodocIsActive]: { default: 'false' },
109
+ [const_1.TabAttrs.dataDiplodocVerticalTab]: { default: 'true' },
110
+ },
111
+ content: `${const_1.TabsNode.RadioTabInput} ${const_1.TabsNode.RadioTabLabel}`,
112
+ group: 'block',
113
+ toDOM(node) {
114
+ return ['div', node.attrs, 0];
115
+ },
116
+ selectable: false,
117
+ allowSelection: false,
118
+ complex: 'inner',
119
+ },
120
+ [const_1.TabsNode.RadioTabInput]: {
121
+ attrs: {
122
+ [const_1.TabAttrs.class]: { default: 'radio' },
123
+ type: { default: 'radio' },
124
+ checked: { default: null },
125
+ },
126
+ group: 'block',
127
+ toDOM(node) {
128
+ return ['input', node.attrs];
129
+ },
130
+ selectable: false,
131
+ allowSelection: false,
132
+ complex: 'leaf',
133
+ },
134
+ [const_1.TabsNode.RadioTabLabel]: {
135
+ attrs: {},
136
+ marks: '',
137
+ content: 'text*',
138
+ group: 'block',
139
+ toDOM(node) {
140
+ return ['label', node.attrs, 0];
141
+ },
142
+ placeholder: {
143
+ content: (_c = placeholder === null || placeholder === void 0 ? void 0 : placeholder[const_1.TabsNode.RadioTab]) !== null && _c !== void 0 ? _c : DEFAULT_PLACEHOLDERS.RadioTabLabelTitle,
144
+ alwaysVisible: true,
145
+ },
146
+ selectable: false,
147
+ allowSelection: false,
148
+ complex: 'leaf',
149
+ },
85
150
  });
86
151
  };
87
152
  exports.getSchemaSpecs = getSchemaSpecs;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.serializerTokens = void 0;
4
+ const nodes_1 = require("../../../../utils/nodes");
4
5
  const placeholder_1 = require("../../../../utils/placeholder");
5
6
  const const_1 = require("./const");
6
7
  exports.serializerTokens = {
@@ -32,4 +33,31 @@ exports.serializerTokens = {
32
33
  [const_1.TabsNode.TabsList]: (state, node) => {
33
34
  state.renderList(node, ' ', () => (node.attrs.bullet || '-') + ' ');
34
35
  },
36
+ [const_1.TabsNode.RadioTabs]: (state, node) => {
37
+ state.write('{% list tabs radio %}');
38
+ state.write('\n');
39
+ state.write('\n');
40
+ const children = (0, nodes_1.getChildrenOfNode)(node);
41
+ for (let i = 0; i < children.length; i++) {
42
+ const child = children[i];
43
+ if (child.node.type.name !== const_1.TabsNode.RadioTab)
44
+ continue;
45
+ state.write('- ' + (child.node.textContent || (0, placeholder_1.getPlaceholderContent)(child.node.lastChild)));
46
+ state.write('\n');
47
+ state.write('\n');
48
+ const nextChild = children[i + 1];
49
+ if (nextChild.node.type.name !== const_1.TabsNode.TabPanel)
50
+ continue;
51
+ state.renderList(nextChild.node, ' ', () => ' ');
52
+ }
53
+ state.write('{% endlist %}');
54
+ state.closeBlock(node);
55
+ },
56
+ [const_1.TabsNode.RadioTab]: (state, node) => {
57
+ state.renderInline(node);
58
+ },
59
+ [const_1.TabsNode.RadioTabInput]: () => { },
60
+ [const_1.TabsNode.RadioTabLabel]: (state, node) => {
61
+ state.renderInline(node);
62
+ },
35
63
  };
@@ -17,6 +17,8 @@ const YfmTabs = (builder) => {
17
17
  builder.use(YfmTabsSpecs_1.YfmTabsSpecs, {
18
18
  tabView: () => views_1.tabView,
19
19
  tabPanelView: () => views_1.tabPanelView,
20
+ vtabView: () => views_1.vtabView,
21
+ vtabInputView: () => views_1.vtabInputView,
20
22
  });
21
23
  builder.addKeymap(() => ({
22
24
  Backspace: (0, prosemirror_commands_1.chainCommands)(plugins_1.removeTabWhenCursorAtTheStartOfTab, plugins_1.joinBackwardToOpenTab),
@@ -1,3 +1,5 @@
1
1
  import { NodeViewConstructor } from 'prosemirror-view';
2
2
  export declare const tabView: NodeViewConstructor;
3
3
  export declare const tabPanelView: NodeViewConstructor;
4
+ export declare const vtabView: NodeViewConstructor;
5
+ export declare const vtabInputView: NodeViewConstructor;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.tabPanelView = exports.tabView = void 0;
3
+ exports.vtabInputView = exports.vtabView = exports.tabPanelView = exports.tabView = void 0;
4
4
  const prosemirror_utils_1 = require("prosemirror-utils");
5
5
  const classname_1 = require("../../../classname");
6
6
  const icons_1 = require("./icons");
@@ -12,7 +12,7 @@ const ignoreMutation = (node, view, getPos) => (mutation) => {
12
12
  mutation.type === 'attributes' &&
13
13
  mutation.attributeName) {
14
14
  const newAttr = mutation.target.getAttribute(mutation.attributeName);
15
- view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, Object.assign(Object.assign({}, node.attrs), { [mutation.attributeName]: String(newAttr) })));
15
+ view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, Object.assign(Object.assign({}, node.attrs), { [mutation.attributeName]: newAttr })));
16
16
  return true;
17
17
  }
18
18
  return false;
@@ -74,7 +74,19 @@ const tabView = (node, view, getPos) => {
74
74
  exports.tabView = tabView;
75
75
  // @ts-expect-error
76
76
  const tabPanelView = (node, view, getPos) => ({
77
- // FIX: ignore mutation and don't rerender node when yfm.js switch tab
77
+ // FIXME: ignore mutation and don't rerender node when yfm.js switch tab
78
78
  ignoreMutation: ignoreMutation(node, view, getPos),
79
79
  });
80
80
  exports.tabPanelView = tabPanelView;
81
+ // @ts-expect-error
82
+ const vtabView = (node, view, getPos) => ({
83
+ // FIXME: ignore mutation and don't rerender node when yfm.js switch tab
84
+ ignoreMutation: ignoreMutation(node, view, getPos),
85
+ });
86
+ exports.vtabView = vtabView;
87
+ // @ts-expect-error
88
+ const vtabInputView = (node, view, getPos) => ({
89
+ // FIXME: ignore mutation and don't rerender node when yfm.js switch tab
90
+ ignoreMutation: ignoreMutation(node, view, getPos),
91
+ });
92
+ exports.vtabInputView = vtabInputView;
@@ -1,6 +1,6 @@
1
1
  import { autocompletion } from '@codemirror/autocomplete';
2
2
  import type { Extension, StateCommand } from '@codemirror/state';
3
- import { EditorView, EditorViewConfig, KeyBinding } from '@codemirror/view';
3
+ import { EditorView, EditorViewConfig, KeyBinding, placeholder } from '@codemirror/view';
4
4
  import { EventMap } from '../../bundle/Editor';
5
5
  import { ReactRenderStorage } from '../../extensions';
6
6
  import { Receiver } from '../../utils';
@@ -10,7 +10,7 @@ export type { YfmLangOptions };
10
10
  declare type Autocompletion = Parameters<typeof autocompletion>[0];
11
11
  export declare type CreateCodemirrorParams = {
12
12
  doc: EditorViewConfig['doc'];
13
- placeholderText: string;
13
+ placeholder: Parameters<typeof placeholder>[0];
14
14
  onCancel: () => void;
15
15
  onSubmit: () => void;
16
16
  onChange: () => void;
@@ -16,8 +16,8 @@ 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, extensions: extraExtensions, disabledExtensions = {}, keymaps = [], receiver, yfmLangOptions, autocompletion: autocompletionConfig, } = params;
20
- const extensions = [gravity_1.gravityTheme, (0, view_1.placeholder)(placeholderText)];
19
+ const { doc, reactRenderer, onCancel, onScroll, onSubmit, onChange, onDocChange, disabledExtensions = {}, keymaps = [], receiver, yfmLangOptions, extensions: extraExtensions, placeholder: placeholderContent, autocompletion: autocompletionConfig, } = params;
20
+ const extensions = [gravity_1.gravityTheme, (0, view_1.placeholder)(placeholderContent)];
21
21
  if (!disabledExtensions.history) {
22
22
  extensions.push((0, commands_1.history)());
23
23
  }
@@ -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.18.2' !== 'undefined' ? '13.18.2' : 'unknown';
5
+ exports.VERSION = typeof '13.20.0' !== 'undefined' ? '13.20.0' : 'unknown';
@@ -57,6 +57,8 @@ export declare type MarkupConfig = {
57
57
  disabledExtensions?: CreateCodemirrorParams['disabledExtensions'];
58
58
  /** Additional keymaps for codemirror instance */
59
59
  keymaps?: CreateCodemirrorParams['keymaps'];
60
+ /** Overrides the default placeholder content. */
61
+ placeholder?: CreateCodemirrorParams['placeholder'];
60
62
  /**
61
63
  * Additional language data for markdown language in codemirror.
62
64
  * Can be used to configure additional autocompletions and others.
@@ -147,10 +147,11 @@ export class EditorImpl extends SafeEventEmitter {
147
147
  return __classPrivateFieldGet(this, _EditorImpl_wysiwygEditor, "f");
148
148
  }
149
149
  get markupEditor() {
150
+ var _a;
150
151
  if (!__classPrivateFieldGet(this, _EditorImpl_markupEditor, "f")) {
151
152
  __classPrivateFieldSet(this, _EditorImpl_markupEditor, new MarkupEditor(createCodemirror({
152
153
  doc: __classPrivateFieldGet(this, _EditorImpl_markup, "f"),
153
- placeholderText: i18n('markup_placeholder'),
154
+ placeholder: (_a = __classPrivateFieldGet(this, _EditorImpl_markupConfig, "f").placeholder) !== null && _a !== void 0 ? _a : i18n('markup_placeholder'),
154
155
  onCancel: () => this.emit('cancel', null),
155
156
  onSubmit: () => this.emit('submit', null),
156
157
  onChange: () => this.emit('rerender-toolbar', null),
@@ -191,15 +191,19 @@ export class MarkdownSerializerState {
191
191
  let trailing = '';
192
192
  const progress = (node, _, index) => {
193
193
  let marks = node ? node.marks : [];
194
- // Remove marks from breaks (hard_break or soft_break) that are the last node inside
194
+ // Remove marks from breaks (hard_break or soft_break) that are the edge node inside
195
195
  // that mark to prevent parser edge cases with new lines just
196
- // before closing marks.
196
+ // before closing or after opening marks.
197
197
  if (node && node.type.spec.isBreak) {
198
198
  marks = marks.filter(m => {
199
+ if (index === 0)
200
+ return false;
199
201
  if (index + 1 == parent.childCount)
200
202
  return false;
203
+ const prev = parent.child(index - 1);
201
204
  const next = parent.child(index + 1);
202
- return m.isInSet(next.marks) && (!next.isText || /\S/.test(next.text));
205
+ return ((m.isInSet(prev.marks) && (!prev.isText || /\S/.test(prev.text))) &&
206
+ (m.isInSet(next.marks) && (!next.isText || /\S/.test(next.text))));
203
207
  });
204
208
  }
205
209
  let leading = trailing;
@@ -63,10 +63,23 @@ export const serializerTokens = {
63
63
  state.ensureNewLine();
64
64
  });
65
65
  },
66
- [YfmTableNode.Row]: (_state, node) => {
67
- throw new Error(`Should not serialize ${node.type.name} node via serialize-token`);
66
+ [YfmTableNode.Row]: (state, node) => {
67
+ console.warn(`Should not serialize ${node.type.name} node via serialize-token`);
68
+ state.write('||');
69
+ state.ensureNewLine();
70
+ state.write('\n');
71
+ state.renderContent(node);
72
+ state.write('||');
73
+ state.ensureNewLine();
68
74
  },
69
- [YfmTableNode.Cell]: (_state, node) => {
70
- throw new Error(`Should not serialize ${node.type.name} node via serialize-token`);
75
+ [YfmTableNode.Cell]: (state, node, parent) => {
76
+ console.warn(`Should not serialize ${node.type.name} node via serialize-token`);
77
+ state.renderContent(node);
78
+ const isLastCellInRow = parent.lastChild === node;
79
+ if (!isLastCellInRow) {
80
+ state.write('|');
81
+ state.ensureNewLine();
82
+ state.write('\n');
83
+ }
71
84
  },
72
85
  };
@@ -1,8 +1,12 @@
1
1
  export declare enum TabsNode {
2
+ TabPanel = "yfm_tab_panel",
2
3
  Tab = "yfm_tab",
3
4
  TabsList = "yfm_tabs_list",
4
- TabPanel = "yfm_tab_panel",
5
- Tabs = "yfm_tabs"
5
+ Tabs = "yfm_tabs",
6
+ RadioTabs = "yfm_radio_tabs",
7
+ RadioTab = "yfm_radio_tab",
8
+ RadioTabInput = "yfm_radio_tab_input",
9
+ RadioTabLabel = "yfm_radio_tab_label"
6
10
  }
7
11
  export declare enum TabsAttrs {
8
12
  class = "class",
@@ -21,7 +25,8 @@ export declare enum TabAttrs {
21
25
  tabindex = "tabindex",
22
26
  dataDiplodocKey = "data-diplodoc-key",
23
27
  dataDiplodocid = "data-diplodoc-id",
24
- dataDiplodocIsActive = "data-diplodoc-is-active"
28
+ dataDiplodocIsActive = "data-diplodoc-is-active",
29
+ dataDiplodocVerticalTab = "data-diplodoc-vertical-tab"
25
30
  }
26
31
  export declare enum TabPanelAttrs {
27
32
  id = "id",
@@ -1,9 +1,14 @@
1
1
  export var TabsNode;
2
2
  (function (TabsNode) {
3
+ // tabs panel is common for tabs and radio tabs
4
+ TabsNode["TabPanel"] = "yfm_tab_panel";
3
5
  TabsNode["Tab"] = "yfm_tab";
4
6
  TabsNode["TabsList"] = "yfm_tabs_list";
5
- TabsNode["TabPanel"] = "yfm_tab_panel";
6
7
  TabsNode["Tabs"] = "yfm_tabs";
8
+ TabsNode["RadioTabs"] = "yfm_radio_tabs";
9
+ TabsNode["RadioTab"] = "yfm_radio_tab";
10
+ TabsNode["RadioTabInput"] = "yfm_radio_tab_input";
11
+ TabsNode["RadioTabLabel"] = "yfm_radio_tab_label";
7
12
  })(TabsNode || (TabsNode = {}));
8
13
  export var TabsAttrs;
9
14
  (function (TabsAttrs) {
@@ -26,6 +31,7 @@ export var TabAttrs;
26
31
  TabAttrs["dataDiplodocKey"] = "data-diplodoc-key";
27
32
  TabAttrs["dataDiplodocid"] = "data-diplodoc-id";
28
33
  TabAttrs["dataDiplodocIsActive"] = "data-diplodoc-is-active";
34
+ TabAttrs["dataDiplodocVerticalTab"] = "data-diplodoc-vertical-tab";
29
35
  })(TabAttrs || (TabAttrs = {}));
30
36
  export var TabPanelAttrs;
31
37
  (function (TabPanelAttrs) {
@@ -14,5 +14,7 @@ export declare type YfmTabsSpecsOptions = {
14
14
  tabsListView?: ExtensionNodeSpec['view'];
15
15
  tabPanelView?: ExtensionNodeSpec['view'];
16
16
  tabsView?: ExtensionNodeSpec['view'];
17
+ vtabView?: ExtensionNodeSpec['view'];
18
+ vtabInputView?: ExtensionNodeSpec['view'];
17
19
  };
18
20
  export declare const YfmTabsSpecs: ExtensionAuto<YfmTabsSpecsOptions>;
@@ -2,6 +2,7 @@ import log from '@diplodoc/transform/lib/log';
2
2
  import yfmPlugin from '@diplodoc/transform/lib/plugins/tabs';
3
3
  import { nodeTypeFactory } from '../../../../utils/schema';
4
4
  import { TabsNode } from './const';
5
+ import { tabsPostPlugin } from './md-plugin';
5
6
  import { parserTokens } from './parser';
6
7
  import { getSchemaSpecs } from './schema';
7
8
  import { serializerTokens } from './serializer';
@@ -13,7 +14,7 @@ export const tabsListType = nodeTypeFactory(TabsNode.TabsList);
13
14
  export const YfmTabsSpecs = (builder, opts) => {
14
15
  const schemaSpecs = getSchemaSpecs(opts);
15
16
  builder
16
- .configureMd((md) => md.use(yfmPlugin, { log }))
17
+ .configureMd((md) => md.use(yfmPlugin, { log }).use(tabsPostPlugin))
17
18
  .addNode(TabsNode.Tab, () => ({
18
19
  spec: schemaSpecs[TabsNode.Tab],
19
20
  toMd: serializerTokens[TabsNode.Tab],
@@ -50,4 +51,39 @@ export const YfmTabsSpecs = (builder, opts) => {
50
51
  },
51
52
  view: opts.tabsView,
52
53
  }));
54
+ builder
55
+ .addNode(TabsNode.RadioTabs, () => ({
56
+ spec: schemaSpecs[TabsNode.RadioTabs],
57
+ toMd: serializerTokens[TabsNode.RadioTabs],
58
+ fromMd: {
59
+ tokenSpec: parserTokens[TabsNode.RadioTabs],
60
+ tokenName: 'r-tabs',
61
+ },
62
+ }))
63
+ .addNode(TabsNode.RadioTab, () => ({
64
+ spec: schemaSpecs[TabsNode.RadioTab],
65
+ toMd: serializerTokens[TabsNode.RadioTab],
66
+ fromMd: {
67
+ tokenSpec: parserTokens[TabsNode.RadioTab],
68
+ tokenName: 'r-tab',
69
+ },
70
+ view: opts.vtabView,
71
+ }))
72
+ .addNode(TabsNode.RadioTabInput, () => ({
73
+ spec: schemaSpecs[TabsNode.RadioTabInput],
74
+ toMd: serializerTokens[TabsNode.RadioTabInput],
75
+ fromMd: {
76
+ tokenSpec: parserTokens[TabsNode.RadioTabInput],
77
+ tokenName: 'r-tab-input',
78
+ },
79
+ view: opts.vtabInputView,
80
+ }))
81
+ .addNode(TabsNode.RadioTabLabel, () => ({
82
+ spec: schemaSpecs[TabsNode.RadioTabLabel],
83
+ toMd: serializerTokens[TabsNode.RadioTabLabel],
84
+ fromMd: {
85
+ tokenSpec: parserTokens[TabsNode.RadioTabLabel],
86
+ tokenName: 'r-tab-label',
87
+ },
88
+ }));
53
89
  };
@@ -0,0 +1,2 @@
1
+ import type MarkdownIt from 'markdown-it';
2
+ export declare const tabsPostPlugin: MarkdownIt.PluginSimple;
@@ -0,0 +1,75 @@
1
+ const prefix = 'r-';
2
+ // prefix is added to token types of radio tabs tokens in order to use other nodes in prosemirror
3
+ export const tabsPostPlugin = (md) => {
4
+ md.core.ruler.push('me_tabs_after', (state) => {
5
+ var _a;
6
+ const stack = [];
7
+ const tabCloseIndexes = [];
8
+ for (const token of state.tokens) {
9
+ switch (token.type) {
10
+ case 'tabs_open': {
11
+ const vertical = (_a = token.attrGet('class')) === null || _a === void 0 ? void 0 : _a.includes('yfm-tabs-vertical');
12
+ stack.push({ vertical });
13
+ if (vertical)
14
+ token.type = prefix + token.type;
15
+ break;
16
+ }
17
+ case 'tabs_close': {
18
+ const item = stack.pop();
19
+ if (item === null || item === void 0 ? void 0 : item.vertical)
20
+ token.type = prefix + token.type;
21
+ break;
22
+ }
23
+ case 'tab_open': {
24
+ const item = stack.at(-1);
25
+ if (item === null || item === void 0 ? void 0 : item.vertical) {
26
+ // --> TODO: remove after updating to tabs-extension v^3.4.0
27
+ const isInput = token.tag === 'input';
28
+ if (isInput)
29
+ token.type = 'tab-input';
30
+ // <--
31
+ token.type = prefix + token.type;
32
+ }
33
+ break;
34
+ }
35
+ case 'tab_close': {
36
+ const item = stack.at(-1);
37
+ if (item === null || item === void 0 ? void 0 : item.vertical) {
38
+ token.type = prefix + token.type;
39
+ // --> TODO: remove after updating to tabs-extension v^3.4.0
40
+ tabCloseIndexes.push(state.tokens.indexOf(token));
41
+ // <--
42
+ }
43
+ break;
44
+ }
45
+ case 'tab-input':
46
+ case 'tab-label_open':
47
+ case 'tab-label_close': {
48
+ const item = stack.at(-1);
49
+ if (item === null || item === void 0 ? void 0 : item.vertical) {
50
+ token.type = prefix + token.type;
51
+ }
52
+ break;
53
+ }
54
+ // --> TODO: remove after updating to tabs-extension v^3.4.0
55
+ case 'label_open': {
56
+ const tokenIndex = state.tokens.indexOf(token);
57
+ const prevToken = tokenIndex > 0 && state.tokens.at(tokenIndex - 1);
58
+ if (prevToken && prevToken.type === 'r-tab-input') {
59
+ token.type = prefix + 'tab-' + token.type;
60
+ }
61
+ break;
62
+ }
63
+ // <--
64
+ }
65
+ }
66
+ // --> TODO: remove after updating to tabs-extension v^3.4.0
67
+ for (const index of tabCloseIndexes.reverse()) {
68
+ if (index === -1)
69
+ continue;
70
+ const labelCloseToken = new state.Token(prefix + 'tab-label_close', 'label', -1);
71
+ state.tokens.splice(index, 0, labelCloseToken);
72
+ }
73
+ // <--
74
+ });
75
+ };
@@ -1,13 +1,13 @@
1
1
  import { TabsNode } from './const';
2
2
  const attrsFromEntries = (token) => (token.attrs ? Object.fromEntries(token.attrs) : {});
3
3
  export const parserTokens = {
4
- [TabsNode.Tab]: {
5
- name: TabsNode.Tab,
4
+ [TabsNode.TabPanel]: {
5
+ name: TabsNode.TabPanel,
6
6
  type: 'block',
7
7
  getAttrs: attrsFromEntries,
8
8
  },
9
- [TabsNode.TabPanel]: {
10
- name: TabsNode.TabPanel,
9
+ [TabsNode.Tab]: {
10
+ name: TabsNode.Tab,
11
11
  type: 'block',
12
12
  getAttrs: attrsFromEntries,
13
13
  },
@@ -21,4 +21,24 @@ export const parserTokens = {
21
21
  type: 'block',
22
22
  getAttrs: attrsFromEntries,
23
23
  },
24
+ [TabsNode.RadioTabs]: {
25
+ name: TabsNode.RadioTabs,
26
+ type: 'block',
27
+ getAttrs: attrsFromEntries,
28
+ },
29
+ [TabsNode.RadioTab]: {
30
+ name: TabsNode.RadioTab,
31
+ type: 'block',
32
+ getAttrs: attrsFromEntries,
33
+ },
34
+ [TabsNode.RadioTabInput]: {
35
+ name: TabsNode.RadioTabInput,
36
+ type: 'node',
37
+ getAttrs: attrsFromEntries,
38
+ },
39
+ [TabsNode.RadioTabLabel]: {
40
+ name: TabsNode.RadioTabLabel,
41
+ type: 'block',
42
+ getAttrs: attrsFromEntries,
43
+ },
24
44
  };
@@ -1,9 +1,10 @@
1
1
  import { TabAttrs, TabPanelAttrs, TabsAttrs, TabsListAttrs, TabsNode } from './const';
2
2
  const DEFAULT_PLACEHOLDERS = {
3
3
  TabTitle: 'Tab title',
4
+ RadioTabLabelTitle: 'Radio title',
4
5
  };
5
6
  export const getSchemaSpecs = (opts, placeholder) => {
6
- var _a, _b;
7
+ var _a, _b, _c;
7
8
  return ({
8
9
  [TabsNode.Tab]: {
9
10
  attrs: {
@@ -79,5 +80,69 @@ export const getSchemaSpecs = (opts, placeholder) => {
79
80
  allowSelection: false,
80
81
  complex: 'inner',
81
82
  },
83
+ [TabsNode.RadioTabs]: {
84
+ attrs: {
85
+ [TabsAttrs.class]: { default: 'yfm-tabs yfm-tabs-vertical' },
86
+ [TabsAttrs.dataDiplodocGroup]: { default: 'unknown' },
87
+ },
88
+ content: `(${TabsNode.RadioTab} ${TabsNode.TabPanel})+`,
89
+ group: 'block',
90
+ toDOM(node) {
91
+ return ['div', node.attrs, 0];
92
+ },
93
+ complex: 'root',
94
+ },
95
+ [TabsNode.RadioTab]: {
96
+ attrs: {
97
+ [TabAttrs.id]: { default: null },
98
+ [TabAttrs.class]: { default: 'yfm-tab yfm-vertical-tab' },
99
+ [TabAttrs.role]: { default: 'unknown' },
100
+ [TabAttrs.ariaControls]: { default: 'unknown' },
101
+ [TabAttrs.ariaSelected]: { default: 'unknown' },
102
+ [TabAttrs.tabindex]: { default: 'unknown' },
103
+ [TabAttrs.dataDiplodocKey]: { default: 'unknown' },
104
+ [TabAttrs.dataDiplodocid]: { default: 'unknown' },
105
+ [TabAttrs.dataDiplodocIsActive]: { default: 'false' },
106
+ [TabAttrs.dataDiplodocVerticalTab]: { default: 'true' },
107
+ },
108
+ content: `${TabsNode.RadioTabInput} ${TabsNode.RadioTabLabel}`,
109
+ group: 'block',
110
+ toDOM(node) {
111
+ return ['div', node.attrs, 0];
112
+ },
113
+ selectable: false,
114
+ allowSelection: false,
115
+ complex: 'inner',
116
+ },
117
+ [TabsNode.RadioTabInput]: {
118
+ attrs: {
119
+ [TabAttrs.class]: { default: 'radio' },
120
+ type: { default: 'radio' },
121
+ checked: { default: null },
122
+ },
123
+ group: 'block',
124
+ toDOM(node) {
125
+ return ['input', node.attrs];
126
+ },
127
+ selectable: false,
128
+ allowSelection: false,
129
+ complex: 'leaf',
130
+ },
131
+ [TabsNode.RadioTabLabel]: {
132
+ attrs: {},
133
+ marks: '',
134
+ content: 'text*',
135
+ group: 'block',
136
+ toDOM(node) {
137
+ return ['label', node.attrs, 0];
138
+ },
139
+ placeholder: {
140
+ content: (_c = placeholder === null || placeholder === void 0 ? void 0 : placeholder[TabsNode.RadioTab]) !== null && _c !== void 0 ? _c : DEFAULT_PLACEHOLDERS.RadioTabLabelTitle,
141
+ alwaysVisible: true,
142
+ },
143
+ selectable: false,
144
+ allowSelection: false,
145
+ complex: 'leaf',
146
+ },
82
147
  });
83
148
  };
@@ -1,3 +1,4 @@
1
+ import { getChildrenOfNode } from '../../../../utils/nodes';
1
2
  import { getPlaceholderContent } from '../../../../utils/placeholder';
2
3
  import { TabsNode } from './const';
3
4
  export const serializerTokens = {
@@ -29,4 +30,31 @@ export const serializerTokens = {
29
30
  [TabsNode.TabsList]: (state, node) => {
30
31
  state.renderList(node, ' ', () => (node.attrs.bullet || '-') + ' ');
31
32
  },
33
+ [TabsNode.RadioTabs]: (state, node) => {
34
+ state.write('{% list tabs radio %}');
35
+ state.write('\n');
36
+ state.write('\n');
37
+ const children = getChildrenOfNode(node);
38
+ for (let i = 0; i < children.length; i++) {
39
+ const child = children[i];
40
+ if (child.node.type.name !== TabsNode.RadioTab)
41
+ continue;
42
+ state.write('- ' + (child.node.textContent || getPlaceholderContent(child.node.lastChild)));
43
+ state.write('\n');
44
+ state.write('\n');
45
+ const nextChild = children[i + 1];
46
+ if (nextChild.node.type.name !== TabsNode.TabPanel)
47
+ continue;
48
+ state.renderList(nextChild.node, ' ', () => ' ');
49
+ }
50
+ state.write('{% endlist %}');
51
+ state.closeBlock(node);
52
+ },
53
+ [TabsNode.RadioTab]: (state, node) => {
54
+ state.renderInline(node);
55
+ },
56
+ [TabsNode.RadioTabInput]: () => { },
57
+ [TabsNode.RadioTabLabel]: (state, node) => {
58
+ state.renderInline(node);
59
+ },
32
60
  };
@@ -2,13 +2,15 @@ import { chainCommands } from 'prosemirror-commands';
2
2
  import { YfmTabsSpecs } from './YfmTabsSpecs';
3
3
  import { createYfmTabs } from './actions';
4
4
  import { dragAutoSwitch, joinBackwardToOpenTab, liftEmptyBlockFromTabPanel, removeTabWhenCursorAtTheStartOfTab, tabEnter, tabPanelArrowDown, } from './plugins';
5
- import { tabPanelView, tabView } from './views';
5
+ import { tabPanelView, tabView, vtabInputView, vtabView } from './views';
6
6
  export { TabsNode, tabType, tabsType, tabsListType, tabPanelType } from './YfmTabsSpecs';
7
7
  const actionName = 'toYfmTabs';
8
8
  export const YfmTabs = (builder) => {
9
9
  builder.use(YfmTabsSpecs, {
10
10
  tabView: () => tabView,
11
11
  tabPanelView: () => tabPanelView,
12
+ vtabView: () => vtabView,
13
+ vtabInputView: () => vtabInputView,
12
14
  });
13
15
  builder.addKeymap(() => ({
14
16
  Backspace: chainCommands(removeTabWhenCursorAtTheStartOfTab, joinBackwardToOpenTab),
@@ -2,3 +2,5 @@ import { NodeViewConstructor } from 'prosemirror-view';
2
2
  import './index.css';
3
3
  export declare const tabView: NodeViewConstructor;
4
4
  export declare const tabPanelView: NodeViewConstructor;
5
+ export declare const vtabView: NodeViewConstructor;
6
+ export declare const vtabInputView: NodeViewConstructor;
@@ -10,7 +10,7 @@ const ignoreMutation = (node, view, getPos) => (mutation) => {
10
10
  mutation.type === 'attributes' &&
11
11
  mutation.attributeName) {
12
12
  const newAttr = mutation.target.getAttribute(mutation.attributeName);
13
- view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, Object.assign(Object.assign({}, node.attrs), { [mutation.attributeName]: String(newAttr) })));
13
+ view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, Object.assign(Object.assign({}, node.attrs), { [mutation.attributeName]: newAttr })));
14
14
  return true;
15
15
  }
16
16
  return false;
@@ -71,6 +71,16 @@ export const tabView = (node, view, getPos) => {
71
71
  };
72
72
  // @ts-expect-error
73
73
  export const tabPanelView = (node, view, getPos) => ({
74
- // FIX: ignore mutation and don't rerender node when yfm.js switch tab
74
+ // FIXME: ignore mutation and don't rerender node when yfm.js switch tab
75
+ ignoreMutation: ignoreMutation(node, view, getPos),
76
+ });
77
+ // @ts-expect-error
78
+ export const vtabView = (node, view, getPos) => ({
79
+ // FIXME: ignore mutation and don't rerender node when yfm.js switch tab
80
+ ignoreMutation: ignoreMutation(node, view, getPos),
81
+ });
82
+ // @ts-expect-error
83
+ export const vtabInputView = (node, view, getPos) => ({
84
+ // FIXME: ignore mutation and don't rerender node when yfm.js switch tab
75
85
  ignoreMutation: ignoreMutation(node, view, getPos),
76
86
  });
@@ -1,6 +1,6 @@
1
1
  import { autocompletion } from '@codemirror/autocomplete';
2
2
  import type { Extension, StateCommand } from '@codemirror/state';
3
- import { EditorView, EditorViewConfig, KeyBinding } from '@codemirror/view';
3
+ import { EditorView, EditorViewConfig, KeyBinding, placeholder } from '@codemirror/view';
4
4
  import { EventMap } from '../../bundle/Editor';
5
5
  import { ReactRenderStorage } from '../../extensions';
6
6
  import { Receiver } from '../../utils';
@@ -10,7 +10,7 @@ export type { YfmLangOptions };
10
10
  declare type Autocompletion = Parameters<typeof autocompletion>[0];
11
11
  export declare type CreateCodemirrorParams = {
12
12
  doc: EditorViewConfig['doc'];
13
- placeholderText: string;
13
+ placeholder: Parameters<typeof placeholder>[0];
14
14
  onCancel: () => void;
15
15
  onSubmit: () => void;
16
16
  onChange: () => void;
@@ -13,8 +13,8 @@ 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, extensions: extraExtensions, disabledExtensions = {}, keymaps = [], receiver, yfmLangOptions, autocompletion: autocompletionConfig, } = params;
17
- const extensions = [gravityTheme, placeholder(placeholderText)];
16
+ const { doc, reactRenderer, onCancel, onScroll, onSubmit, onChange, onDocChange, disabledExtensions = {}, keymaps = [], receiver, yfmLangOptions, extensions: extraExtensions, placeholder: placeholderContent, autocompletion: autocompletionConfig, } = params;
17
+ const extensions = [gravityTheme, placeholder(placeholderContent)];
18
18
  if (!disabledExtensions.history) {
19
19
  extensions.push(history());
20
20
  }
@@ -1,2 +1,2 @@
1
1
  /** During build process, the current version will be injected here */
2
- export const VERSION = typeof '13.18.2' !== 'undefined' ? '13.18.2' : 'unknown';
2
+ export const VERSION = typeof '13.20.0' !== 'undefined' ? '13.20.0' : 'unknown';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gravity-ui/markdown-editor",
3
- "version": "13.18.2",
3
+ "version": "13.20.0",
4
4
  "description": "Markdown wysiwyg and markup editor",
5
5
  "license": "MIT",
6
6
  "repository": {