@gravity-ui/markdown-editor 14.8.0 → 14.10.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 (166) hide show
  1. package/README.md +16 -14
  2. package/build/cjs/bundle/Editor.d.ts +2 -0
  3. package/build/cjs/bundle/Editor.js +15 -4
  4. package/build/cjs/bundle/MarkdownEditorView.d.ts +27 -0
  5. package/build/cjs/bundle/MarkdownEditorView.js +19 -2
  6. package/build/cjs/bundle/config/action-names.d.ts +1 -1
  7. package/build/cjs/bundle/config/action-names.js +51 -27
  8. package/build/cjs/bundle/config/index.d.ts +3 -0
  9. package/build/cjs/bundle/config/index.js +3 -0
  10. package/build/cjs/bundle/config/markup.js +3 -0
  11. package/build/cjs/bundle/config/wysiwyg.d.ts +3 -0
  12. package/build/cjs/bundle/toolbar/utils/flattenPreset.d.ts +2 -0
  13. package/build/cjs/bundle/toolbar/utils/flattenPreset.js +14 -0
  14. package/build/cjs/bundle/toolbar/utils/toolbarsConfigs.d.ts +17 -0
  15. package/build/cjs/bundle/toolbar/utils/toolbarsConfigs.js +60 -0
  16. package/build/cjs/bundle/types.d.ts +6 -0
  17. package/build/cjs/bundle/useMarkdownEditor.js +8 -2
  18. package/build/cjs/bundle/wysiwyg-preset.d.ts +1 -0
  19. package/build/cjs/bundle/wysiwyg-preset.js +1 -1
  20. package/build/cjs/core/Editor.d.ts +3 -1
  21. package/build/cjs/core/Editor.js +2 -1
  22. package/build/cjs/core/ExtensionsManager.d.ts +2 -0
  23. package/build/cjs/core/ExtensionsManager.js +8 -4
  24. package/build/cjs/core/ParserTokensRegistry.d.ts +2 -1
  25. package/build/cjs/core/ParserTokensRegistry.js +2 -2
  26. package/build/cjs/core/markdown/MarkdownParser.d.ts +3 -1
  27. package/build/cjs/core/markdown/MarkdownParser.js +5 -2
  28. package/build/cjs/core/markdown/MarkdownSerializer.js +1 -1
  29. package/build/cjs/core/markdown/ProseMirrorTransformer/emptyRowTransformer.d.ts +2 -0
  30. package/build/cjs/core/markdown/ProseMirrorTransformer/emptyRowTransformer.js +15 -0
  31. package/build/cjs/core/markdown/ProseMirrorTransformer/getTransformers.d.ts +7 -0
  32. package/build/cjs/core/markdown/ProseMirrorTransformer/getTransformers.js +13 -0
  33. package/build/cjs/core/markdown/ProseMirrorTransformer/index.d.ts +15 -0
  34. package/build/cjs/core/markdown/ProseMirrorTransformer/index.js +25 -0
  35. package/build/cjs/extensions/additional/Math/MathSpecs/index.js +1 -0
  36. package/build/cjs/extensions/base/BaseSchema/BaseSchemaSpecs/index.d.ts +1 -0
  37. package/build/cjs/extensions/base/BaseSchema/BaseSchemaSpecs/index.js +23 -3
  38. package/build/cjs/extensions/behavior/Selection/selection.js +1 -5
  39. package/build/cjs/extensions/markdown/Blockquote/BlockquoteSpecs/index.js +1 -0
  40. package/build/cjs/extensions/markdown/CodeBlock/commands.js +1 -1
  41. package/build/cjs/extensions/markdown/HorizontalRule/HorizontalRuleSpecs/index.js +1 -0
  42. package/build/cjs/extensions/markdown/Link/paste-plugin.js +21 -5
  43. package/build/cjs/extensions/yfm/YfmTabs/YfmTabsSpecs/schema.js +1 -0
  44. package/build/cjs/i18n/empty-row/en.json +3 -0
  45. package/build/cjs/i18n/empty-row/index.d.ts +5 -0
  46. package/build/cjs/i18n/empty-row/index.js +9 -0
  47. package/build/cjs/i18n/empty-row/ru.json +3 -0
  48. package/build/cjs/i18n/menubar/en.json +1 -0
  49. package/build/cjs/i18n/menubar/index.d.ts +2 -1
  50. package/build/cjs/i18n/menubar/ru.json +1 -0
  51. package/build/cjs/i18n/yfm-note/index.d.ts +1 -1
  52. package/build/cjs/index.d.ts +1 -0
  53. package/build/cjs/index.js +1 -0
  54. package/build/cjs/markup/codemirror/autocomplete/emptyRow.d.ts +9 -0
  55. package/build/cjs/markup/codemirror/autocomplete/emptyRow.js +26 -0
  56. package/build/cjs/markup/codemirror/autocomplete/index.d.ts +5 -0
  57. package/build/cjs/markup/codemirror/autocomplete/index.js +14 -0
  58. package/build/cjs/markup/codemirror/create.d.ts +1 -0
  59. package/build/cjs/markup/codemirror/create.js +30 -12
  60. package/build/cjs/markup/codemirror/smart-reindent/index.d.ts +4 -0
  61. package/build/cjs/markup/codemirror/smart-reindent/index.js +42 -0
  62. package/build/cjs/markup/codemirror/smart-reindent/utils.d.ts +15 -0
  63. package/build/cjs/markup/codemirror/smart-reindent/utils.js +59 -0
  64. package/build/cjs/markup/codemirror/yfm.d.ts +2 -1
  65. package/build/cjs/markup/codemirror/yfm.js +3 -3
  66. package/build/cjs/markup/commands/emptyRow.d.ts +2 -0
  67. package/build/cjs/markup/commands/emptyRow.js +43 -0
  68. package/build/cjs/markup/commands/index.d.ts +1 -0
  69. package/build/cjs/markup/commands/index.js +1 -0
  70. package/build/cjs/modules/toolbars/constants.d.ts +13 -0
  71. package/build/cjs/modules/toolbars/constants.js +18 -0
  72. package/build/cjs/modules/toolbars/items.d.ts +127 -0
  73. package/build/cjs/modules/toolbars/items.js +736 -0
  74. package/build/cjs/modules/toolbars/presets.d.ts +6 -0
  75. package/build/cjs/modules/toolbars/presets.js +465 -0
  76. package/build/cjs/modules/toolbars/types.d.ts +62 -0
  77. package/build/cjs/modules/toolbars/types.js +2 -0
  78. package/build/cjs/shortcuts/const.d.ts +1 -0
  79. package/build/cjs/shortcuts/const.js +1 -0
  80. package/build/cjs/shortcuts/default.js +1 -0
  81. package/build/cjs/toolbar/types.d.ts +8 -0
  82. package/build/cjs/toolbar/types.js +2 -0
  83. package/build/cjs/version.js +1 -1
  84. package/build/esm/bundle/Editor.d.ts +2 -0
  85. package/build/esm/bundle/Editor.js +15 -4
  86. package/build/esm/bundle/MarkdownEditorView.d.ts +27 -0
  87. package/build/esm/bundle/MarkdownEditorView.js +19 -2
  88. package/build/esm/bundle/config/action-names.d.ts +1 -1
  89. package/build/esm/bundle/config/action-names.js +51 -27
  90. package/build/esm/bundle/config/index.d.ts +3 -0
  91. package/build/esm/bundle/config/index.js +3 -0
  92. package/build/esm/bundle/config/markup.js +3 -0
  93. package/build/esm/bundle/config/wysiwyg.d.ts +3 -0
  94. package/build/esm/bundle/toolbar/utils/flattenPreset.d.ts +2 -0
  95. package/build/esm/bundle/toolbar/utils/flattenPreset.js +10 -0
  96. package/build/esm/bundle/toolbar/utils/toolbarsConfigs.d.ts +17 -0
  97. package/build/esm/bundle/toolbar/utils/toolbarsConfigs.js +55 -0
  98. package/build/esm/bundle/types.d.ts +6 -0
  99. package/build/esm/bundle/useMarkdownEditor.js +8 -2
  100. package/build/esm/bundle/wysiwyg-preset.d.ts +1 -0
  101. package/build/esm/bundle/wysiwyg-preset.js +1 -1
  102. package/build/esm/core/Editor.d.ts +3 -1
  103. package/build/esm/core/Editor.js +2 -1
  104. package/build/esm/core/ExtensionsManager.d.ts +2 -0
  105. package/build/esm/core/ExtensionsManager.js +8 -4
  106. package/build/esm/core/ParserTokensRegistry.d.ts +2 -1
  107. package/build/esm/core/ParserTokensRegistry.js +2 -2
  108. package/build/esm/core/markdown/MarkdownParser.d.ts +3 -1
  109. package/build/esm/core/markdown/MarkdownParser.js +5 -2
  110. package/build/esm/core/markdown/MarkdownSerializer.js +1 -1
  111. package/build/esm/core/markdown/ProseMirrorTransformer/emptyRowTransformer.d.ts +2 -0
  112. package/build/esm/core/markdown/ProseMirrorTransformer/emptyRowTransformer.js +11 -0
  113. package/build/esm/core/markdown/ProseMirrorTransformer/getTransformers.d.ts +7 -0
  114. package/build/esm/core/markdown/ProseMirrorTransformer/getTransformers.js +9 -0
  115. package/build/esm/core/markdown/ProseMirrorTransformer/index.d.ts +15 -0
  116. package/build/esm/core/markdown/ProseMirrorTransformer/index.js +21 -0
  117. package/build/esm/extensions/additional/Math/MathSpecs/index.js +1 -0
  118. package/build/esm/extensions/base/BaseSchema/BaseSchemaSpecs/index.d.ts +1 -0
  119. package/build/esm/extensions/base/BaseSchema/BaseSchemaSpecs/index.js +23 -3
  120. package/build/esm/extensions/behavior/Selection/selection.js +1 -5
  121. package/build/esm/extensions/markdown/Blockquote/BlockquoteSpecs/index.js +1 -0
  122. package/build/esm/extensions/markdown/CodeBlock/commands.js +1 -1
  123. package/build/esm/extensions/markdown/HorizontalRule/HorizontalRuleSpecs/index.js +1 -0
  124. package/build/esm/extensions/markdown/Link/paste-plugin.js +21 -5
  125. package/build/esm/extensions/yfm/YfmTabs/YfmTabsSpecs/schema.js +1 -0
  126. package/build/esm/i18n/empty-row/en.json +3 -0
  127. package/build/esm/i18n/empty-row/index.d.ts +5 -0
  128. package/build/esm/i18n/empty-row/index.js +5 -0
  129. package/build/esm/i18n/empty-row/ru.json +3 -0
  130. package/build/esm/i18n/menubar/en.json +1 -0
  131. package/build/esm/i18n/menubar/index.d.ts +2 -1
  132. package/build/esm/i18n/menubar/ru.json +1 -0
  133. package/build/esm/i18n/yfm-note/index.d.ts +1 -1
  134. package/build/esm/index.d.ts +1 -0
  135. package/build/esm/index.js +1 -0
  136. package/build/esm/markup/codemirror/autocomplete/emptyRow.d.ts +9 -0
  137. package/build/esm/markup/codemirror/autocomplete/emptyRow.js +23 -0
  138. package/build/esm/markup/codemirror/autocomplete/index.d.ts +5 -0
  139. package/build/esm/markup/codemirror/autocomplete/index.js +10 -0
  140. package/build/esm/markup/codemirror/create.d.ts +1 -0
  141. package/build/esm/markup/codemirror/create.js +31 -13
  142. package/build/esm/markup/codemirror/smart-reindent/index.d.ts +4 -0
  143. package/build/esm/markup/codemirror/smart-reindent/index.js +38 -0
  144. package/build/esm/markup/codemirror/smart-reindent/utils.d.ts +15 -0
  145. package/build/esm/markup/codemirror/smart-reindent/utils.js +55 -0
  146. package/build/esm/markup/codemirror/yfm.d.ts +2 -1
  147. package/build/esm/markup/codemirror/yfm.js +1 -1
  148. package/build/esm/markup/commands/emptyRow.d.ts +2 -0
  149. package/build/esm/markup/commands/emptyRow.js +39 -0
  150. package/build/esm/markup/commands/index.d.ts +1 -0
  151. package/build/esm/markup/commands/index.js +1 -0
  152. package/build/esm/modules/toolbars/constants.d.ts +13 -0
  153. package/build/esm/modules/toolbars/constants.js +15 -0
  154. package/build/esm/modules/toolbars/items.d.ts +127 -0
  155. package/build/esm/modules/toolbars/items.js +730 -0
  156. package/build/esm/modules/toolbars/presets.d.ts +6 -0
  157. package/build/esm/modules/toolbars/presets.js +462 -0
  158. package/build/esm/modules/toolbars/types.d.ts +62 -0
  159. package/build/esm/modules/toolbars/types.js +1 -0
  160. package/build/esm/shortcuts/const.d.ts +1 -0
  161. package/build/esm/shortcuts/const.js +1 -0
  162. package/build/esm/shortcuts/default.js +1 -0
  163. package/build/esm/toolbar/types.d.ts +8 -0
  164. package/build/esm/toolbar/types.js +2 -0
  165. package/build/esm/version.js +1 -1
  166. package/package.json +1 -1
@@ -1,5 +1,6 @@
1
1
  import { Mark } from 'prosemirror-model';
2
2
  import { logger } from '../../logger';
3
+ import { ProseMirrorTransformer } from './ProseMirrorTransformer';
3
4
  const openSuffix = '_open';
4
5
  const closeSuffix = '_close';
5
6
  var TokenType;
@@ -9,12 +10,13 @@ var TokenType;
9
10
  TokenType["default"] = "default";
10
11
  })(TokenType || (TokenType = {}));
11
12
  export class MarkdownParser {
12
- constructor(schema, tokenizer, tokens) {
13
+ constructor(schema, tokenizer, tokens, pmTransformers) {
13
14
  this.stack = [];
14
15
  this.schema = schema;
15
16
  this.marks = Mark.none;
16
17
  this.tokens = tokens;
17
18
  this.tokenizer = tokenizer;
19
+ this.pmTransformers = pmTransformers;
18
20
  }
19
21
  validateLink(url) {
20
22
  return this.tokenizer.validateLink(url);
@@ -47,7 +49,8 @@ export class MarkdownParser {
47
49
  do {
48
50
  doc = this.closeNode();
49
51
  } while (this.stack.length);
50
- return (doc || this.schema.topNodeType.createAndFill());
52
+ const pmTransformer = new ProseMirrorTransformer(this.pmTransformers);
53
+ return doc ? pmTransformer.transform(doc) : this.schema.topNodeType.createAndFill();
51
54
  }
52
55
  finally {
53
56
  logger.metrics({ component: 'parser', event: 'parse', duration: Date.now() - time });
@@ -161,7 +161,7 @@ export class MarkdownSerializerState {
161
161
  const startOfLine = this.atBlank() || this.closed;
162
162
  this.write();
163
163
  let text = lines[i];
164
- if (escape !== false)
164
+ if (escape !== false && this.options.escape !== false)
165
165
  text = this.esc(text, startOfLine);
166
166
  if (this.escapeWhitespace)
167
167
  text = this.escWhitespace(text);
@@ -0,0 +1,2 @@
1
+ import { TransformFn } from './index';
2
+ export declare const transformEmptyParagraph: TransformFn;
@@ -0,0 +1,11 @@
1
+ export const transformEmptyParagraph = (node) => {
2
+ var _a, _b;
3
+ if (node.type !== 'paragraph')
4
+ return;
5
+ if (((_a = node.content) === null || _a === void 0 ? void 0 : _a.length) !== 1)
6
+ return;
7
+ if (((_b = node.content[0]) === null || _b === void 0 ? void 0 : _b.type) !== 'text')
8
+ return;
9
+ if (node.content[0].text === String.fromCharCode(160))
10
+ delete node.content;
11
+ };
@@ -0,0 +1,7 @@
1
+ import { TransformFn } from '.';
2
+ declare type GetTransformersProps = {
3
+ emptyRowTransformer?: boolean;
4
+ };
5
+ declare type GetPMTransformersType = (config: GetTransformersProps) => TransformFn[];
6
+ export declare const getPMTransformers: GetPMTransformersType;
7
+ export {};
@@ -0,0 +1,9 @@
1
+ // TODO: add a new method to the ExtensionBuilder
2
+ import { transformEmptyParagraph } from './emptyRowTransformer';
3
+ export const getPMTransformers = ({ emptyRowTransformer }) => {
4
+ const transformers = [];
5
+ if (emptyRowTransformer) {
6
+ transformers.push(transformEmptyParagraph);
7
+ }
8
+ return transformers;
9
+ };
@@ -0,0 +1,15 @@
1
+ import { Node } from 'prosemirror-model';
2
+ declare type PMNodeJSON = {
3
+ type: string;
4
+ attrs?: Record<string, any>;
5
+ content?: PMNodeJSON[];
6
+ text?: string;
7
+ };
8
+ export declare type TransformFn = (node: PMNodeJSON) => void;
9
+ export declare class ProseMirrorTransformer {
10
+ private readonly _transformers;
11
+ constructor(fns: TransformFn[]);
12
+ transform(doc: Node): Node;
13
+ transformJSON(node: PMNodeJSON): void;
14
+ }
15
+ export {};
@@ -0,0 +1,21 @@
1
+ import { Node } from 'prosemirror-model';
2
+ export class ProseMirrorTransformer {
3
+ constructor(fns) {
4
+ this._transformers = fns;
5
+ }
6
+ transform(doc) {
7
+ const docJSON = doc.toJSON();
8
+ this.transformJSON(docJSON);
9
+ return Node.fromJSON(doc.type.schema, docJSON);
10
+ }
11
+ transformJSON(node) {
12
+ for (const fn of this._transformers) {
13
+ fn(node);
14
+ }
15
+ if (node.content) {
16
+ for (const child of node.content) {
17
+ this.transformJSON(child);
18
+ }
19
+ }
20
+ }
21
+ }
@@ -40,6 +40,7 @@ export const MathSpecs = (builder) => {
40
40
  code: true,
41
41
  toDOM: () => ['div', { class: 'math-block' }, 0],
42
42
  parseDOM: [{ tag: 'div.math-block', priority: 200 }],
43
+ selectable: true,
43
44
  },
44
45
  fromMd: {
45
46
  tokenName: 'math_block',
@@ -8,5 +8,6 @@ export declare enum BaseNode {
8
8
  export declare const pType: (schema: import("prosemirror-model").Schema<any, any>) => import("prosemirror-model").NodeType;
9
9
  export declare type BaseSchemaSpecsOptions = {
10
10
  paragraphPlaceholder?: NonNullable<NodeSpec['placeholder']>['content'];
11
+ preserveEmptyRows?: boolean;
11
12
  };
12
13
  export declare const BaseSchemaSpecs: ExtensionAuto<BaseSchemaSpecsOptions>;
@@ -42,6 +42,7 @@ export const BaseSchemaSpecs = (builder, opts) => {
42
42
  0,
43
43
  ];
44
44
  },
45
+ selectable: true,
45
46
  placeholder: opts.paragraphPlaceholder
46
47
  ? {
47
48
  content: opts.paragraphPlaceholder,
@@ -50,9 +51,28 @@ export const BaseSchemaSpecs = (builder, opts) => {
50
51
  : undefined,
51
52
  },
52
53
  fromMd: { tokenSpec: { name: BaseNode.Paragraph, type: 'block' } },
53
- toMd: (state, node) => {
54
- state.renderInline(node);
55
- state.closeBlock(node);
54
+ toMd: (state, node, parent) => {
55
+ /*
56
+ An empty line is added only if there is some content in the parent element.
57
+ This is necessary in order to prevent an empty document with empty lines
58
+ */
59
+ if (opts.preserveEmptyRows && !node.content.size) {
60
+ let isParentEmpty = true;
61
+ for (let index = 0; index < parent.content.childCount; index++) {
62
+ const parentChild = parent.content.child(index);
63
+ if (parentChild.content.size !== 0 ||
64
+ parentChild.type.name !== 'paragraph') {
65
+ isParentEmpty = false;
66
+ }
67
+ }
68
+ if (!isParentEmpty) {
69
+ state.write('&nbsp;\n\n');
70
+ }
71
+ }
72
+ else {
73
+ state.renderInline(node);
74
+ state.closeBlock(node);
75
+ }
56
76
  },
57
77
  }));
58
78
  };
@@ -71,11 +71,7 @@ const getTopLevelNodesFromSelection = (selection, doc) => {
71
71
  const { from, to } = selection;
72
72
  doc.nodesBetween(from, to, (node, pos) => {
73
73
  const withinSelection = from <= pos && pos + node.nodeSize <= to;
74
- if (node &&
75
- node.type.name !== 'paragraph' &&
76
- !node.isText &&
77
- node.type.spec.selectable &&
78
- withinSelection) {
74
+ if (node && !node.isText && node.type.spec.selectable !== false && withinSelection) {
79
75
  nodes.push({ node, pos });
80
76
  return false;
81
77
  }
@@ -12,6 +12,7 @@ export const BlockquoteSpecs = (builder) => {
12
12
  toDOM() {
13
13
  return ['blockquote', 0];
14
14
  },
15
+ selectable: true,
15
16
  },
16
17
  fromMd: { tokenSpec: { name: blockquoteNodeName, type: 'block' } },
17
18
  toMd: (state, node) => {
@@ -16,7 +16,7 @@ export const setCodeBlockType = ({ serializer }) => (state, dispatch) => {
16
16
  if (!setBlockType(nodeType)(state))
17
17
  return false;
18
18
  if (dispatch) {
19
- const markup = serializer.serialize(state.selection.content().content);
19
+ const markup = serializer.serialize(state.selection.content().content, { escape: false });
20
20
  dispatch(state.tr.replaceSelectionWith(nodeType.createAndFill({}, markup ? state.schema.text(markup) : null)));
21
21
  }
22
22
  return true;
@@ -11,6 +11,7 @@ export const HorizontalRuleSpecs = (builder) => {
11
11
  toDOM() {
12
12
  return ['div', ['hr']];
13
13
  },
14
+ selectable: true,
14
15
  },
15
16
  fromMd: {
16
17
  tokenName: 'hr',
@@ -10,21 +10,37 @@ export function linkPasteEnhance({ markupParser: parser }) {
10
10
  paste(view, e) {
11
11
  const { state, dispatch } = view;
12
12
  const sel = state.selection;
13
+ let tr = null;
13
14
  if (isTextSelection(sel) ||
14
15
  (isNodeSelection(sel) && sel.node.type === imageType(state.schema))) {
15
16
  const { $from, $to } = sel;
16
- if ($from.pos !== $to.pos && $from.sameParent($to)) {
17
+ if ($from.pos === $to.pos) {
17
18
  const url = getUrl(e.clipboardData, parser);
18
19
  if (url) {
19
- const tr = state.tr.addMark($from.pos, $to.pos, linkType(state.schema).create({
20
+ const linkMarkType = linkType(state.schema);
21
+ tr = state.tr.replaceSelectionWith(state.schema.text(url, [
22
+ ...$from
23
+ .marks()
24
+ .filter((mark) => mark.type !== linkMarkType),
25
+ linkMarkType.create({ [LinkAttr.Href]: url }),
26
+ ]), false);
27
+ }
28
+ }
29
+ else if ($from.sameParent($to)) {
30
+ const url = getUrl(e.clipboardData, parser);
31
+ if (url) {
32
+ tr = state.tr.addMark($from.pos, $to.pos, linkType(state.schema).create({
20
33
  [LinkAttr.Href]: url,
21
34
  }));
22
- dispatch(tr.setSelection(TextSelection.create(tr.doc, $to.pos)));
23
- e.preventDefault();
24
- return true;
35
+ tr.setSelection(TextSelection.create(tr.doc, $to.pos));
25
36
  }
26
37
  }
27
38
  }
39
+ if (tr) {
40
+ dispatch(tr.scrollIntoView());
41
+ e.preventDefault();
42
+ return true;
43
+ }
28
44
  return false;
29
45
  },
30
46
  },
@@ -64,6 +64,7 @@ export const getSchemaSpecs = (opts, placeholder) => {
64
64
  toDOM(node) {
65
65
  return ['div', node.attrs, 0];
66
66
  },
67
+ selectable: true,
67
68
  complex: 'root',
68
69
  },
69
70
  [TabsNode.TabsList]: {
@@ -0,0 +1,3 @@
1
+ {
2
+ "snippet.text": "Empty row"
3
+ }
@@ -0,0 +1,5 @@
1
+ export declare const i18n: <G extends "snippet.text", S extends string>(key: G | (string extends S ? S : never), params?: {
2
+ [key: string]: any;
3
+ } | undefined) => S extends G ? {
4
+ "snippet.text": string;
5
+ }[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 = 'empty-row';
5
+ export const i18n = registerKeyset(KEYSET, { en, ru });
@@ -0,0 +1,3 @@
1
+ {
2
+ "snippet.text": "Пустая строка"
3
+ }
@@ -44,6 +44,7 @@
44
44
  "mermaid": "Mermaid",
45
45
  "mono": "Monospace",
46
46
  "more_action": "More action",
47
+ "move_list": "Move list item",
47
48
  "note": "Note",
48
49
  "olist": "Ordered list",
49
50
  "quote": "Quote",
@@ -1,4 +1,4 @@
1
- export declare const i18n: <G extends "bold" | "code" | "colorify" | "mono" | "link" | "italic" | "strike" | "underline" | "mark" | "quote" | "text" | "html" | "cut" | "table" | "image" | "code_inline" | "list" | "heading" | "note" | "file" | "checkbox" | "emoji" | "undo" | "redo" | "heading1" | "heading2" | "heading3" | "heading4" | "heading5" | "heading6" | "math_inline" | "math_block" | "tabs" | "mermaid" | "gpt" | "codeblock" | "math" | "colorify__color_blue" | "colorify__color_default" | "colorify__color_gray" | "colorify__color_green" | "colorify__color_orange" | "colorify__color_red" | "colorify__color_violet" | "colorify__color_yellow" | "colorify__group_text" | "emoji__hint" | "folding-heading" | "folding-heading__hint" | "hrule" | "list__action_lift" | "list__action_sink" | "list_action_disabled" | "more_action" | "olist" | "ulist", S extends string>(key: G | (string extends S ? S : never), params?: {
1
+ export declare const i18n: <G extends "bold" | "code" | "colorify" | "mono" | "link" | "italic" | "strike" | "underline" | "mark" | "quote" | "text" | "html" | "cut" | "table" | "image" | "code_inline" | "list" | "heading" | "note" | "file" | "checkbox" | "emoji" | "gpt" | "heading1" | "heading2" | "heading3" | "heading4" | "heading5" | "heading6" | "math_block" | "math_inline" | "mermaid" | "redo" | "tabs" | "undo" | "codeblock" | "math" | "colorify__color_blue" | "colorify__color_default" | "colorify__color_gray" | "colorify__color_green" | "colorify__color_orange" | "colorify__color_red" | "colorify__color_violet" | "colorify__color_yellow" | "colorify__group_text" | "emoji__hint" | "folding-heading" | "folding-heading__hint" | "hrule" | "list__action_lift" | "list__action_sink" | "list_action_disabled" | "more_action" | "move_list" | "olist" | "ulist", S extends string>(key: G | (string extends S ? S : never), params?: {
2
2
  [key: string]: any;
3
3
  } | undefined) => S extends G ? {
4
4
  bold: string;
@@ -46,6 +46,7 @@ export declare const i18n: <G extends "bold" | "code" | "colorify" | "mono" | "l
46
46
  mermaid: string;
47
47
  mono: string;
48
48
  more_action: string;
49
+ move_list: string;
49
50
  note: string;
50
51
  olist: string;
51
52
  quote: string;
@@ -44,6 +44,7 @@
44
44
  "mermaid": "Mermaid",
45
45
  "mono": "Моноширинный",
46
46
  "more_action": "Другие действия",
47
+ "move_list": "Переместить элемент списка",
47
48
  "note": "Примечание",
48
49
  "olist": "Нумерованный список",
49
50
  "quote": "Цитата",
@@ -1,4 +1,4 @@
1
- export declare const i18n: <G extends "remove" | "info" | "warning" | "tip" | "alert", S extends string>(key: G | (string extends S ? S : never), params?: {
1
+ export declare const i18n: <G extends "remove" | "info" | "tip" | "warning" | "alert", S extends string>(key: G | (string extends S ? S : never), params?: {
2
2
  [key: string]: any;
3
3
  } | undefined) => S extends G ? {
4
4
  info: string;
@@ -1,6 +1,7 @@
1
1
  export * from './common';
2
2
  export * from './core';
3
3
  export * from './toolbar';
4
+ export * from './modules/toolbars/types';
4
5
  export * from './react-utils';
5
6
  export * from './classname';
6
7
  export * from './logger';
@@ -1,6 +1,7 @@
1
1
  export * from './common';
2
2
  export * from './core';
3
3
  export * from './toolbar';
4
+ export * from './modules/toolbars/types';
4
5
  export * from './react-utils';
5
6
  export * from './classname';
6
7
  export * from './logger';
@@ -0,0 +1,9 @@
1
+ import { CompletionContext, CompletionResult } from '@codemirror/autocomplete';
2
+ export declare const emptyRowSnippetTemplate = "&nbsp;\n\n";
3
+ export declare const emptyRowSnippet: (editor: {
4
+ state: import("@codemirror/state").EditorState;
5
+ dispatch: (tr: import("@codemirror/state").Transaction) => void;
6
+ }, completion: import("@codemirror/autocomplete").Completion | null, from: number, to: number) => void;
7
+ export declare const emptyRowAutocomplete: {
8
+ autocomplete: (context: CompletionContext) => CompletionResult | null;
9
+ };
@@ -0,0 +1,23 @@
1
+ import { snippet } from '@codemirror/autocomplete';
2
+ import { i18n } from '../../../../src/i18n/empty-row';
3
+ export const emptyRowSnippetTemplate = '&nbsp;\n\n';
4
+ export const emptyRowSnippet = snippet(emptyRowSnippetTemplate);
5
+ export const emptyRowAutocomplete = {
6
+ autocomplete: (context) => {
7
+ const word = context.matchBefore(/^.*/);
8
+ if (word === null || word === void 0 ? void 0 : word.text.startsWith('&')) {
9
+ return {
10
+ from: word.from,
11
+ options: [
12
+ {
13
+ label: '&nbsp;',
14
+ displayLabel: i18n('snippet.text'),
15
+ type: 'text',
16
+ apply: emptyRowSnippet,
17
+ },
18
+ ],
19
+ };
20
+ }
21
+ return null;
22
+ },
23
+ };
@@ -0,0 +1,5 @@
1
+ declare type GetAutocompleteConfig = {
2
+ preserveEmptyRows?: boolean;
3
+ };
4
+ export declare const getAutocompleteConfig: ({ preserveEmptyRows }: GetAutocompleteConfig) => import("../yfm").LanguageData[];
5
+ export {};
@@ -0,0 +1,10 @@
1
+ import { mdAutocomplete } from '../yfm';
2
+ import { emptyRowAutocomplete } from './emptyRow';
3
+ export const getAutocompleteConfig = ({ preserveEmptyRows }) => {
4
+ const autocompleteItems = [];
5
+ if (preserveEmptyRows) {
6
+ autocompleteItems.push(emptyRowAutocomplete);
7
+ }
8
+ autocompleteItems.push(mdAutocomplete);
9
+ return autocompleteItems;
10
+ };
@@ -33,6 +33,7 @@ export declare type CreateCodemirrorParams = {
33
33
  yfmLangOptions?: YfmLangOptions;
34
34
  autocompletion?: Autocompletion;
35
35
  directiveSyntax: DirectiveSyntaxContext;
36
+ preserveEmptyRows: boolean;
36
37
  };
37
38
  export declare function createCodemirror(params: CreateCodemirrorParams): EditorView;
38
39
  export declare function withLogger(action: string, command: StateCommand): StateCommand;
@@ -6,7 +6,7 @@ import { ActionName } from '../../bundle/config/action-names';
6
6
  import { logger } from '../../logger';
7
7
  import { Action as A, formatter as f } from '../../shortcuts';
8
8
  import { DataTransferType, shouldSkipHtmlConversion } from '../../utils/clipboard';
9
- import { insertImages, insertLink, toH1, toH2, toH3, toH4, toH5, toH6, toggleBold, toggleItalic, toggleStrikethrough, toggleUnderline, wrapToCodeBlock, wrapToInlineCode, wrapToYfmCut, wrapToYfmNote, } from '../commands';
9
+ import { insertEmptyRow, insertImages, insertLink, toH1, toH2, toH3, toH4, toH5, toH6, toggleBold, toggleItalic, toggleStrikethrough, toggleUnderline, wrapToCodeBlock, wrapToInlineCode, wrapToYfmCut, wrapToYfmNote, } from '../commands';
10
10
  import { DirectiveSyntaxFacet } from './directive-facet';
11
11
  import { FileUploadHandlerFacet } from './files-upload-facet';
12
12
  import { gravityHighlightStyle, gravityTheme } from './gravity';
@@ -14,9 +14,10 @@ import { MarkdownConverter } from './html-to-markdown/converters';
14
14
  import { PairingCharactersExtension } from './pairing-chars';
15
15
  import { ReactRendererFacet } from './react-facet';
16
16
  import { SearchPanelPlugin } from './search-plugin/plugin';
17
+ import { smartReindent } from './smart-reindent';
17
18
  import { yfmLang } from './yfm';
18
19
  export function createCodemirror(params) {
19
- const { doc, reactRenderer, onCancel, onScroll, onSubmit, onChange, onDocChange, disabledExtensions = {}, keymaps = [], receiver, yfmLangOptions, extensions: extraExtensions, placeholder: placeholderContent, autocompletion: autocompletionConfig, parseHtmlOnPaste, parseInsertedUrlAsImage, directiveSyntax, } = params;
20
+ const { doc, reactRenderer, onCancel, onScroll, onSubmit, onChange, onDocChange, disabledExtensions = {}, keymaps = [], receiver, yfmLangOptions, extensions: extraExtensions, placeholder: placeholderContent, autocompletion: autocompletionConfig, parseHtmlOnPaste, parseInsertedUrlAsImage, directiveSyntax, preserveEmptyRows, } = params;
20
21
  const extensions = [gravityTheme, placeholder(placeholderContent)];
21
22
  if (!disabledExtensions.history) {
22
23
  extensions.push(history());
@@ -70,12 +71,16 @@ export function createCodemirror(params) {
70
71
  var _a;
71
72
  if (!event.clipboardData)
72
73
  return;
74
+ const { from } = editor.state.selection.main;
75
+ const line = editor.state.doc.lineAt(from);
76
+ const currentLine = line.text;
73
77
  // if clipboard contains YFM content - avoid any meddling with pasted content
74
78
  // since text/yfm will contain valid markdown
75
79
  const yfmContent = event.clipboardData.getData(DataTransferType.Yfm);
76
80
  if (yfmContent) {
77
81
  event.preventDefault();
78
- editor.dispatch(editor.state.replaceSelection(yfmContent));
82
+ const reindentedYfmContent = smartReindent(yfmContent, currentLine);
83
+ editor.dispatch(editor.state.replaceSelection(reindentedYfmContent));
79
84
  return;
80
85
  }
81
86
  // checking if a copy buffer content is suitable for convertion
@@ -100,29 +105,42 @@ export function createCodemirror(params) {
100
105
  }
101
106
  if (parsedMarkdownMarkup !== undefined) {
102
107
  event.preventDefault();
103
- editor.dispatch(editor.state.replaceSelection(parsedMarkdownMarkup));
108
+ const reindentedHtmlContent = smartReindent(parsedMarkdownMarkup, currentLine);
109
+ editor.dispatch(editor.state.replaceSelection(reindentedHtmlContent));
104
110
  return;
105
111
  }
106
112
  }
107
113
  if (parseInsertedUrlAsImage) {
108
114
  const { imageUrl, title } = parseInsertedUrlAsImage((_a = event.clipboardData.getData(DataTransferType.Text)) !== null && _a !== void 0 ? _a : '') || {};
109
- if (!imageUrl) {
110
- return;
115
+ if (imageUrl) {
116
+ event.preventDefault();
117
+ insertImages([
118
+ {
119
+ url: imageUrl,
120
+ alt: title,
121
+ title,
122
+ },
123
+ ])(editor);
111
124
  }
125
+ }
126
+ // Reindenting pasted plain text
127
+ const pastedText = event.clipboardData.getData(DataTransferType.Text);
128
+ const reindentedText = smartReindent(pastedText, currentLine);
129
+ // but only if there is a need for reindentation
130
+ if (pastedText !== reindentedText) {
131
+ editor.dispatch(editor.state.replaceSelection(reindentedText));
112
132
  event.preventDefault();
113
- insertImages([
114
- {
115
- url: imageUrl,
116
- alt: title,
117
- title,
118
- },
119
- ])(editor);
120
133
  }
121
134
  },
122
135
  }), SearchPanelPlugin({
123
136
  anchorSelector: '.g-md-search-anchor',
124
137
  receiver,
125
138
  }));
139
+ if (preserveEmptyRows) {
140
+ extensions.push(keymap.of([
141
+ { key: f.toCM(A.EmptyRow), run: withLogger(ActionName.emptyRow, insertEmptyRow) },
142
+ ]));
143
+ }
126
144
  if (params.uploadHandler) {
127
145
  extensions.push(FileUploadHandlerFacet.of({
128
146
  fn: params.uploadHandler,
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Reindents pasted text based on the current line's markers
3
+ */
4
+ export declare function smartReindent(pastedText: string, currentLineText: string): string;
@@ -0,0 +1,38 @@
1
+ import { parseMarkers } from './utils';
2
+ /**
3
+ * Reindents pasted text based on the current line's markers
4
+ */
5
+ export function smartReindent(pastedText, currentLineText) {
6
+ // If current line is empty, return pasted text as is
7
+ if (currentLineText.length === 0) {
8
+ return pastedText;
9
+ }
10
+ // Get markers from current line
11
+ const markers = parseMarkers(currentLineText);
12
+ // If no markers found, return pasted text as is
13
+ if (markers.length === 0) {
14
+ return pastedText;
15
+ }
16
+ // Create indentation for subsequent lines by replacing list markers with spaces
17
+ const subsequentIndent = markers
18
+ .map((marker) => {
19
+ if (marker.match(/^\d{1,6}\. |-|\*|\+/)) {
20
+ return ' '.repeat(marker.length);
21
+ }
22
+ return marker;
23
+ })
24
+ .join('');
25
+ // Split and process the pasted text
26
+ const lines = pastedText.split('\n');
27
+ const reindentedText = lines
28
+ .map((line, index) => {
29
+ // First line doesn't need indentation
30
+ if (index === 0) {
31
+ return line;
32
+ }
33
+ // Add indentation to all subsequent lines, including empty ones
34
+ return subsequentIndent + line;
35
+ })
36
+ .join('\n');
37
+ return reindentedText;
38
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Parses markdown-style markers from the start of a line
3
+ * Returns an array of markers found:
4
+ * - ' ' for indentation
5
+ * - '> ' for blockquotes
6
+ * - '* ' or '- ' for list items
7
+ * - '1. ' for numbered lists
8
+ *
9
+ * Example inputs:
10
+ * " * list" -> [' ', '* ']
11
+ * "> quoted" -> ['> ']
12
+ * " nested" -> [' ', ' ']
13
+ * "1. list" -> ['1. ']
14
+ */
15
+ export declare function parseMarkers(text: string): string[];
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Parses markdown-style markers from the start of a line
3
+ * Returns an array of markers found:
4
+ * - ' ' for indentation
5
+ * - '> ' for blockquotes
6
+ * - '* ' or '- ' for list items
7
+ * - '1. ' for numbered lists
8
+ *
9
+ * Example inputs:
10
+ * " * list" -> [' ', '* ']
11
+ * "> quoted" -> ['> ']
12
+ * " nested" -> [' ', ' ']
13
+ * "1. list" -> ['1. ']
14
+ */
15
+ export function parseMarkers(text) {
16
+ const markers = [];
17
+ let pos = 0;
18
+ while (pos < text.length) {
19
+ // Handle code block (4 spaces)
20
+ if (pos + 3 < text.length &&
21
+ text[pos] === ' ' &&
22
+ text[pos + 1] === ' ' &&
23
+ text[pos + 2] === ' ' &&
24
+ text[pos + 3] === ' ') {
25
+ markers.push(' ');
26
+ pos += 4;
27
+ continue;
28
+ }
29
+ // Handle numbered lists (1-6 digits followed by dot and space)
30
+ if (/^\d{1,6}\. /.test(text.slice(pos))) {
31
+ const match = text.slice(pos).match(/^(\d{1,6}\. )/);
32
+ if (match) {
33
+ markers.push(match[1]);
34
+ pos += match[1].length;
35
+ continue;
36
+ }
37
+ }
38
+ // Handle block quotes and list markers
39
+ if (text[pos] === '>' || text[pos] === '-' || text[pos] === '*' || text[pos] === '+') {
40
+ if (pos + 1 < text.length && text[pos + 1] === ' ') {
41
+ markers.push(text[pos] + ' ');
42
+ pos += 2;
43
+ continue;
44
+ }
45
+ }
46
+ // Handle single space (last priority)
47
+ if (text[pos] === ' ') {
48
+ markers.push(' ');
49
+ pos += 1;
50
+ continue;
51
+ }
52
+ break;
53
+ }
54
+ return markers;
55
+ }