@gravity-ui/markdown-editor 14.11.2 → 14.12.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 (108) hide show
  1. package/build/cjs/bundle/Editor.js +9 -2
  2. package/build/cjs/bundle/MarkupManager.d.ts +54 -0
  3. package/build/cjs/bundle/MarkupManager.js +68 -0
  4. package/build/cjs/bundle/config/dynamicModifiers.d.ts +3 -0
  5. package/build/cjs/bundle/config/dynamicModifiers.js +76 -0
  6. package/build/cjs/bundle/config/markup.js +7 -0
  7. package/build/cjs/bundle/config/previews/ActionPreview.css +45 -0
  8. package/build/cjs/bundle/config/previews/ActionPreview.d.ts +4 -0
  9. package/build/cjs/bundle/config/previews/ActionPreview.js +11 -0
  10. package/build/cjs/bundle/config/previews/HeadingPreview.d.ts +5 -0
  11. package/build/cjs/bundle/config/previews/HeadingPreview.js +16 -0
  12. package/build/cjs/bundle/config/previews/TextPreview.d.ts +1 -0
  13. package/build/cjs/bundle/config/previews/TextPreview.js +14 -0
  14. package/build/cjs/bundle/config/w-heading-config.js +11 -0
  15. package/build/cjs/bundle/toolbar/utils/toolbarsConfigs.js +2 -1
  16. package/build/cjs/bundle/types.d.ts +11 -0
  17. package/build/cjs/core/Editor.d.ts +2 -2
  18. package/build/cjs/core/Editor.js +16 -1
  19. package/build/cjs/core/ExtensionsManager.d.ts +12 -2
  20. package/build/cjs/core/ExtensionsManager.js +29 -13
  21. package/build/cjs/core/ParserTokensRegistry.d.ts +2 -1
  22. package/build/cjs/core/ParserTokensRegistry.js +2 -2
  23. package/build/cjs/core/SchemaDynamicModifier.d.ts +3 -0
  24. package/build/cjs/core/SchemaDynamicModifier.js +25 -0
  25. package/build/cjs/core/SchemaSpecRegistry.d.ts +2 -1
  26. package/build/cjs/core/SchemaSpecRegistry.js +9 -4
  27. package/build/cjs/core/SerializerTokensRegistry.d.ts +2 -1
  28. package/build/cjs/core/SerializerTokensRegistry.js +2 -2
  29. package/build/cjs/core/index.d.ts +2 -0
  30. package/build/cjs/core/markdown/MarkdownParser.d.ts +7 -3
  31. package/build/cjs/core/markdown/MarkdownParser.js +85 -16
  32. package/build/cjs/core/markdown/MarkdownSerializer.d.ts +4 -2
  33. package/build/cjs/core/markdown/MarkdownSerializer.js +16 -6
  34. package/build/cjs/core/markdown/MarkdownSerializerDynamicModifier.d.ts +1 -0
  35. package/build/cjs/core/markdown/MarkdownSerializerDynamicModifier.js +47 -0
  36. package/build/cjs/core/types/dynamicModifiers.d.ts +1 -0
  37. package/build/cjs/core/types/dynamicModifiers.js +2 -0
  38. package/build/cjs/core/utils/dynamicModifiers.d.ts +9 -0
  39. package/build/cjs/core/utils/dynamicModifiers.js +48 -0
  40. package/build/cjs/extensions/behavior/CommandMenu/component.d.ts +1 -1
  41. package/build/cjs/extensions/behavior/CommandMenu/component.js +10 -8
  42. package/build/cjs/i18n/action-previews/en.json +5 -0
  43. package/build/cjs/i18n/action-previews/index.d.ts +7 -0
  44. package/build/cjs/i18n/action-previews/index.js +9 -0
  45. package/build/cjs/i18n/action-previews/ru.json +5 -0
  46. package/build/cjs/modules/toolbars/items.js +9 -0
  47. package/build/cjs/modules/toolbars/types.d.ts +1 -0
  48. package/build/cjs/toolbar/PreviewTooltip.css +8 -0
  49. package/build/cjs/toolbar/PreviewTooltip.d.ts +7 -0
  50. package/build/cjs/toolbar/PreviewTooltip.js +13 -0
  51. package/build/cjs/toolbar/ToolbarListButton.js +23 -18
  52. package/build/cjs/toolbar/types.d.ts +1 -0
  53. package/build/cjs/version.js +1 -1
  54. package/build/esm/bundle/Editor.js +9 -2
  55. package/build/esm/bundle/MarkupManager.d.ts +54 -0
  56. package/build/esm/bundle/MarkupManager.js +64 -0
  57. package/build/esm/bundle/config/dynamicModifiers.d.ts +3 -0
  58. package/build/esm/bundle/config/dynamicModifiers.js +72 -0
  59. package/build/esm/bundle/config/markup.js +7 -0
  60. package/build/esm/bundle/config/previews/ActionPreview.css +45 -0
  61. package/build/esm/bundle/config/previews/ActionPreview.d.ts +5 -0
  62. package/build/esm/bundle/config/previews/ActionPreview.js +7 -0
  63. package/build/esm/bundle/config/previews/HeadingPreview.d.ts +6 -0
  64. package/build/esm/bundle/config/previews/HeadingPreview.js +12 -0
  65. package/build/esm/bundle/config/previews/TextPreview.d.ts +2 -0
  66. package/build/esm/bundle/config/previews/TextPreview.js +10 -0
  67. package/build/esm/bundle/config/w-heading-config.js +10 -0
  68. package/build/esm/bundle/toolbar/utils/toolbarsConfigs.js +2 -1
  69. package/build/esm/bundle/types.d.ts +11 -0
  70. package/build/esm/core/Editor.d.ts +2 -2
  71. package/build/esm/core/Editor.js +16 -1
  72. package/build/esm/core/ExtensionsManager.d.ts +12 -2
  73. package/build/esm/core/ExtensionsManager.js +29 -13
  74. package/build/esm/core/ParserTokensRegistry.d.ts +2 -1
  75. package/build/esm/core/ParserTokensRegistry.js +2 -2
  76. package/build/esm/core/SchemaDynamicModifier.d.ts +3 -0
  77. package/build/esm/core/SchemaDynamicModifier.js +21 -0
  78. package/build/esm/core/SchemaSpecRegistry.d.ts +2 -1
  79. package/build/esm/core/SchemaSpecRegistry.js +9 -4
  80. package/build/esm/core/SerializerTokensRegistry.d.ts +2 -1
  81. package/build/esm/core/SerializerTokensRegistry.js +2 -2
  82. package/build/esm/core/index.d.ts +2 -0
  83. package/build/esm/core/markdown/MarkdownParser.d.ts +7 -3
  84. package/build/esm/core/markdown/MarkdownParser.js +83 -15
  85. package/build/esm/core/markdown/MarkdownSerializer.d.ts +4 -2
  86. package/build/esm/core/markdown/MarkdownSerializer.js +16 -6
  87. package/build/esm/core/markdown/MarkdownSerializerDynamicModifier.d.ts +1 -0
  88. package/build/esm/core/markdown/MarkdownSerializerDynamicModifier.js +43 -0
  89. package/build/esm/core/types/dynamicModifiers.d.ts +1 -0
  90. package/build/esm/core/types/dynamicModifiers.js +1 -0
  91. package/build/esm/core/utils/dynamicModifiers.d.ts +9 -0
  92. package/build/esm/core/utils/dynamicModifiers.js +44 -0
  93. package/build/esm/extensions/behavior/CommandMenu/component.d.ts +1 -1
  94. package/build/esm/extensions/behavior/CommandMenu/component.js +10 -8
  95. package/build/esm/i18n/action-previews/en.json +5 -0
  96. package/build/esm/i18n/action-previews/index.d.ts +7 -0
  97. package/build/esm/i18n/action-previews/index.js +5 -0
  98. package/build/esm/i18n/action-previews/ru.json +5 -0
  99. package/build/esm/modules/toolbars/items.js +9 -0
  100. package/build/esm/modules/toolbars/types.d.ts +1 -0
  101. package/build/esm/toolbar/PreviewTooltip.css +8 -0
  102. package/build/esm/toolbar/PreviewTooltip.d.ts +8 -0
  103. package/build/esm/toolbar/PreviewTooltip.js +9 -0
  104. package/build/esm/toolbar/ToolbarListButton.js +23 -18
  105. package/build/esm/toolbar/types.d.ts +1 -0
  106. package/build/esm/version.js +1 -1
  107. package/build/styles.css +53 -0
  108. package/package.json +3 -2
@@ -1,13 +1,14 @@
1
1
  export class MarkdownSerializer {
2
- constructor(nodes: any, marks: any);
2
+ constructor(nodes: any, marks: any, dynamicModifier: any);
3
3
  nodes: any;
4
4
  marks: any;
5
+ dynamicModifier: any;
5
6
  serialize(content: any, options: any): string;
6
7
  containsNode(nodeName: any): boolean;
7
8
  containsMark(markName: any): boolean;
8
9
  }
9
10
  export class MarkdownSerializerState {
10
- constructor(nodes: any, marks: any, options: any);
11
+ constructor(nodes: any, marks: any, options: any, dynamicModifier: any);
11
12
  nodes: any;
12
13
  marks: any;
13
14
  delim: string;
@@ -20,6 +21,7 @@ export class MarkdownSerializerState {
20
21
  /** @type {Boolean} */
21
22
  escapeWhitespace: boolean;
22
23
  options: any;
24
+ dynamicModifier: any;
23
25
  setNoAutoBlank(): void;
24
26
  unsetNoAutoBlank(): void;
25
27
  flushClose(size: any): void;
@@ -36,18 +36,19 @@ export class MarkdownSerializer {
36
36
  // outside the marks. This is necessary for emphasis marks as
37
37
  // CommonMark does not permit enclosing whitespace inside emphasis
38
38
  // marks, see: http://spec.commonmark.org/0.26/#example-330
39
- constructor(nodes, marks) {
39
+ constructor(nodes, marks, dynamicModifier) {
40
40
  // :: Object<(MarkdownSerializerState, Node)> The node serializer
41
41
  // functions for this serializer.
42
42
  this.nodes = nodes;
43
43
  // :: Object The mark serializer info.
44
44
  this.marks = marks;
45
+ this.dynamicModifier = dynamicModifier;
45
46
  }
46
47
  // :: (Node, ?Object) → string
47
48
  // Serialize the content of the given node to
48
49
  // [CommonMark](http://commonmark.org/).
49
50
  serialize(content, options) {
50
- const state = new MarkdownSerializerState(this.nodes, this.marks, options);
51
+ const state = new MarkdownSerializerState(this.nodes, this.marks, options, this.dynamicModifier);
51
52
  state.renderContent(content);
52
53
  return state.out;
53
54
  }
@@ -65,7 +66,7 @@ export class MarkdownSerializer {
65
66
  // node and mark serialization methods (see `toMarkdown`).
66
67
  // prettier-ignore
67
68
  export class MarkdownSerializerState {
68
- constructor(nodes, marks, options) {
69
+ constructor(nodes, marks, options, dynamicModifier) {
69
70
  this.nodes = nodes;
70
71
  this.marks = marks;
71
72
  this.delim = this.out = '';
@@ -83,6 +84,7 @@ export class MarkdownSerializerState {
83
84
  // on a node level by specifying a tight attribute on the node.
84
85
  // Defaults to false.
85
86
  this.options = options || {};
87
+ this.dynamicModifier = dynamicModifier;
86
88
  if (typeof this.options.tightLists === 'undefined') {
87
89
  this.options.tightLists = false;
88
90
  }
@@ -173,11 +175,19 @@ export class MarkdownSerializerState {
173
175
  // :: (Node)
174
176
  // Render the given node as a block.
175
177
  render(node, parent, index) {
176
- if (typeof parent === 'number')
178
+ if (typeof parent === 'number') {
177
179
  throw new Error('!');
178
- if (!this.nodes[node.type.name])
180
+ }
181
+ if (!this.nodes[node.type.name]) {
179
182
  throw new Error('Token type `' + node.type.name + '` not supported by Markdown renderer');
180
- this.nodes[node.type.name](this, node, parent, index);
183
+ }
184
+ const callback = this.nodes[node.type.name];
185
+ if (this.dynamicModifier) {
186
+ this.dynamicModifier.processNode(this, node, parent, index, callback);
187
+ }
188
+ else {
189
+ callback(this, node, parent, index);
190
+ }
181
191
  }
182
192
  // :: (Node)
183
193
  // Render the contents of `parent` as block nodes.
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Class MarkdownSerializerDynamicModifier
3
+ *
4
+ * Provides a mechanism for dynamic modification of node serialization during conversion of ProseMirror nodes
5
+ * to a markdown-like format. It allows sequential processing of nodes by applying a series of custom handlers:
6
+ *
7
+ * - `processNode`: An array of handlers that process nodes sequentially, each modifying the output.
8
+ *
9
+ * Example:
10
+ * ```ts
11
+ * const serializerModifier = new MarkdownSerializerDynamicModifier({
12
+ * paragraph: {
13
+ * processNode: [
14
+ * (state, node, parent, index, callback) => {
15
+ * // Custom modifications can be performed here.
16
+ * callback(state, node, parent, index);
17
+ * },
18
+ * ],
19
+ * },
20
+ * });
21
+ * ```
22
+ *
23
+ * This class extends the functionality of a MarkdownSerializer for scenarios such as:
24
+ * - Customizing the serialization output.
25
+ * - Logging or modifying node content during serialization.
26
+ */
27
+ /** @internal */
28
+ export class MarkdownSerializerDynamicModifier {
29
+ constructor(config) {
30
+ this.nodeProcessors = new Map(Object.entries(config));
31
+ }
32
+ processNode(state, node, parent, index, callback) {
33
+ const nodeType = this.nodeProcessors.get(node.type.name);
34
+ if (!nodeType || !nodeType.processNode || nodeType.processNode.length === 0) {
35
+ callback(state, node, parent, index);
36
+ }
37
+ else {
38
+ nodeType.processNode.forEach((process) => {
39
+ process(state, node, parent, index, callback);
40
+ });
41
+ }
42
+ }
43
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,9 @@
1
+ import { SchemaDynamicModifierConfig } from '../SchemaDynamicModifier';
2
+ import { MarkdownParserDynamicModifierConfig } from '../markdown/MarkdownParser';
3
+ import { MarkdownSerializerDynamicModifierConfig } from '../markdown/MarkdownSerializerDynamicModifier';
4
+ import { DynamicModifiers } from '../types/dynamicModifiers';
5
+ export declare function convertDynamicModifiersConfigs(modifiers: DynamicModifiers[]): {
6
+ parser: MarkdownParserDynamicModifierConfig;
7
+ schema: SchemaDynamicModifierConfig;
8
+ serializer: MarkdownSerializerDynamicModifierConfig;
9
+ };
@@ -0,0 +1,44 @@
1
+ export function convertDynamicModifiersConfigs(modifiers) {
2
+ const parser = {};
3
+ const schema = {};
4
+ const serializer = {};
5
+ modifiers.forEach((modifier) => {
6
+ var _a, _b, _c, _d;
7
+ switch (modifier.type) {
8
+ case 'parserToken': {
9
+ const { tokenName, process } = modifier;
10
+ parser[tokenName] = parser[tokenName] || {};
11
+ parser[tokenName].processToken = parser[tokenName].processToken || [];
12
+ (_a = parser[tokenName].processToken) === null || _a === void 0 ? void 0 : _a.push(process);
13
+ break;
14
+ }
15
+ case 'parserNodeAttrs': {
16
+ const { tokenName, process } = modifier;
17
+ parser[tokenName] = parser[tokenName] || {};
18
+ parser[tokenName].processNodeAttrs = parser[tokenName].processNodeAttrs || [];
19
+ (_b = parser[tokenName].processNodeAttrs) === null || _b === void 0 ? void 0 : _b.push(process);
20
+ break;
21
+ }
22
+ case 'parserNode': {
23
+ const { nodeName, process } = modifier;
24
+ parser[nodeName] = parser[nodeName] || {};
25
+ parser[nodeName].processNode = parser[nodeName].processNode || [];
26
+ (_c = parser[nodeName].processNode) === null || _c === void 0 ? void 0 : _c.push(process);
27
+ break;
28
+ }
29
+ case 'serializerNode': {
30
+ const { nodeName, process } = modifier;
31
+ serializer[nodeName] = serializer[nodeName] || {};
32
+ serializer[nodeName].processNode = serializer[nodeName].processNode || [];
33
+ (_d = serializer[nodeName].processNode) === null || _d === void 0 ? void 0 : _d.push(process);
34
+ break;
35
+ }
36
+ case 'schemaNodeSpec': {
37
+ const { nodeName, allowedAttrs } = modifier;
38
+ schema[nodeName] = { allowedAttrs };
39
+ break;
40
+ }
41
+ }
42
+ });
43
+ return { parser, schema, serializer };
44
+ }
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  import { AutocompletePopupProps } from '../../../utils/autocomplete-popup';
3
3
  import type { CommandAction } from './types';
4
4
  import './component.css';
5
- export declare type CommandMenuItem = Pick<CommandAction, 'id' | 'title' | 'icon' | 'hotkey' | 'hint'>;
5
+ export declare type CommandMenuItem = Pick<CommandAction, 'id' | 'title' | 'icon' | 'hotkey' | 'hint' | 'preview'>;
6
6
  export declare type CommandMenuComponentProps = AutocompletePopupProps & {
7
7
  currentIndex?: number;
8
8
  items: readonly CommandMenuItem[];
@@ -5,6 +5,7 @@ import { cn } from '../../../classname';
5
5
  import { i18n } from '../../../i18n/suggest';
6
6
  import { isFunction } from '../../../lodash';
7
7
  import { ErrorLoggerBoundary } from '../../../react-utils/ErrorBoundary';
8
+ import { PreviewTooltip } from '../../../toolbar/PreviewTooltip';
8
9
  import './component.css';
9
10
  const b = cn('command-menu');
10
11
  const placement = ['bottom-start', 'top-start', 'bottom-end', 'top-end'];
@@ -23,16 +24,17 @@ export const CommandMenuComponent = ({ anchor, currentIndex, items, onClick, onE
23
24
  React.createElement("div", { className: b() },
24
25
  React.createElement(List, { virtualized: true, items: items, sortable: false, filterable: false, emptyPlaceholder: i18n('empty-msg'), itemHeight: ITEM_HEIGHT, itemsHeight: calcListHeight(items.length), renderItem: renderItem, deactivateOnLeave: false, activeItemIndex: currentIndex, onItemClick: (_item, index) => onClick(index), className: b('list'), itemClassName: b('list-item') }))));
25
26
  };
26
- function renderItem({ id, title, icon, hotkey, hint }) {
27
+ function renderItem({ id, title, icon, hotkey, hint, preview }) {
27
28
  const titleText = isFunction(title) ? title() : title;
28
29
  const hintText = isFunction(hint) ? hint() : hint;
29
- return (React.createElement("div", { key: id, className: b('item', { id }) },
30
- React.createElement(Icon, { data: icon.data, size: 20, className: b('item-icon') }),
31
- React.createElement("div", { className: b('item-body') },
32
- React.createElement("span", { className: b('item-title') }, titleText),
33
- React.createElement("div", { className: b('item-extra') },
34
- hotkey && React.createElement(Hotkey, { value: hotkey, className: b('item-hotkey') }),
35
- hintText && React.createElement(HelpPopover, { className: b('item- hint'), content: hintText })))));
30
+ return (React.createElement(PreviewTooltip, { preview: preview },
31
+ React.createElement("div", { key: id, className: b('item', { id }) },
32
+ React.createElement(Icon, { data: icon.data, size: 20, className: b('item-icon') }),
33
+ React.createElement("div", { className: b('item-body') },
34
+ React.createElement("span", { className: b('item-title') }, titleText),
35
+ React.createElement("div", { className: b('item-extra') },
36
+ hotkey && React.createElement(Hotkey, { value: hotkey, className: b('item-hotkey') }),
37
+ hintText && React.createElement(HelpPopover, { className: b('item-hint'), content: hintText }))))));
36
38
  }
37
39
  export function render(props) {
38
40
  return (React.createElement(ErrorLoggerBoundary, null,
@@ -0,0 +1,5 @@
1
+ {
2
+ "text": "This is a text without a title. \nBoth the title and the text \nbelow it can be highlighted in bold, italics, color, \nstrikethrough, and underline. You can also add lists, \ntables, links, formulas, anchors, \nand code blocks.",
3
+ "text-with-head": "This is the text with the title. \nBoth the title and the text \nbelow it can be highlighted in bold, italics, color, \nstrikethrough, and underline. You can also add lists, \ntables, links, formulas, anchors, \nand code blocks.",
4
+ "heading": "Text"
5
+ }
@@ -0,0 +1,7 @@
1
+ export declare const i18n: <G extends "text" | "heading" | "text-with-head", S extends string>(key: G | (string extends S ? S : never), params?: {
2
+ [key: string]: any;
3
+ } | undefined) => S extends G ? {
4
+ text: string;
5
+ "text-with-head": string;
6
+ heading: string;
7
+ }[G] : string;
@@ -0,0 +1,5 @@
1
+ import { registerKeyset } from '../i18n';
2
+ import en from './en.json';
3
+ import ru from './ru.json';
4
+ const KEYSET = 'action-previews';
5
+ export const i18n = registerKeyset(KEYSET, { en, ru });
@@ -0,0 +1,5 @@
1
+ {
2
+ "text": "Это текст без заголовка. И заголовок, и \nтекст под ним можно выделять \nполужирным, курсивом, цветом, \nзачёркивать и подчёркивать. А ещё — \nдобавлять списки, таблицы, ссылки, \nформулы, якоря и блоки кода",
3
+ "text-with-head": "Это текст с заголовком. И заголовок, и \nтекст под ним можно выделять \nполужирным, курсивом, цветом, \nзачёркивать и подчёркивать. А ещё — \nдобавлять списки, таблицы, ссылки, \nформулы, якоря и блоки кода.",
4
+ "heading": "О тексте"
5
+ }
@@ -1,5 +1,7 @@
1
1
  import React from 'react';
2
2
  import { icons } from '../../bundle/config/icons';
3
+ import { HeadingPreview } from '../../bundle/config/previews/HeadingPreview';
4
+ import { TextPreview } from '../../bundle/config/previews/TextPreview';
3
5
  import { MToolbarColors } from '../../bundle/toolbar/markup/MToolbarColors';
4
6
  import { MToolbarFilePopup } from '../../bundle/toolbar/markup/MToolbarFilePopup';
5
7
  import { MToolbarImagePopup } from '../../bundle/toolbar/markup/MToolbarImagePopup';
@@ -453,6 +455,7 @@ export const heading1ItemView = {
453
455
  title: i18n.bind(null, 'heading1'),
454
456
  icon: icons.h1,
455
457
  hotkey: f.toView(A.Heading1),
458
+ preview: React.createElement(HeadingPreview, { level: 1 }),
456
459
  };
457
460
  export const heading1ItemWysiwyg = {
458
461
  exec: (e) => e.actions.toH1.run(),
@@ -470,6 +473,7 @@ export const heading2ItemView = {
470
473
  title: i18n.bind(null, 'heading2'),
471
474
  icon: icons.h2,
472
475
  hotkey: f.toView(A.Heading2),
476
+ preview: React.createElement(HeadingPreview, { level: 2 }),
473
477
  };
474
478
  export const heading2ItemWysiwyg = {
475
479
  exec: (e) => e.actions.toH2.run(),
@@ -487,6 +491,7 @@ export const heading3ItemView = {
487
491
  title: i18n.bind(null, 'heading3'),
488
492
  icon: icons.h3,
489
493
  hotkey: f.toView(A.Heading3),
494
+ preview: React.createElement(HeadingPreview, { level: 3 }),
490
495
  };
491
496
  export const heading3ItemWysiwyg = {
492
497
  exec: (e) => e.actions.toH3.run(),
@@ -504,6 +509,7 @@ export const heading4ItemView = {
504
509
  title: i18n.bind(null, 'heading4'),
505
510
  icon: icons.h4,
506
511
  hotkey: f.toView(A.Heading4),
512
+ preview: React.createElement(HeadingPreview, { level: 4 }),
507
513
  };
508
514
  export const heading4ItemWysiwyg = {
509
515
  exec: (e) => e.actions.toH4.run(),
@@ -521,6 +527,7 @@ export const heading5ItemView = {
521
527
  title: i18n.bind(null, 'heading5'),
522
528
  icon: icons.h5,
523
529
  hotkey: f.toView(A.Heading5),
530
+ preview: React.createElement(HeadingPreview, { level: 5 }),
524
531
  };
525
532
  export const heading5ItemWysiwyg = {
526
533
  exec: (e) => e.actions.toH5.run(),
@@ -538,6 +545,7 @@ export const heading6ItemView = {
538
545
  title: i18n.bind(null, 'heading6'),
539
546
  icon: icons.h6,
540
547
  hotkey: f.toView(A.Heading6),
548
+ preview: React.createElement(HeadingPreview, { level: 6 }),
541
549
  };
542
550
  export const heading6ItemWysiwyg = {
543
551
  exec: (e) => e.actions.toH6.run(),
@@ -653,6 +661,7 @@ export const paragraphItemView = {
653
661
  icon: icons.text,
654
662
  hotkey: f.toView(A.Text),
655
663
  doNotActivateList: true,
664
+ preview: React.createElement(TextPreview, null),
656
665
  };
657
666
  export const paragraphItemWisywig = {
658
667
  exec: (e) => e.actions.toParagraph.run(),
@@ -22,6 +22,7 @@ export declare type ToolbarItemView<T extends ToolbarDataType = ToolbarDataType.
22
22
  } & (T extends ToolbarDataType.SingleButton ? {
23
23
  icon: ToolbarIconData;
24
24
  title: string | (() => string);
25
+ preview?: React.ReactNode;
25
26
  } : T extends ToolbarDataType.ListButton ? {
26
27
  withArrow?: boolean;
27
28
  icon: ToolbarIconData;
@@ -0,0 +1,8 @@
1
+ .g-md-preview-tooltip {
2
+ padding: var(--g-spacing-3);
3
+ border-radius: var(--g-border-radius-s);
4
+ background-color: var(--g-color-base-float-heavy);
5
+ }
6
+ .g-md-preview-tooltip .g-popup__content:first-child {
7
+ padding: 0 0 0 var(--g-spacing-2);
8
+ }
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ import './PreviewTooltip.css';
3
+ declare type PreviewTooltipProps = {
4
+ preview?: React.ReactNode;
5
+ children: React.ReactElement;
6
+ };
7
+ export declare const PreviewTooltip: React.FC<PreviewTooltipProps>;
8
+ export {};
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ import { Tooltip } from '@gravity-ui/uikit';
3
+ import { cn } from '../classname';
4
+ import { ToolbarTooltipDelay } from './const';
5
+ import './PreviewTooltip.css';
6
+ const b = cn('preview-tooltip');
7
+ export const PreviewTooltip = ({ preview, children }) => {
8
+ return (React.createElement(Tooltip, { placement: "right", className: b(), contentClassName: b('content'), disabled: !preview, openDelay: ToolbarTooltipDelay.Open, closeDelay: ToolbarTooltipDelay.Close, content: preview }, children));
9
+ };
@@ -6,6 +6,7 @@ import { cn } from '../classname';
6
6
  import { i18n } from '../i18n/common';
7
7
  import { isFunction } from '../lodash';
8
8
  import { useBooleanState } from '../react-utils/hooks';
9
+ import { PreviewTooltip } from './PreviewTooltip';
9
10
  import { ToolbarTooltipDelay } from './const';
10
11
  import './ToolbarListButton.css';
11
12
  const b = cn('toolbar-list-button');
@@ -46,7 +47,7 @@ export function ToolbarListButton({ className, editor, focus, onClick, icon, tit
46
47
  React.createElement(Menu, { size: "l", className: b('menu') }, data
47
48
  .map((data) => {
48
49
  var _a;
49
- const { id, title, icon, hotkey, isActive, isEnable, exec, hint, hintWhenDisabled, disabledPopoverVisible = true, } = data;
50
+ const { id, title, icon, hotkey, isActive, isEnable, exec, hint, hintWhenDisabled, disabledPopoverVisible = true, preview, } = data;
50
51
  const titleText = isFunction(title) ? title() : title;
51
52
  const hintText = isFunction(hint) ? hint() : hint;
52
53
  const disabled = !isEnable(editor);
@@ -56,24 +57,28 @@ export function ToolbarListButton({ className, editor, focus, onClick, icon, tit
56
57
  : typeof hintWhenDisabled === 'function'
57
58
  ? hintWhenDisabled()
58
59
  : i18n('toolbar_action_disabled');
60
+ const handleClick = () => {
61
+ hide();
62
+ if (isPopupItem(data)) {
63
+ setPopupItem(data);
64
+ }
65
+ else {
66
+ setPopupItem(undefined);
67
+ focus();
68
+ exec(editor);
69
+ onClick === null || onClick === void 0 ? void 0 : onClick(id);
70
+ }
71
+ };
59
72
  return (React.createElement(Popover, { className: b('action-disabled-popover'), tooltipContentClassName: b('action-disabled-tooltip'), content: hintWhenDisabledText, placement: 'left', disabled: hideHintWhenDisabled, key: id },
60
- React.createElement(Menu.Item, { key: id, active: isActive(editor), disabled: !isEnable(editor), onClick: () => {
61
- hide();
62
- if (isPopupItem(data)) {
63
- setPopupItem(data);
64
- }
65
- else {
66
- setPopupItem(undefined);
67
- focus();
68
- exec(editor);
69
- onClick === null || onClick === void 0 ? void 0 : onClick(id);
70
- }
71
- }, icon: React.createElement(Icon, { data: icon.data, size: (_a = icon.size) !== null && _a !== void 0 ? _a : 16 }), extraProps: { 'aria-label': titleText } },
72
- React.createElement("div", { className: b('item') },
73
- titleText,
74
- React.createElement("div", { className: b('extra') },
75
- hotkey && React.createElement(Hotkey, { value: hotkey }),
76
- hintText && (React.createElement(HelpPopover, { className: b('hint'), content: hintText })))))));
73
+ React.createElement(PreviewTooltip, { preview: preview },
74
+ React.createElement(Menu.Item, { key: id, active: isActive(editor), disabled: !isEnable(editor), onClick: handleClick, icon: React.createElement(Icon, { data: icon.data, size: (_a = icon.size) !== null && _a !== void 0 ? _a : 16 }), extraProps: {
75
+ 'aria-label': titleText,
76
+ } },
77
+ React.createElement("div", { className: b('item') },
78
+ titleText,
79
+ React.createElement("div", { className: b('extra') },
80
+ hotkey && React.createElement(Hotkey, { value: hotkey }),
81
+ hintText && (React.createElement(HelpPopover, { className: b('hint'), content: hintText }))))))));
77
82
  })
78
83
  .filter(Boolean))),
79
84
  popupItem
@@ -17,6 +17,7 @@ export declare type ToolbarItemData<E> = {
17
17
  title: string | (() => string);
18
18
  hint?: string | (() => string);
19
19
  hotkey?: HotkeyProps['value'];
20
+ preview?: React.ReactNode;
20
21
  /** @deprecated Use _hintWhenDisabled_ setting instead */
21
22
  disabledPopoverVisible?: boolean;
22
23
  /**
@@ -1,2 +1,2 @@
1
1
  /** During build process, the current version will be injected here */
2
- export const VERSION = typeof '14.11.2' !== 'undefined' ? '14.11.2' : 'unknown';
2
+ export const VERSION = typeof '14.12.0' !== 'undefined' ? '14.12.0' : 'unknown';
package/build/styles.css CHANGED
@@ -345,6 +345,14 @@
345
345
  display: flex;
346
346
  gap: 0 8px;
347
347
  }
348
+ .g-md-preview-tooltip {
349
+ padding: var(--g-spacing-3);
350
+ border-radius: var(--g-border-radius-s);
351
+ background-color: var(--g-color-base-float-heavy);
352
+ }
353
+ .g-md-preview-tooltip .g-popup__content:first-child {
354
+ padding: 0 0 0 var(--g-spacing-2);
355
+ }
348
356
  .g-md-toolbar {
349
357
  display: flex;
350
358
  }
@@ -583,6 +591,51 @@
583
591
  .g-md-base-tooltip__content__switch {
584
592
  margin-right: 28px;
585
593
  }
594
+ :root {
595
+ --toolbar-item-preview-width: 144px;
596
+ --toolbar-item-preview-height: 104px;
597
+ --toolbar-item-preview-h1-margin: 10px 0 2px;
598
+ --toolbar-item-preview-h2-margin: 20px 0 4px;
599
+ --toolbar-item-preview-h3-margin: 24px 0 4px;
600
+ --toolbar-item-preview-h4-margin: 24px 0 4px;
601
+ --toolbar-item-preview-h5-margin: 26px 0 6px;
602
+ --toolbar-item-preview-h6-margin: 28px 0 6px;
603
+ }
604
+
605
+ .g-md-action-preview {
606
+ overflow: hidden;
607
+ width: var(--toolbar-item-preview-width);
608
+ height: var(--toolbar-item-preview-height);
609
+ white-space: pre;
610
+ }
611
+ .g-md-action-preview.yfm > *:not(h2):not(h3):not(h4):not(h5):not(h6):first-child {
612
+ /* stylelint-disable-next-line declaration-no-important */
613
+ margin: var(--toolbar-item-preview-h1-margin) !important;
614
+ }
615
+ .g-md-action-preview.yfm > h2 {
616
+ /* stylelint-disable-next-line declaration-no-important */
617
+ margin: var(--toolbar-item-preview-h2-margin) !important;
618
+ }
619
+ .g-md-action-preview.yfm > h3 {
620
+ /* stylelint-disable-next-line declaration-no-important */
621
+ margin: var(--toolbar-item-preview-h3-margin) !important;
622
+ }
623
+ .g-md-action-preview.yfm > h4 {
624
+ /* stylelint-disable-next-line declaration-no-important */
625
+ margin: var(--toolbar-item-preview-h4-margin) !important;
626
+ }
627
+ .g-md-action-preview.yfm > h5 {
628
+ /* stylelint-disable-next-line declaration-no-important */
629
+ margin: var(--toolbar-item-preview-h5-margin) !important;
630
+ }
631
+ .g-md-action-preview.yfm > h6 {
632
+ /* stylelint-disable-next-line declaration-no-important */
633
+ margin: var(--toolbar-item-preview-h6-margin) !important;
634
+ }
635
+ .g-md-action-preview__text-with-head {
636
+ margin-top: 0;
637
+ color: var(--g-color-text-hint);
638
+ }
586
639
  .g-md-markdown-hints {
587
640
  min-width: 210px;
588
641
  line-height: var(--g-text-code-1-line-height);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gravity-ui/markdown-editor",
3
- "version": "14.11.2",
3
+ "version": "14.12.0",
4
4
  "description": "Markdown wysiwyg and markup editor",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -199,7 +199,8 @@
199
199
  "react-error-boundary": "^3.1.4",
200
200
  "react-hotkeys-hook": "4.5.0",
201
201
  "react-use": "^17.3.2",
202
- "tslib": "^2.3.1"
202
+ "tslib": "^2.3.1",
203
+ "uuid": "11.0.5"
203
204
  },
204
205
  "devDependencies": {
205
206
  "@diplodoc/cut-extension": "^0.6.1",