decap-cms-widget-markdown 3.12.0 → 3.13.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 (45) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/decap-cms-widget-markdown.js +6 -6
  3. package/dist/decap-cms-widget-markdown.js.map +1 -1
  4. package/dist/esm/MarkdownControl/Toolbar.js +6 -6
  5. package/dist/esm/MarkdownControl/VisualEditor.js +7 -3
  6. package/dist/esm/MarkdownControl/components/InlineShortcode.js +67 -0
  7. package/dist/esm/MarkdownControl/index.js +2 -2
  8. package/dist/esm/MarkdownControl/plugins/shortcodes/insertShortcode.js +53 -4
  9. package/dist/esm/MarkdownControl/plugins/shortcodes/withShortcodes.js +11 -1
  10. package/dist/esm/MarkdownControl/renderers.js +21 -16
  11. package/dist/esm/MarkdownPreview.js +37 -3
  12. package/dist/esm/serializers/remarkRehypeShortcodes.js +18 -15
  13. package/dist/esm/serializers/remarkShortcodes.js +118 -12
  14. package/dist/esm/serializers/remarkSlate.js +14 -1
  15. package/dist/esm/serializers/slateRemark.js +18 -2
  16. package/package.json +23 -13
  17. package/src/MarkdownControl/Toolbar.js +2 -2
  18. package/src/MarkdownControl/VisualEditor.js +5 -1
  19. package/src/MarkdownControl/components/InlineShortcode.js +77 -0
  20. package/src/MarkdownControl/components/__tests__/InlineShortcode.spec.js +81 -0
  21. package/src/MarkdownControl/index.js +2 -2
  22. package/src/MarkdownControl/plugins/shortcodes/__tests__/insertShortcode.spec.js +106 -0
  23. package/src/MarkdownControl/plugins/shortcodes/insertShortcode.js +59 -6
  24. package/src/MarkdownControl/plugins/shortcodes/withShortcodes.js +12 -2
  25. package/src/MarkdownControl/renderers.js +3 -0
  26. package/src/MarkdownPreview.js +38 -3
  27. package/src/__tests__/renderer.spec.js +68 -0
  28. package/src/serializers/__tests__/remarkShortcodes.spec.js +210 -1
  29. package/src/serializers/__tests__/slate.spec.js +80 -0
  30. package/src/serializers/remarkRehypeShortcodes.js +19 -9
  31. package/src/serializers/remarkShortcodes.js +140 -12
  32. package/src/serializers/remarkSlate.js +7 -0
  33. package/src/serializers/slateRemark.js +13 -2
  34. package/LICENSE +0 -22
  35. package/dist/esm/MarkdownControl/plugins/BreakToDefaultBlock.js +0 -26
  36. package/dist/esm/MarkdownControl/plugins/CloseBlock.js +0 -28
  37. package/dist/esm/MarkdownControl/plugins/CommandsAndQueries.js +0 -180
  38. package/dist/esm/MarkdownControl/plugins/ForceInsert.js +0 -42
  39. package/dist/esm/MarkdownControl/plugins/Hotkey.js +0 -26
  40. package/dist/esm/MarkdownControl/plugins/LineBreak.js +0 -13
  41. package/dist/esm/MarkdownControl/plugins/Link.js +0 -49
  42. package/dist/esm/MarkdownControl/plugins/lists/locations/isCursorInItemContainingNestedList.js +0 -6
  43. package/dist/esm/MarkdownControl/plugins/util.js +0 -11
  44. package/dist/esm/serializers/remarkImagesToText.js +0 -31
  45. package/dist/esm/types.js +0 -2
@@ -1,8 +1,7 @@
1
- import React from 'react';
2
- import map from 'lodash/map';
3
1
  import has from 'lodash/has';
4
2
  import { renderToString } from 'react-dom/server';
5
3
  import u from 'unist-builder';
4
+ import { createElement } from 'react';
6
5
 
7
6
  /**
8
7
  * This plugin doesn't actually transform Remark (MDAST) nodes to Rehype
@@ -14,8 +13,21 @@ export default function remarkToRehypeShortcodes({ plugins, getAsset, resolveWid
14
13
  return transform;
15
14
 
16
15
  function transform(root) {
17
- const transformedChildren = map(root.children, processShortcodes);
18
- return { ...root, children: transformedChildren };
16
+ function walk(node) {
17
+ if (!node) return node;
18
+ if (has(node, ['data', 'shortcode'])) {
19
+ return processShortcodes(node);
20
+ }
21
+ if (Array.isArray(node.children)) {
22
+ return {
23
+ ...node,
24
+ children: node.children.map(walk),
25
+ };
26
+ }
27
+ return node;
28
+ }
29
+
30
+ return walk(root);
19
31
  }
20
32
 
21
33
  /**
@@ -33,6 +45,7 @@ export default function remarkToRehypeShortcodes({ plugins, getAsset, resolveWid
33
45
  */
34
46
  const { shortcode, shortcodeData } = node.data;
35
47
  const plugin = plugins.get(shortcode);
48
+ if (!plugin) return node;
36
49
 
37
50
  /**
38
51
  * Run the shortcode plugin's `toPreview` method, which will return either
@@ -45,9 +58,7 @@ export default function remarkToRehypeShortcodes({ plugins, getAsset, resolveWid
45
58
  /**
46
59
  * Return a new 'html' type node containing the shortcode preview markup.
47
60
  */
48
- const textNode = u('html', valueHtml);
49
- const children = [textNode];
50
- return { ...node, children };
61
+ return u('html', valueHtml);
51
62
  }
52
63
 
53
64
  /**
@@ -58,7 +69,6 @@ export default function remarkToRehypeShortcodes({ plugins, getAsset, resolveWid
58
69
  if (toPreview) {
59
70
  return toPreview(shortcodeData, getAsset, fields);
60
71
  }
61
-
62
72
  /**
63
73
  * For editor components without a custom `toPreview` (e.g. container
64
74
  * components with nested markdown/richtext fields), render each sub-field
@@ -88,7 +98,7 @@ export default function remarkToRehypeShortcodes({ plugins, getAsset, resolveWid
88
98
  * Last resort fallback: try resolving the widget and rendering its preview.
89
99
  */
90
100
  const preview = resolveWidget(plugin.widget);
91
- return React.createElement(preview.preview, {
101
+ return createElement(preview.preview, {
92
102
  value: shortcodeData,
93
103
  field: plugin,
94
104
  getAsset,
@@ -1,16 +1,31 @@
1
1
  export function remarkParseShortcodes({ plugins }) {
2
2
  const Parser = this.Parser;
3
- const tokenizers = Parser.prototype.blockTokenizers;
4
- const methods = Parser.prototype.blockMethods;
3
+ const blockTokenizers = Parser.prototype.blockTokenizers;
4
+ const blockMethods = Parser.prototype.blockMethods;
5
+ const inlineTokenizers = Parser.prototype.inlineTokenizers;
6
+ const inlineMethods = Parser.prototype.inlineMethods;
5
7
 
6
- tokenizers.shortcode = createShortcodeTokenizer({ plugins });
8
+ blockTokenizers.shortcode = createShortcodeTokenizer({ plugins });
9
+ blockMethods.unshift('shortcode');
7
10
 
8
- methods.unshift('shortcode');
11
+ inlineTokenizers.inlineShortcode = createInlineShortcodeTokenizer({ plugins });
12
+ inlineMethods.unshift('inlineShortcode');
13
+ }
14
+
15
+ function createPattern(pattern, { anchored = false } = {}) {
16
+ let source = pattern.source;
17
+ if (anchored && !source.startsWith('^')) {
18
+ source = `^${source}`;
19
+ } else if (!anchored && source.startsWith('^')) {
20
+ source = source.slice(1);
21
+ }
22
+
23
+ return new RegExp(source, pattern.flags.replace(/[gy]/g, ''));
9
24
  }
10
25
 
11
26
  function createShortcodeTokenizer({ plugins }) {
12
27
  plugins.forEach(plugin => {
13
- if (plugin.pattern.flags.includes('m')) {
28
+ if (plugin.pattern && plugin.pattern.flags.includes('m')) {
14
29
  console.warn(
15
30
  `Invalid RegExp: editor component '${plugin.id}' must not use the multiline flag in its pattern.`,
16
31
  );
@@ -20,17 +35,18 @@ function createShortcodeTokenizer({ plugins }) {
20
35
  let match;
21
36
  const potentialMatchValue = value.split('\n\n')[0].trimEnd();
22
37
  const plugin = plugins.find(plugin => {
38
+ if (plugin.type === 'inline') {
39
+ return false;
40
+ }
23
41
  let { pattern } = plugin;
24
42
  // Plugin patterns must start with a caret (^) to match the beginning of the block.
25
43
  // If the pattern does not start with a caret, we add it
26
44
  // to ensure that remark consumes only the shortcode, without any leading text.
27
- if (!pattern.source.startsWith('^')) {
28
- pattern = new RegExp(`^${pattern.source}`, pattern.flags);
29
- }
45
+ pattern = createPattern(pattern, { anchored: true });
30
46
 
31
- match = value.match(pattern);
47
+ match = pattern.exec(value);
32
48
  if (!match) {
33
- match = potentialMatchValue.match(pattern);
49
+ match = pattern.exec(potentialMatchValue);
34
50
  }
35
51
 
36
52
  return !!match;
@@ -46,7 +62,11 @@ function createShortcodeTokenizer({ plugins }) {
46
62
  return true;
47
63
  }
48
64
 
49
- const shortcodeData = plugin.fromBlock(match);
65
+ const shortcodeData = plugin.fromBlock
66
+ ? plugin.fromBlock(match)
67
+ : plugin.fromInline
68
+ ? plugin.fromInline(match)
69
+ : match;
50
70
 
51
71
  try {
52
72
  return eat(match[0])({
@@ -65,17 +85,125 @@ function createShortcodeTokenizer({ plugins }) {
65
85
  };
66
86
  }
67
87
 
88
+ function createInlineShortcodeTokenizer({ plugins }) {
89
+ plugins.forEach(plugin => {
90
+ if (plugin.type === 'inline' && plugin.pattern) {
91
+ if (plugin.pattern.flags.includes('m')) {
92
+ console.warn(
93
+ `Invalid RegExp: inline editor component '${plugin.id}' must not use the multiline flag in its pattern.`,
94
+ );
95
+ }
96
+ if (/(\.\*|\.\+)(?!\?)/.test(plugin.pattern.source)) {
97
+ console.warn(
98
+ `Potentially greedy RegExp in inline component '${plugin.id}': consider using non-greedy quantifier (e.g. .*? or .+?) or specific character classes to prevent overmatching within paragraphs.`,
99
+ );
100
+ }
101
+ }
102
+ });
103
+
104
+ function tokenizeInlineShortcode(eat, value, silent) {
105
+ let match;
106
+ const plugin = plugins.find(plugin => {
107
+ if (plugin.type !== 'inline') {
108
+ return false;
109
+ }
110
+ let { pattern } = plugin;
111
+ // Inline patterns must match at the current offset (leading ^)
112
+ pattern = createPattern(pattern, { anchored: true });
113
+
114
+ match = pattern.exec(value);
115
+ return !!match;
116
+ });
117
+
118
+ if (match) {
119
+ if (silent) {
120
+ return true;
121
+ }
122
+
123
+ const shortcodeData = plugin.fromInline
124
+ ? plugin.fromInline(match)
125
+ : plugin.fromBlock
126
+ ? plugin.fromBlock(match)
127
+ : match;
128
+
129
+ try {
130
+ return eat(match[0])({
131
+ type: 'inline-shortcode',
132
+ data: {
133
+ shortcode: plugin.id,
134
+ shortcodeData,
135
+ isVoid: true,
136
+ },
137
+ });
138
+ } catch (e) {
139
+ console.warn(
140
+ `Sent invalid data to remark. Inline plugin: ${plugin.id}. Value: ${
141
+ match[0]
142
+ }. Data: ${JSON.stringify(shortcodeData)}`,
143
+ );
144
+ return false;
145
+ }
146
+ }
147
+ }
148
+
149
+ tokenizeInlineShortcode.locator = function locateInlineShortcode(value, fromIndex) {
150
+ let minIndex = -1;
151
+ plugins.forEach(plugin => {
152
+ if (plugin.type !== 'inline') {
153
+ return;
154
+ }
155
+
156
+ if (plugin.trigger) {
157
+ const triggerIndex = value.indexOf(plugin.trigger, fromIndex);
158
+ if (triggerIndex !== -1 && (minIndex === -1 || triggerIndex < minIndex)) {
159
+ minIndex = triggerIndex;
160
+ }
161
+ } else {
162
+ const searchPattern = createPattern(plugin.pattern);
163
+ const slice = value.slice(fromIndex);
164
+ const match = searchPattern.exec(slice);
165
+ if (match && typeof match.index === 'number') {
166
+ const foundIndex = fromIndex + match.index;
167
+ if (minIndex === -1 || foundIndex < minIndex) {
168
+ minIndex = foundIndex;
169
+ }
170
+ }
171
+ }
172
+ });
173
+ return minIndex;
174
+ };
175
+
176
+ return tokenizeInlineShortcode;
177
+ }
178
+
68
179
  export function createRemarkShortcodeStringifier({ plugins }) {
69
180
  return function remarkStringifyShortcodes() {
70
181
  const Compiler = this.Compiler;
71
182
  const visitors = Compiler.prototype.visitors;
72
183
 
73
184
  visitors.shortcode = shortcode;
185
+ visitors['inline-shortcode'] = inlineShortcode;
74
186
 
75
187
  function shortcode(node) {
76
188
  const { data } = node;
77
189
  const plugin = plugins.find(plugin => data.shortcode === plugin.id);
78
- return plugin.toBlock(data.shortcodeData);
190
+ if (!plugin) return '';
191
+ return plugin.toBlock
192
+ ? plugin.toBlock(data.shortcodeData)
193
+ : plugin.toInline
194
+ ? plugin.toInline(data.shortcodeData)
195
+ : '';
196
+ }
197
+
198
+ function inlineShortcode(node) {
199
+ const { data } = node;
200
+ const plugin = plugins.find(plugin => data.shortcode === plugin.id);
201
+ if (!plugin) return '';
202
+ return plugin.toInline
203
+ ? plugin.toInline(data.shortcodeData)
204
+ : plugin.toBlock
205
+ ? plugin.toBlock(data.shortcodeData)
206
+ : '';
79
207
  }
80
208
  };
81
209
  }
@@ -21,6 +21,7 @@ const typeMap = {
21
21
  link: 'link',
22
22
  image: 'image',
23
23
  shortcode: 'shortcode',
24
+ 'inline-shortcode': 'inline-shortcode',
24
25
  };
25
26
 
26
27
  /**
@@ -279,6 +280,12 @@ export default function remarkToSlate({ voidCodeBlock } = {}) {
279
280
  return createBlock(typeMap[node.type], nodes, { data });
280
281
  }
281
282
 
283
+ case 'inline-shortcode': {
284
+ const nodes = [createText('')];
285
+ const data = { ...node.data, id: node.data.shortcode, shortcodeNew: true };
286
+ return createInline(typeMap[node.type], { data }, nodes);
287
+ }
288
+
282
289
  case 'text': {
283
290
  const text = node.value;
284
291
  return createText(text);
@@ -32,6 +32,7 @@ const typeMap = {
32
32
  link: 'link',
33
33
  image: 'image',
34
34
  shortcode: 'shortcode',
35
+ 'inline-shortcode': 'inline-shortcode',
35
36
  };
36
37
 
37
38
  /**
@@ -62,7 +63,7 @@ const blockTypes = [
62
63
  'table-cell',
63
64
  ];
64
65
 
65
- const inlineTypes = ['link', 'image', 'break'];
66
+ const inlineTypes = ['link', 'image', 'break', 'inline-shortcode'];
66
67
 
67
68
  const leadingWhitespaceExp = /^\s+\S/;
68
69
  const trailingWhitespaceExp = /(?!\S)\s+$/;
@@ -114,7 +115,8 @@ export default function slateToRemark(value, { voidCodeBlock }) {
114
115
  }
115
116
 
116
117
  case 'image':
117
- case 'break': {
118
+ case 'break':
119
+ case 'inline-shortcode': {
118
120
  const data = omit(node.data, 'marks');
119
121
  return { ...node, data };
120
122
  }
@@ -153,6 +155,7 @@ export default function slateToRemark(value, { voidCodeBlock }) {
153
155
 
154
156
  case 'break':
155
157
  case 'image':
158
+ case 'inline-shortcode':
156
159
  return map(get(node, ['data', 'marks']), mark => mark.type);
157
160
 
158
161
  default:
@@ -468,6 +471,14 @@ export default function slateToRemark(value, { voidCodeBlock }) {
468
471
  const { url, title, alt, ...data } = get(node, 'data', {});
469
472
  return u(typeMap[node.type], { url, title, alt, data });
470
473
  }
474
+
475
+ /**
476
+ * Inline Shortcodes
477
+ */
478
+ case 'inline-shortcode': {
479
+ const { data } = node;
480
+ return u(typeMap[node.type], { data });
481
+ }
471
482
  }
472
483
  }
473
484
  }
package/LICENSE DELETED
@@ -1,22 +0,0 @@
1
- Copyright (c) 2016 Netlify <decap@p-m.si>
2
-
3
- MIT License
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining
6
- a copy of this software and associated documentation files (the
7
- "Software"), to deal in the Software without restriction, including
8
- without limitation the rights to use, copy, modify, merge, publish,
9
- distribute, sublicense, and/or sell copies of the Software, and to
10
- permit persons to whom the Software is furnished to do so, subject to
11
- the following conditions:
12
-
13
- The above copyright notice and this permission notice shall be
14
- included in all copies or substantial portions of the Software.
15
-
16
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -1,26 +0,0 @@
1
- import isHotkey from 'is-hotkey';
2
- function BreakToDefaultBlock({
3
- defaultType
4
- }) {
5
- return {
6
- onKeyDown(event, editor, next) {
7
- const {
8
- selection,
9
- startBlock
10
- } = editor.value;
11
- const isEnter = isHotkey('enter', event);
12
- if (!isEnter) {
13
- return next();
14
- }
15
- if (selection.isExpanded) {
16
- editor.delete();
17
- return next();
18
- }
19
- if (selection.start.isAtEndOfNode(startBlock) && startBlock.type !== defaultType) {
20
- return editor.insertBlock(defaultType);
21
- }
22
- return next();
23
- }
24
- };
25
- }
26
- export default BreakToDefaultBlock;
@@ -1,28 +0,0 @@
1
- import isHotkey from 'is-hotkey';
2
- function CloseBlock({
3
- defaultType
4
- }) {
5
- return {
6
- onKeyDown(event, editor, next) {
7
- const {
8
- selection,
9
- startBlock
10
- } = editor.value;
11
- const isBackspace = isHotkey('backspace', event);
12
- if (!isBackspace) {
13
- return next();
14
- }
15
- if (selection.isExpanded) {
16
- return editor.delete();
17
- }
18
- if (!selection.start.isAtStartOfNode(startBlock) || startBlock.text.length > 0) {
19
- return next();
20
- }
21
- if (startBlock.type !== defaultType) {
22
- editor.setBlocks(defaultType);
23
- }
24
- return next();
25
- }
26
- };
27
- }
28
- export default CloseBlock;
@@ -1,180 +0,0 @@
1
- import isArray from 'lodash/isArray';
2
- import tail from 'lodash/tail';
3
- import castArray from 'lodash/castArray';
4
- function CommandsAndQueries({
5
- defaultType
6
- }) {
7
- return {
8
- queries: {
9
- atStartOf(editor, node) {
10
- const {
11
- selection
12
- } = editor.value;
13
- return selection.isCollapsed && selection.start.isAtStartOfNode(node);
14
- },
15
- getAncestor(editor, firstKey, lastKey) {
16
- if (firstKey === lastKey) {
17
- return editor.value.document.getParent(firstKey);
18
- }
19
- return editor.value.document.getCommonAncestor(firstKey, lastKey);
20
- },
21
- getOffset(editor, node) {
22
- const parent = editor.value.document.getParent(node.key);
23
- return parent.nodes.indexOf(node);
24
- },
25
- getSelectedChildren(editor, node) {
26
- return node.nodes.filter(child => editor.isSelected(child));
27
- },
28
- getCommonAncestor(editor) {
29
- const {
30
- startBlock,
31
- endBlock,
32
- document: doc
33
- } = editor.value;
34
- return doc.getCommonAncestor(startBlock.key, endBlock.key);
35
- },
36
- getClosestType(editor, node, type) {
37
- const types = castArray(type);
38
- return editor.value.document.getClosest(node.key, n => types.includes(n.type));
39
- },
40
- getBlockContainer(editor, node) {
41
- const targetTypes = ['bulleted-list', 'numbered-list', 'list-item', 'quote', 'table-cell'];
42
- const {
43
- startBlock,
44
- selection
45
- } = editor.value;
46
- const target = node ? editor.value.document.getParent(node.key) : selection.isCollapsed && startBlock || editor.getCommonAncestor();
47
- if (!target) {
48
- return editor.value.document;
49
- }
50
- if (targetTypes.includes(target.type)) {
51
- return target;
52
- }
53
- return editor.getBlockContainer(target);
54
- },
55
- isSelected(editor, nodes) {
56
- return castArray(nodes).every(node => {
57
- return editor.value.document.isInRange(node.key, editor.value.selection);
58
- });
59
- },
60
- isFirstChild(editor, node) {
61
- return editor.value.document.getParent(node.key).nodes.first().key === node.key;
62
- },
63
- areSiblings(editor, nodes) {
64
- if (!isArray(nodes) || nodes.length < 2) {
65
- return true;
66
- }
67
- const parent = editor.value.document.getParent(nodes[0].key);
68
- return tail(nodes).every(node => {
69
- return editor.value.document.getParent(node.key).key === parent.key;
70
- });
71
- },
72
- everyBlock(editor, type) {
73
- return editor.value.blocks.every(block => block.type === type);
74
- },
75
- hasMark(editor, type) {
76
- return editor.value.activeMarks.some(mark => mark.type === type);
77
- },
78
- hasBlock(editor, type) {
79
- return editor.value.blocks.some(node => node.type === type);
80
- },
81
- hasInline(editor, type) {
82
- return editor.value.inlines.some(node => node.type === type);
83
- },
84
- hasQuote(editor, quoteLabel) {
85
- const {
86
- value
87
- } = editor;
88
- const {
89
- document,
90
- blocks
91
- } = value;
92
- return blocks.some(node => {
93
- const {
94
- key: descendantNodeKey
95
- } = node;
96
- /* When focusing a quote block, the actual block that gets the focus is the paragraph block whose parent is a `quote` block.
97
- Hence, we need to get its parent and check if its type is `quote`. This parent will always be defined because every block in the editor
98
- has a Document object as parent by default.
99
- */
100
- const parent = document.getParent(descendantNodeKey);
101
- return parent.type === quoteLabel;
102
- });
103
- },
104
- hasListItems(editor, listType) {
105
- const {
106
- value
107
- } = editor;
108
- const {
109
- document,
110
- blocks
111
- } = value;
112
- return blocks.some(node => {
113
- const {
114
- key: lowestNodeKey
115
- } = node;
116
- /* A list block has the following structure:
117
- <ol>
118
- <li>
119
- <p>Coffee</p>
120
- </li>
121
- <li>
122
- <p>Tea</p>
123
- </li>
124
- </ol>
125
- */
126
- const parent = document.getParent(lowestNodeKey);
127
- const grandparent = document.getParent(parent.key);
128
- return parent.type === 'list-item' && grandparent?.type === listType;
129
- });
130
- }
131
- },
132
- commands: {
133
- toggleBlock(editor, type) {
134
- switch (type) {
135
- case 'heading-one':
136
- case 'heading-two':
137
- case 'heading-three':
138
- case 'heading-four':
139
- case 'heading-five':
140
- case 'heading-six':
141
- return editor.setBlocks(editor.everyBlock(type) ? defaultType : type);
142
- case 'quote':
143
- return editor.toggleQuoteBlock();
144
- case 'numbered-list':
145
- case 'bulleted-list':
146
- {
147
- return editor.toggleList(type);
148
- }
149
- }
150
- },
151
- unwrapBlockChildren(editor, block) {
152
- if (!block || block.object !== 'block') {
153
- throw Error(`Expected block but received ${block}.`);
154
- }
155
- const index = editor.value.document.getPath(block.key).last();
156
- const parent = editor.value.document.getParent(block.key);
157
- editor.withoutNormalizing(() => {
158
- block.nodes.forEach((node, idx) => {
159
- editor.moveNodeByKey(node.key, parent.key, index + idx);
160
- });
161
- editor.removeNodeByKey(block.key);
162
- });
163
- },
164
- unwrapNodeToDepth(editor, node, depth) {
165
- let currentDepth = 0;
166
- editor.withoutNormalizing(() => {
167
- while (currentDepth < depth) {
168
- editor.unwrapNodeByKey(node.key);
169
- currentDepth += 1;
170
- }
171
- });
172
- },
173
- unwrapNodeFromAncestor(editor, node, ancestor) {
174
- const depth = ancestor.getDepth(node.key);
175
- editor.unwrapNodeToDepth(node, depth);
176
- }
177
- }
178
- };
179
- }
180
- export default CommandsAndQueries;
@@ -1,42 +0,0 @@
1
- function ForceInsert({
2
- defaultType
3
- }) {
4
- return {
5
- queries: {
6
- canInsertBeforeNode(editor, node) {
7
- if (!editor.isVoid(node)) {
8
- return true;
9
- }
10
- return !!editor.value.document.getPreviousSibling(node.key);
11
- },
12
- canInsertAfterNode(editor, node) {
13
- if (!editor.isVoid(node)) {
14
- return true;
15
- }
16
- const nextSibling = editor.value.document.getNextSibling(node.key);
17
- return nextSibling && !editor.isVoid(nextSibling);
18
- }
19
- },
20
- commands: {
21
- forceInsertBeforeNode(editor, node) {
22
- const block = {
23
- type: defaultType,
24
- object: 'block'
25
- };
26
- const parent = editor.value.document.getParent(node.key);
27
- return editor.insertNodeByKey(parent.key, 0, block).moveToStartOfNode(parent).focus();
28
- },
29
- forceInsertAfterNode(editor, node) {
30
- return editor.moveToEndOfNode(node).insertBlock(defaultType).focus();
31
- },
32
- moveToEndOfDocument(editor) {
33
- const lastBlock = editor.value.document.nodes.last();
34
- if (editor.isVoid(lastBlock)) {
35
- editor.insertBlock(defaultType);
36
- }
37
- return editor.moveToEndOfNode(lastBlock).focus();
38
- }
39
- }
40
- };
41
- }
42
- export default ForceInsert;
@@ -1,26 +0,0 @@
1
- import isHotkey from 'is-hotkey';
2
- export const HOT_KEY_MAP = {
3
- bold: 'mod+b',
4
- code: 'mod+shift+c',
5
- italic: 'mod+i',
6
- strikethrough: 'mod+shift+s',
7
- 'heading-one': 'mod+1',
8
- 'heading-two': 'mod+2',
9
- 'heading-three': 'mod+3',
10
- 'heading-four': 'mod+4',
11
- 'heading-five': 'mod+5',
12
- 'heading-six': 'mod+6',
13
- link: 'mod+k'
14
- };
15
- function Hotkey(key, fn) {
16
- return {
17
- onKeyDown(event, editor, next) {
18
- if (!isHotkey(key, event)) {
19
- return next();
20
- }
21
- event.preventDefault();
22
- editor.command(fn);
23
- }
24
- };
25
- }
26
- export default Hotkey;
@@ -1,13 +0,0 @@
1
- import isHotkey from 'is-hotkey';
2
- function LineBreak() {
3
- return {
4
- onKeyDown(event, editor, next) {
5
- const isShiftEnter = isHotkey('shift+enter', event);
6
- if (!isShiftEnter) {
7
- return next();
8
- }
9
- return editor.insertInline('break').insertText('').moveToStartOfNextText();
10
- }
11
- };
12
- }
13
- export default LineBreak;