@gravity-ui/markdown-editor 14.3.1 → 14.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/build/cjs/bundle/Editor.d.ts +2 -0
  2. package/build/cjs/bundle/Editor.js +9 -2
  3. package/build/cjs/bundle/MarkdownEditorView.js +1 -0
  4. package/build/cjs/bundle/SplitModeView.js +1 -0
  5. package/build/cjs/bundle/config/markup.d.ts +41 -17
  6. package/build/cjs/bundle/config/markup.js +413 -308
  7. package/build/cjs/bundle/config/wysiwyg.d.ts +29 -18
  8. package/build/cjs/bundle/config/wysiwyg.js +526 -310
  9. package/build/cjs/bundle/sticky/sticky.css +1 -1
  10. package/build/cjs/bundle/types.d.ts +24 -0
  11. package/build/cjs/bundle/useMarkdownEditor.js +6 -2
  12. package/build/cjs/bundle/wysiwyg-preset.d.ts +11 -2
  13. package/build/cjs/bundle/wysiwyg-preset.js +1 -0
  14. package/build/cjs/extensions/behavior/Clipboard/utils.d.ts +1 -0
  15. package/build/cjs/extensions/behavior/Clipboard/utils.js +1 -0
  16. package/build/cjs/extensions/markdown/CodeBlock/handle-paste.js +5 -17
  17. package/build/cjs/extensions/yfm/YfmCut/YfmCutSpecs/const.d.ts +4 -0
  18. package/build/cjs/extensions/yfm/YfmCut/YfmCutSpecs/const.js +6 -1
  19. package/build/cjs/extensions/yfm/YfmCut/YfmCutSpecs/index.d.ts +8 -1
  20. package/build/cjs/extensions/yfm/YfmCut/YfmCutSpecs/index.js +11 -5
  21. package/build/cjs/extensions/yfm/YfmCut/YfmCutSpecs/parser.js +5 -1
  22. package/build/cjs/extensions/yfm/YfmCut/YfmCutSpecs/schema.d.ts +1 -1
  23. package/build/cjs/extensions/yfm/YfmCut/YfmCutSpecs/schema.js +8 -3
  24. package/build/cjs/extensions/yfm/YfmCut/YfmCutSpecs/serializer.d.ts +3 -1
  25. package/build/cjs/extensions/yfm/YfmCut/YfmCutSpecs/serializer.js +42 -24
  26. package/build/cjs/extensions/yfm/YfmFile/YfmFileSpecs/const.d.ts +12 -0
  27. package/build/cjs/extensions/yfm/YfmFile/YfmFileSpecs/const.js +30 -11
  28. package/build/cjs/extensions/yfm/YfmFile/YfmFileSpecs/index.d.ts +8 -1
  29. package/build/cjs/extensions/yfm/YfmFile/YfmFileSpecs/index.js +46 -24
  30. package/build/cjs/extensions/yfm/YfmFile/index.d.ts +1 -0
  31. package/build/cjs/extensions/yfm/YfmFile/index.js +1 -0
  32. package/build/cjs/index.d.ts +1 -1
  33. package/build/cjs/index.js +2 -1
  34. package/build/cjs/markup/codemirror/create.d.ts +8 -5
  35. package/build/cjs/markup/codemirror/create.js +43 -5
  36. package/build/cjs/markup/codemirror/directive-facet.d.ts +3 -0
  37. package/build/cjs/markup/codemirror/directive-facet.js +8 -0
  38. package/build/cjs/markup/codemirror/html-to-markdown/converters.d.ts +111 -0
  39. package/build/cjs/markup/codemirror/html-to-markdown/converters.js +214 -0
  40. package/build/cjs/markup/codemirror/html-to-markdown/handlers.d.ts +104 -0
  41. package/build/cjs/markup/codemirror/html-to-markdown/handlers.js +233 -0
  42. package/build/cjs/markup/codemirror/html-to-markdown/helpers.d.ts +1 -0
  43. package/build/cjs/markup/codemirror/html-to-markdown/helpers.js +21 -0
  44. package/build/cjs/markup/codemirror/index.d.ts +1 -0
  45. package/build/cjs/markup/codemirror/index.js +3 -1
  46. package/build/cjs/markup/codemirror/yfm.d.ts +5 -0
  47. package/build/cjs/markup/codemirror/yfm.js +26 -4
  48. package/build/cjs/markup/commands/inline.js +18 -8
  49. package/build/cjs/markup/commands/yfm.js +10 -1
  50. package/build/cjs/utils/clipboard.d.ts +14 -0
  51. package/build/cjs/utils/clipboard.js +36 -1
  52. package/build/cjs/utils/directive.d.ts +28 -0
  53. package/build/cjs/utils/directive.js +55 -0
  54. package/build/cjs/utils/index.d.ts +1 -0
  55. package/build/cjs/version.js +1 -1
  56. package/build/esm/bundle/Editor.d.ts +2 -0
  57. package/build/esm/bundle/Editor.js +9 -2
  58. package/build/esm/bundle/MarkdownEditorView.js +1 -0
  59. package/build/esm/bundle/SplitModeView.js +1 -0
  60. package/build/esm/bundle/config/markup.d.ts +41 -17
  61. package/build/esm/bundle/config/markup.js +411 -307
  62. package/build/esm/bundle/config/wysiwyg.d.ts +29 -18
  63. package/build/esm/bundle/config/wysiwyg.js +499 -284
  64. package/build/esm/bundle/sticky/sticky.css +1 -1
  65. package/build/esm/bundle/types.d.ts +24 -0
  66. package/build/esm/bundle/useMarkdownEditor.js +6 -2
  67. package/build/esm/bundle/wysiwyg-preset.d.ts +11 -2
  68. package/build/esm/bundle/wysiwyg-preset.js +1 -0
  69. package/build/esm/extensions/behavior/Clipboard/utils.d.ts +1 -0
  70. package/build/esm/extensions/behavior/Clipboard/utils.js +1 -0
  71. package/build/esm/extensions/markdown/CodeBlock/handle-paste.js +2 -14
  72. package/build/esm/extensions/yfm/YfmCut/YfmCutSpecs/const.d.ts +4 -0
  73. package/build/esm/extensions/yfm/YfmCut/YfmCutSpecs/const.js +5 -0
  74. package/build/esm/extensions/yfm/YfmCut/YfmCutSpecs/index.d.ts +8 -1
  75. package/build/esm/extensions/yfm/YfmCut/YfmCutSpecs/index.js +8 -3
  76. package/build/esm/extensions/yfm/YfmCut/YfmCutSpecs/parser.js +6 -2
  77. package/build/esm/extensions/yfm/YfmCut/YfmCutSpecs/schema.d.ts +1 -1
  78. package/build/esm/extensions/yfm/YfmCut/YfmCutSpecs/schema.js +8 -3
  79. package/build/esm/extensions/yfm/YfmCut/YfmCutSpecs/serializer.d.ts +3 -1
  80. package/build/esm/extensions/yfm/YfmCut/YfmCutSpecs/serializer.js +41 -24
  81. package/build/esm/extensions/yfm/YfmFile/YfmFileSpecs/const.d.ts +12 -0
  82. package/build/esm/extensions/yfm/YfmFile/YfmFileSpecs/const.js +21 -2
  83. package/build/esm/extensions/yfm/YfmFile/YfmFileSpecs/index.d.ts +8 -1
  84. package/build/esm/extensions/yfm/YfmFile/YfmFileSpecs/index.js +32 -10
  85. package/build/esm/extensions/yfm/YfmFile/index.d.ts +1 -0
  86. package/build/esm/extensions/yfm/YfmFile/index.js +2 -1
  87. package/build/esm/index.d.ts +1 -1
  88. package/build/esm/index.js +1 -1
  89. package/build/esm/markup/codemirror/create.d.ts +8 -5
  90. package/build/esm/markup/codemirror/create.js +42 -4
  91. package/build/esm/markup/codemirror/directive-facet.d.ts +3 -0
  92. package/build/esm/markup/codemirror/directive-facet.js +5 -0
  93. package/build/esm/markup/codemirror/html-to-markdown/converters.d.ts +111 -0
  94. package/build/esm/markup/codemirror/html-to-markdown/converters.js +210 -0
  95. package/build/esm/markup/codemirror/html-to-markdown/handlers.d.ts +104 -0
  96. package/build/esm/markup/codemirror/html-to-markdown/handlers.js +215 -0
  97. package/build/esm/markup/codemirror/html-to-markdown/helpers.d.ts +1 -0
  98. package/build/esm/markup/codemirror/html-to-markdown/helpers.js +17 -0
  99. package/build/esm/markup/codemirror/index.d.ts +1 -0
  100. package/build/esm/markup/codemirror/index.js +1 -0
  101. package/build/esm/markup/codemirror/yfm.d.ts +5 -0
  102. package/build/esm/markup/codemirror/yfm.js +25 -3
  103. package/build/esm/markup/commands/inline.js +18 -8
  104. package/build/esm/markup/commands/yfm.js +9 -1
  105. package/build/esm/utils/clipboard.d.ts +14 -0
  106. package/build/esm/utils/clipboard.js +32 -0
  107. package/build/esm/utils/directive.d.ts +28 -0
  108. package/build/esm/utils/directive.js +51 -0
  109. package/build/esm/utils/index.d.ts +1 -0
  110. package/build/esm/version.js +1 -1
  111. package/build/styles.css +1 -1
  112. package/package.json +10 -6
@@ -0,0 +1,215 @@
1
+ /**
2
+ * Base handler class implementing the Chain of Responsibility pattern for HTML node processing.
3
+ * Each concrete handler decides whether it can process a node or should pass it to the next handler.
4
+ */
5
+ export class NodeHandler {
6
+ constructor() {
7
+ this.next = null;
8
+ }
9
+ /**
10
+ * Sets up the next handler in the chain
11
+ */
12
+ setNext(handler) {
13
+ this.next = handler;
14
+ return handler;
15
+ }
16
+ /**
17
+ * Delegates processing to the next handler in the chain
18
+ */
19
+ handleNext(node, visitor) {
20
+ if (this.next) {
21
+ return this.next.handle(node, visitor);
22
+ }
23
+ return '';
24
+ }
25
+ }
26
+ /**
27
+ * Handles text nodes, converting them to markdown text
28
+ */
29
+ export class TextNodeHandler extends NodeHandler {
30
+ handle(node, visitor) {
31
+ if (node.nodeType === Node.TEXT_NODE) {
32
+ return visitor.visitText(node);
33
+ }
34
+ return this.handleNext(node, visitor);
35
+ }
36
+ }
37
+ /**
38
+ * Handles anchor elements, converting them to markdown links
39
+ */
40
+ export class LinkHandler extends NodeHandler {
41
+ handle(node, visitor) {
42
+ if (node.nodeType === Node.ELEMENT_NODE &&
43
+ node.tagName.toLowerCase() === 'a') {
44
+ return visitor.visitLink(node);
45
+ }
46
+ return this.handleNext(node, visitor);
47
+ }
48
+ }
49
+ /**
50
+ * Handles header elements (h1-h6), converting them to markdown headers
51
+ */
52
+ export class HeaderHandler extends NodeHandler {
53
+ handle(node, visitor) {
54
+ if (node.nodeType !== Node.ELEMENT_NODE) {
55
+ return this.handleNext(node, visitor);
56
+ }
57
+ const tagName = node.tagName.toLowerCase();
58
+ const headerMatch = tagName.match(/^h([1-6])$/);
59
+ if (headerMatch) {
60
+ const headerLevel = parseInt(headerMatch[1], 10);
61
+ return visitor.visitHeader(node, headerLevel);
62
+ }
63
+ return this.handleNext(node, visitor);
64
+ }
65
+ }
66
+ /**
67
+ * Handles paragraph elements, converting them to markdown paragraphs
68
+ */
69
+ export class ParagraphHandler extends NodeHandler {
70
+ handle(node, visitor) {
71
+ if (node.nodeType !== Node.ELEMENT_NODE) {
72
+ return this.handleNext(node, visitor);
73
+ }
74
+ if (node.tagName.toLowerCase() === 'p') {
75
+ return visitor.visitParagraph(node);
76
+ }
77
+ return this.handleNext(node, visitor);
78
+ }
79
+ }
80
+ /**
81
+ * Handles text formatting elements (b, strong, i, em), converting them to markdown formatting
82
+ */
83
+ export class FormattingHandler extends NodeHandler {
84
+ handle(node, visitor) {
85
+ var _a;
86
+ if (node.nodeType !== Node.ELEMENT_NODE) {
87
+ return this.handleNext(node, visitor);
88
+ }
89
+ const element = node;
90
+ const tagName = element.tagName.toLowerCase();
91
+ const formattingTags = ['b', 'strong', 'i', 'em', 'span'];
92
+ if (formattingTags.includes(tagName) &&
93
+ !['a', 'code', 'pre'].includes(((_a = element.parentElement) === null || _a === void 0 ? void 0 : _a.tagName.toLowerCase()) || '')) {
94
+ return visitor.visitFormatting(element);
95
+ }
96
+ return this.handleNext(node, visitor);
97
+ }
98
+ }
99
+ /**
100
+ * Handles code elements, converting them to markdown code blocks or inline code
101
+ */
102
+ export class CodeHandler extends NodeHandler {
103
+ handle(node, visitor) {
104
+ if (node.nodeType !== Node.ELEMENT_NODE) {
105
+ return this.handleNext(node, visitor);
106
+ }
107
+ if (node.tagName.toLowerCase() === 'code' ||
108
+ node.tagName.toLowerCase() === 'pre') {
109
+ return visitor.visitCode(node);
110
+ }
111
+ return this.handleNext(node, visitor);
112
+ }
113
+ }
114
+ /**
115
+ * Fallback handler for any HTML elements not handled by other specific handlers
116
+ */
117
+ export class GenericHandler extends NodeHandler {
118
+ handle(node, visitor) {
119
+ return visitor.visitGeneric(node);
120
+ }
121
+ }
122
+ /**
123
+ * Handles ordered list elements, converting them to markdown ordered lists
124
+ */
125
+ export class OrderedListHandler extends NodeHandler {
126
+ handle(node, visitor) {
127
+ if (node.nodeType === Node.ELEMENT_NODE &&
128
+ node.tagName.toLowerCase() === 'ol') {
129
+ const items = Array.from(node.childNodes)
130
+ .filter((child) => child.nodeType === Node.ELEMENT_NODE &&
131
+ child.tagName.toLowerCase() === 'li')
132
+ .map((item, index) => `${index + 1}. ${visitor.visitGeneric(item)}`)
133
+ .join('\n');
134
+ return items + '\n';
135
+ }
136
+ return this.handleNext(node, visitor);
137
+ }
138
+ }
139
+ /**
140
+ * Handles unordered list elements, converting them to markdown unordered lists
141
+ */
142
+ export class UnorderedListHandler extends NodeHandler {
143
+ handle(node, visitor) {
144
+ if (node.nodeType === Node.ELEMENT_NODE &&
145
+ node.tagName.toLowerCase() === 'ul') {
146
+ const items = Array.from(node.childNodes)
147
+ .filter((child) => child.nodeType === Node.ELEMENT_NODE &&
148
+ child.tagName.toLowerCase() === 'li')
149
+ .map((item) => `- ${visitor.visitGeneric(item)}`)
150
+ .join('\n');
151
+ return items + '\n';
152
+ }
153
+ return this.handleNext(node, visitor);
154
+ }
155
+ }
156
+ /**
157
+ * Handles div elements, converting them to markdown paragraphs
158
+ */
159
+ export class DivHandler extends NodeHandler {
160
+ handle(node, visitor) {
161
+ if (node.nodeType === Node.ELEMENT_NODE &&
162
+ node.tagName.toLowerCase() === 'div') {
163
+ return visitor.visitDiv(node);
164
+ }
165
+ return this.handleNext(node, visitor);
166
+ }
167
+ }
168
+ /**
169
+ * Handles br elements, converting them to markdown newlines
170
+ */
171
+ export class BrHandler extends NodeHandler {
172
+ handle(node, visitor) {
173
+ if (node.nodeType === Node.ELEMENT_NODE &&
174
+ node.tagName.toLowerCase() === 'br') {
175
+ return visitor.visitBr();
176
+ }
177
+ return this.handleNext(node, visitor);
178
+ }
179
+ }
180
+ /**
181
+ * Handles table row elements, converting them to markdown table rows
182
+ */
183
+ export class TableRowHandler extends NodeHandler {
184
+ handle(node, visitor) {
185
+ if (node.nodeType === Node.ELEMENT_NODE &&
186
+ node.tagName.toLowerCase() === 'tr') {
187
+ return visitor.visitTableRow(node);
188
+ }
189
+ return this.handleNext(node, visitor);
190
+ }
191
+ }
192
+ /**
193
+ * Handles table elements, converting them to markdown tables
194
+ */
195
+ export class TableHandler extends NodeHandler {
196
+ handle(node, visitor) {
197
+ if (node.nodeType === Node.ELEMENT_NODE &&
198
+ node.tagName.toLowerCase() === 'table') {
199
+ return visitor.visitTable(node);
200
+ }
201
+ return this.handleNext(node, visitor);
202
+ }
203
+ }
204
+ /**
205
+ * Handles image elements, converting them to markdown images
206
+ */
207
+ export class ImageHandler extends NodeHandler {
208
+ handle(node, visitor) {
209
+ if (node.nodeType === Node.ELEMENT_NODE &&
210
+ node.tagName.toLowerCase() === 'img') {
211
+ return visitor.visitImage(node);
212
+ }
213
+ return this.handleNext(node, visitor);
214
+ }
215
+ }
@@ -0,0 +1 @@
1
+ export declare function applyFormatting(text: string, element: HTMLElement): string;
@@ -0,0 +1,17 @@
1
+ export function applyFormatting(text, element) {
2
+ // Check for italic formatting (either through tags or CSS)
3
+ const hasItalic = element.tagName.toLowerCase() === 'i' ||
4
+ element.tagName.toLowerCase() === 'em' ||
5
+ element.style.fontStyle === 'italic';
6
+ // Check for bold formatting (either through tags or CSS font-weight)
7
+ const hasBold = element.tagName.toLowerCase() === 'b' ||
8
+ element.tagName.toLowerCase() === 'strong' ||
9
+ parseInt(element.style.fontWeight, 10) >= 600;
10
+ // Apply markdown formatting in specific order
11
+ let formatted = text;
12
+ if (hasItalic)
13
+ formatted = `*${formatted}*`; // Wrap in single asterisks for italic
14
+ if (hasBold)
15
+ formatted = `**${formatted}**`; // Wrap in double asterisks for bold
16
+ return formatted;
17
+ }
@@ -1,5 +1,6 @@
1
1
  export type { CreateCodemirrorParams } from './create';
2
2
  export { createCodemirror } from './create';
3
3
  export { ReactRendererFacet } from './react-facet';
4
+ export { DirectiveSyntaxFacet } from './directive-facet';
4
5
  export { getImageDimensions, IMG_MAX_HEIGHT } from './files-upload-plugin';
5
6
  export type { YfmLangOptions } from './yfm';
@@ -1,3 +1,4 @@
1
1
  export { createCodemirror } from './create';
2
2
  export { ReactRendererFacet } from './react-facet';
3
+ export { DirectiveSyntaxFacet } from './directive-facet';
3
4
  export { getImageDimensions, IMG_MAX_HEIGHT } from './files-upload-plugin';
@@ -15,6 +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 const yfmCutDirectiveSnippetTemplate = ":::cut [#{title}]\n#{}\n:::\n\n";
19
+ export declare const yfmCutDirectiveSnippet: (editor: {
20
+ state: import("@codemirror/state").EditorState;
21
+ dispatch: (tr: import("@codemirror/state").Transaction) => void;
22
+ }, completion: Completion | null, from: number, to: number) => void;
18
23
  export interface LanguageData {
19
24
  autocomplete: CompletionSource;
20
25
  [key: string]: any;
@@ -2,6 +2,7 @@ import { snippet } from '@codemirror/autocomplete';
2
2
  import { markdown, markdownLanguage } from '@codemirror/lang-markdown';
3
3
  import { Tag, tags } from '@lezer/highlight';
4
4
  import { capitalize } from '../../lodash';
5
+ import { DirectiveSyntaxFacet } from './directive-facet';
5
6
  export const customTags = {
6
7
  underline: Tag.define(),
7
8
  monospace: Tag.define(),
@@ -54,8 +55,11 @@ export const yfmNoteSnippets = {
54
55
  };
55
56
  export const yfmCutSnippetTemplate = '{% cut "#{title}" %}\n\n#{}\n\n{% endcut %}\n\n';
56
57
  export const yfmCutSnippet = snippet(yfmCutSnippetTemplate);
58
+ export const yfmCutDirectiveSnippetTemplate = ':::cut [#{title}]\n#{}\n:::\n\n';
59
+ export const yfmCutDirectiveSnippet = snippet(yfmCutDirectiveSnippetTemplate);
57
60
  const mdAutocomplete = {
58
61
  autocomplete: (context) => {
62
+ const directiveContext = context.state.facet(DirectiveSyntaxFacet);
59
63
  // TODO: add more actions and re-enable
60
64
  // let word = context.matchBefore(/\/.*/);
61
65
  // if (word) {
@@ -73,13 +77,15 @@ const mdAutocomplete = {
73
77
  // label: '/yfm cut',
74
78
  // displayLabel: 'YFM Cut',
75
79
  // type: 'text',
76
- // apply: yfmCutSnippet,
80
+ // apply: directiveFacet.shouldInsertDirectiveMarkup('yfmCut')
81
+ // ? yfmCutDirectiveSnippet
82
+ // : yfmCutSnippet,
77
83
  // },
78
84
  // ],
79
85
  // };
80
86
  // }
81
87
  const word = context.matchBefore(/^.*/);
82
- if (word === null || word === void 0 ? void 0 : word.text.startsWith('{%')) {
88
+ if (directiveContext.option !== 'only' && (word === null || word === void 0 ? void 0 : word.text.startsWith('{%'))) {
83
89
  return {
84
90
  from: word.from,
85
91
  options: [
@@ -95,11 +101,27 @@ const mdAutocomplete = {
95
101
  label: '{% cut',
96
102
  displayLabel: 'YFM Cut',
97
103
  type: 'text',
98
- apply: yfmCutSnippet,
104
+ apply: directiveContext.shouldInsertDirectiveMarkup('yfmCut')
105
+ ? yfmCutDirectiveSnippet
106
+ : yfmCutSnippet,
99
107
  },
100
108
  ],
101
109
  };
102
110
  }
111
+ if (directiveContext.option !== 'disabled' && (word === null || word === void 0 ? void 0 : word.text.startsWith(':'))) {
112
+ const options = [];
113
+ if (directiveContext.valueFor('yfmCut') !== 'disabled') {
114
+ options.push({
115
+ label: ':::cut',
116
+ displayLabel: 'YFM Cut',
117
+ type: 'text',
118
+ apply: yfmCutDirectiveSnippet,
119
+ });
120
+ }
121
+ if (options.length) {
122
+ return { from: word.from, options };
123
+ }
124
+ }
103
125
  return null;
104
126
  },
105
127
  };
@@ -1,4 +1,5 @@
1
1
  import { snippet } from '@codemirror/autocomplete';
2
+ import { DirectiveSyntaxFacet } from '../codemirror';
2
3
  const defaultLinkSnippet = snippet(`[#{2:link}](#{1:url} "#{3:title}")`);
3
4
  export const insertLink = ({ state, dispatch }) => {
4
5
  const { from, to, empty } = state.selection.main;
@@ -41,16 +42,25 @@ export function insertImages(images) {
41
42
  return true;
42
43
  };
43
44
  }
45
+ const fileToCurlySyntax = (file) => {
46
+ const attrsStr = Object.entries(file)
47
+ .map(([key, value]) => `${key}="${value.replace('"', '')}"`)
48
+ .join(' ');
49
+ return `{% file ${attrsStr} %}`;
50
+ };
51
+ const fileToDirectiveSyntax = (file) => {
52
+ const { src, name, type } = file;
53
+ let markup = `:file[${name}](${src})`;
54
+ if (type)
55
+ markup += `{type="${type}"}`;
56
+ return markup;
57
+ };
44
58
  export const insertFiles = (files) => {
45
59
  return ({ state, dispatch }) => {
46
- const markup = files
47
- .map((attrs) => {
48
- const attrsStr = Object.entries(attrs)
49
- .map(([key, value]) => `${key}="${value.replace('"', '')}"`)
50
- .join(' ');
51
- return `{% file ${attrsStr} %}`;
52
- })
53
- .join(' ');
60
+ const serializer = state.facet(DirectiveSyntaxFacet).shouldInsertDirectiveMarkup('yfmFile')
61
+ ? fileToDirectiveSyntax
62
+ : fileToCurlySyntax;
63
+ const markup = files.map(serializer).join(' ');
54
64
  const tr = state.changeByRange((range) => {
55
65
  const changes = state.changes({ from: range.from, to: range.to, insert: markup });
56
66
  return { changes, range: range.map(changes) };
@@ -1,5 +1,13 @@
1
+ import { DirectiveSyntaxFacet } from '../codemirror/directive-facet';
1
2
  import { wrapToBlock } from './helpers';
2
- export const wrapToYfmCut = wrapToBlock(({ lineBreak }) => '{% cut "title" %}' + lineBreak.repeat(2), ({ lineBreak }) => lineBreak.repeat(2) + '{% endcut %}');
3
+ const wrapToYfmCutCurly = wrapToBlock(({ lineBreak }) => '{% cut "title" %}' + lineBreak.repeat(2), ({ lineBreak }) => lineBreak.repeat(2) + '{% endcut %}');
4
+ const wrapToYfmCutDirective = wrapToBlock(({ lineBreak }) => ':::cut [title]' + lineBreak, ({ lineBreak }) => lineBreak + ':::');
5
+ export const wrapToYfmCut = (target) => {
6
+ const cmd = target.state.facet(DirectiveSyntaxFacet).shouldInsertDirectiveMarkup('yfmCut')
7
+ ? wrapToYfmCutDirective
8
+ : wrapToYfmCutCurly;
9
+ return cmd(target);
10
+ };
3
11
  export const wrapToYfmNote = wrapToBlock(({ lineBreak }) => '{% note info %}' + lineBreak.repeat(2), ({ lineBreak }) => lineBreak.repeat(2) + '{% endnote %}');
4
12
  export const insertYfmTabs = wrapToBlock(({ lineBreak }) => '{% list tabs %}' + lineBreak.repeat(2) + '- Tab name' + lineBreak.repeat(2), ({ lineBreak }) => lineBreak.repeat(2) + '{% endlist %}', { before: ' ', after: '' });
5
13
  export const insertYfmTable = wrapToBlock(({ lineBreak }) => ['#|', '||'].join(lineBreak) + lineBreak, ({ lineBreak }) => lineBreak + ['|', '', '||', '||', '', '|', '', '||', '|#', ''].join(lineBreak));
@@ -3,6 +3,7 @@ export declare enum DataTransferType {
3
3
  Text = "text/plain",
4
4
  Html = "text/html",
5
5
  Yfm = "text/yfm",
6
+ Rtf = "text/rtf",
6
7
  UriList = "text/uri-list",
7
8
  VSCodeData = "vscode-editor-data",
8
9
  Files = "Files"
@@ -10,3 +11,16 @@ export declare enum DataTransferType {
10
11
  export declare function isFilesOnly({ types }: DataTransfer): boolean;
11
12
  export declare function isFilesFromHtml({ types }: DataTransfer): boolean;
12
13
  export declare function isImageFile(file: File): boolean;
14
+ export declare function isVSCode(data: DataTransfer): boolean;
15
+ export declare type VSCodeData = {
16
+ version: number;
17
+ isFromEmptySelection: boolean;
18
+ multicursorText: null | string;
19
+ mode: string;
20
+ [key: string]: unknown;
21
+ };
22
+ export declare function tryParseVSCodeData(data: DataTransfer): VSCodeData | undefined;
23
+ /**
24
+ * Checks if HTML conversion should be skipped based on clipboard contents.
25
+ */
26
+ export declare function shouldSkipHtmlConversion(clipboardData: DataTransfer): boolean;
@@ -4,6 +4,7 @@ export var DataTransferType;
4
4
  DataTransferType["Text"] = "text/plain";
5
5
  DataTransferType["Html"] = "text/html";
6
6
  DataTransferType["Yfm"] = "text/yfm";
7
+ DataTransferType["Rtf"] = "text/rtf";
7
8
  DataTransferType["UriList"] = "text/uri-list";
8
9
  DataTransferType["VSCodeData"] = "vscode-editor-data";
9
10
  DataTransferType["Files"] = "Files";
@@ -19,3 +20,34 @@ export function isFilesFromHtml({ types }) {
19
20
  export function isImageFile(file) {
20
21
  return file.type.startsWith('image/');
21
22
  }
23
+ export function isVSCode(data) {
24
+ return data.types.includes(DataTransferType.VSCodeData);
25
+ }
26
+ export function tryParseVSCodeData(data) {
27
+ try {
28
+ return JSON.parse(data.getData(DataTransferType.VSCodeData));
29
+ }
30
+ catch (e) {
31
+ console.error(e);
32
+ return undefined;
33
+ }
34
+ }
35
+ /**
36
+ * Checks if HTML conversion should be skipped based on clipboard contents.
37
+ */
38
+ export function shouldSkipHtmlConversion(clipboardData) {
39
+ const hasHtml = clipboardData.types.includes(DataTransferType.Html);
40
+ // If there's no HTML content, skip conversion
41
+ if (!hasHtml)
42
+ return true;
43
+ // Check for standard HTML clipboard (text/plain + text/html)
44
+ if (clipboardData.types.length === 2)
45
+ return false;
46
+ // Check for WebStorm/Safari case (includes RTF)
47
+ if (clipboardData.types.length === 3) {
48
+ const rtf = clipboardData.getData(DataTransferType.Rtf);
49
+ return rtf.indexOf('\fmodern JetBrains') > 0;
50
+ }
51
+ // Skip conversion for any other cases
52
+ return true;
53
+ }
@@ -0,0 +1,28 @@
1
+ export declare type DirectiveSyntaxValue = 'disabled' | 'enabled' | 'preserve' | 'overwrite' | 'only';
2
+ declare type DirectiveSyntaxMdPluginValue = 'disabled' | 'enabled' | 'only';
3
+ export declare type DirectiveSyntaxOption = DirectiveSyntaxValue | DirectiveSyntaxOptionObj;
4
+ declare type DirectiveSyntaxOptionObj = {
5
+ [K in keyof MarkdownEditor.DirectiveSyntaxAdditionalSupportedExtensions]?: DirectiveSyntaxValue;
6
+ };
7
+ declare global {
8
+ namespace MarkdownEditor {
9
+ /**
10
+ * Add more keys for you additional supported extensions
11
+ */
12
+ interface DirectiveSyntaxAdditionalSupportedExtensions {
13
+ }
14
+ }
15
+ }
16
+ export declare class DirectiveSyntaxContext {
17
+ #private;
18
+ protected set option(value: DirectiveSyntaxOption | undefined);
19
+ get option(): DirectiveSyntaxOption;
20
+ constructor(option: DirectiveSyntaxOption | undefined);
21
+ valueFor(key: keyof DirectiveSyntaxOptionObj): DirectiveSyntaxValue;
22
+ mdPluginValueFor(key: keyof DirectiveSyntaxOptionObj): DirectiveSyntaxMdPluginValue;
23
+ /** helper for wisywig serializer */
24
+ shouldSerializeToDirective(key: keyof DirectiveSyntaxOptionObj, tokenMarkup: unknown): boolean;
25
+ /** helper for markup-mode commands and actions */
26
+ shouldInsertDirectiveMarkup(key: keyof DirectiveSyntaxOptionObj): boolean;
27
+ }
28
+ export {};
@@ -0,0 +1,51 @@
1
+ var _DirectiveSyntaxContext_option;
2
+ import { __classPrivateFieldGet, __classPrivateFieldSet } from "tslib";
3
+ const DIRECTIVE_SYNTAX_DEFAULT = 'disabled';
4
+ export class DirectiveSyntaxContext {
5
+ constructor(option) {
6
+ _DirectiveSyntaxContext_option.set(this, void 0);
7
+ this.option = option;
8
+ __classPrivateFieldSet(this, _DirectiveSyntaxContext_option, this.option, "f");
9
+ }
10
+ set option(value) {
11
+ __classPrivateFieldSet(this, _DirectiveSyntaxContext_option, value !== null && value !== void 0 ? value : DIRECTIVE_SYNTAX_DEFAULT, "f");
12
+ }
13
+ get option() {
14
+ return __classPrivateFieldGet(this, _DirectiveSyntaxContext_option, "f");
15
+ }
16
+ valueFor(key) {
17
+ let value;
18
+ if (typeof this.option === 'object')
19
+ value = this.option[key];
20
+ if (typeof this.option === 'string')
21
+ value = this.option;
22
+ return value !== null && value !== void 0 ? value : DIRECTIVE_SYNTAX_DEFAULT;
23
+ }
24
+ mdPluginValueFor(key) {
25
+ const value = this.valueFor(key);
26
+ return value === 'preserve' || value === 'overwrite' ? 'enabled' : value;
27
+ }
28
+ /** helper for wisywig serializer */
29
+ shouldSerializeToDirective(key, tokenMarkup) {
30
+ const option = this.valueFor(key);
31
+ if (option === 'overwrite' || option === 'only')
32
+ return true;
33
+ if (typeof tokenMarkup === 'string') {
34
+ if (tokenMarkup.startsWith(':'))
35
+ return true;
36
+ if (tokenMarkup.startsWith('{'))
37
+ return false;
38
+ }
39
+ if (option === 'preserve')
40
+ return true;
41
+ return false;
42
+ }
43
+ /** helper for markup-mode commands and actions */
44
+ shouldInsertDirectiveMarkup(key) {
45
+ const value = this.valueFor(key);
46
+ if (value === 'disabled' || value === 'enabled')
47
+ return false;
48
+ return true;
49
+ }
50
+ }
51
+ _DirectiveSyntaxContext_option = new WeakMap();
@@ -19,3 +19,4 @@ export * from './serialize-for-clipboard';
19
19
  export * from './sync-scroll';
20
20
  export * from './upload';
21
21
  export * from './get-proportional-size';
22
+ export type { DirectiveSyntaxValue, DirectiveSyntaxOption } from './directive';
@@ -1,2 +1,2 @@
1
1
  /** During build process, the current version will be injected here */
2
- export const VERSION = typeof '14.3.1' !== 'undefined' ? '14.3.1' : 'unknown';
2
+ export const VERSION = typeof '14.5.0' !== 'undefined' ? '14.5.0' : 'unknown';
package/build/styles.css CHANGED
@@ -455,7 +455,7 @@
455
455
  position: absolute;
456
456
  inset: var(--g-md-toolbar-sticky-inset, -4px);
457
457
  content: "";
458
- border: 1px solid var(--g-color-line-generic-solid);
458
+ border: var(--g-md-toolbar-sticky-border, 1px solid var(--g-color-line-generic-solid));
459
459
  border-radius: 4px;
460
460
  background-color: var(--g-color-base-background);
461
461
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gravity-ui/markdown-editor",
3
- "version": "14.3.1",
3
+ "version": "14.5.0",
4
4
  "description": "Markdown wysiwyg and markup editor",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -166,6 +166,7 @@
166
166
  "@codemirror/search": "~6.5.6",
167
167
  "@codemirror/state": "~6.4.1",
168
168
  "@codemirror/view": "~6.34.1",
169
+ "@diplodoc/file-extension": "^0.2.1",
169
170
  "@gravity-ui/i18n": "^1.1.0",
170
171
  "@gravity-ui/icons": "^2.10.0",
171
172
  "@lezer/highlight": "~1.2.1",
@@ -200,13 +201,13 @@
200
201
  "tslib": "^2.3.1"
201
202
  },
202
203
  "devDependencies": {
203
- "@diplodoc/cut-extension": "^0.3.1",
204
+ "@diplodoc/cut-extension": "^0.5.0",
204
205
  "@diplodoc/folding-headings-extension": "0.1.0",
205
- "@diplodoc/html-extension": "2.3.2",
206
+ "@diplodoc/html-extension": "^2.5.0",
206
207
  "@diplodoc/latex-extension": "1.0.3",
207
208
  "@diplodoc/mermaid-extension": "1.2.1",
208
209
  "@diplodoc/tabs-extension": "^3.5.1",
209
- "@diplodoc/transform": "^4.36.0",
210
+ "@diplodoc/transform": "^4.40.0",
210
211
  "@gravity-ui/components": "3.0.0",
211
212
  "@gravity-ui/eslint-config": "3.1.1",
212
213
  "@gravity-ui/prettier-config": "1.1.0",
@@ -225,6 +226,7 @@
225
226
  "@types/gulp": "4.0.9",
226
227
  "@types/gulp-sass": "5.0.0",
227
228
  "@types/jest": "^27.0.3",
229
+ "@types/jsdom": "21.1.7",
228
230
  "@types/katex": "0.16.7",
229
231
  "@types/lodash": "^4.14.177",
230
232
  "@types/markdown-it-emoji": "2.0.2",
@@ -245,6 +247,7 @@
245
247
  "identity-obj-proxy": "^3.0.0",
246
248
  "jest": "^27.3.1",
247
249
  "jest-css-modules": "^2.1.0",
250
+ "jsdom": "25.0.1",
248
251
  "lowlight": "3.0.0",
249
252
  "markdown-it-testgen": "^0.1.6",
250
253
  "mermaid": "10.9.0",
@@ -258,6 +261,7 @@
258
261
  "sass": "^1.64.1",
259
262
  "sass-loader": "^13.3.2",
260
263
  "stylelint": "15.11.0",
264
+ "ts-dedent": "2.2.0",
261
265
  "ts-jest": "^27.0.7",
262
266
  "typescript": "^4.5.2"
263
267
  },
@@ -282,9 +286,9 @@
282
286
  }
283
287
  },
284
288
  "peerDependencies": {
285
- "@diplodoc/cut-extension": "^0.3.1",
289
+ "@diplodoc/cut-extension": "^0.3.1 || ^0.4.0 || ^0.5.0",
286
290
  "@diplodoc/folding-headings-extension": "^0.1.0",
287
- "@diplodoc/html-extension": "2.3.2",
291
+ "@diplodoc/html-extension": "^2.3.2",
288
292
  "@diplodoc/latex-extension": "^1.0.3",
289
293
  "@diplodoc/mermaid-extension": "^1.0.0",
290
294
  "@diplodoc/tabs-extension": "^3.5.1",