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.
- package/CHANGELOG.md +12 -0
- package/dist/decap-cms-widget-markdown.js +6 -6
- package/dist/decap-cms-widget-markdown.js.map +1 -1
- package/dist/esm/MarkdownControl/Toolbar.js +6 -6
- package/dist/esm/MarkdownControl/VisualEditor.js +7 -3
- package/dist/esm/MarkdownControl/components/InlineShortcode.js +67 -0
- package/dist/esm/MarkdownControl/index.js +2 -2
- package/dist/esm/MarkdownControl/plugins/shortcodes/insertShortcode.js +53 -4
- package/dist/esm/MarkdownControl/plugins/shortcodes/withShortcodes.js +11 -1
- package/dist/esm/MarkdownControl/renderers.js +21 -16
- package/dist/esm/MarkdownPreview.js +37 -3
- package/dist/esm/serializers/remarkRehypeShortcodes.js +18 -15
- package/dist/esm/serializers/remarkShortcodes.js +118 -12
- package/dist/esm/serializers/remarkSlate.js +14 -1
- package/dist/esm/serializers/slateRemark.js +18 -2
- package/package.json +23 -13
- package/src/MarkdownControl/Toolbar.js +2 -2
- package/src/MarkdownControl/VisualEditor.js +5 -1
- package/src/MarkdownControl/components/InlineShortcode.js +77 -0
- package/src/MarkdownControl/components/__tests__/InlineShortcode.spec.js +81 -0
- package/src/MarkdownControl/index.js +2 -2
- package/src/MarkdownControl/plugins/shortcodes/__tests__/insertShortcode.spec.js +106 -0
- package/src/MarkdownControl/plugins/shortcodes/insertShortcode.js +59 -6
- package/src/MarkdownControl/plugins/shortcodes/withShortcodes.js +12 -2
- package/src/MarkdownControl/renderers.js +3 -0
- package/src/MarkdownPreview.js +38 -3
- package/src/__tests__/renderer.spec.js +68 -0
- package/src/serializers/__tests__/remarkShortcodes.spec.js +210 -1
- package/src/serializers/__tests__/slate.spec.js +80 -0
- package/src/serializers/remarkRehypeShortcodes.js +19 -9
- package/src/serializers/remarkShortcodes.js +140 -12
- package/src/serializers/remarkSlate.js +7 -0
- package/src/serializers/slateRemark.js +13 -2
- package/LICENSE +0 -22
- package/dist/esm/MarkdownControl/plugins/BreakToDefaultBlock.js +0 -26
- package/dist/esm/MarkdownControl/plugins/CloseBlock.js +0 -28
- package/dist/esm/MarkdownControl/plugins/CommandsAndQueries.js +0 -180
- package/dist/esm/MarkdownControl/plugins/ForceInsert.js +0 -42
- package/dist/esm/MarkdownControl/plugins/Hotkey.js +0 -26
- package/dist/esm/MarkdownControl/plugins/LineBreak.js +0 -13
- package/dist/esm/MarkdownControl/plugins/Link.js +0 -49
- package/dist/esm/MarkdownControl/plugins/lists/locations/isCursorInItemContainingNestedList.js +0 -6
- package/dist/esm/MarkdownControl/plugins/util.js +0 -11
- package/dist/esm/serializers/remarkImagesToText.js +0 -31
- 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
|
-
|
|
18
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
4
|
-
const
|
|
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
|
-
|
|
8
|
+
blockTokenizers.shortcode = createShortcodeTokenizer({ plugins });
|
|
9
|
+
blockMethods.unshift('shortcode');
|
|
7
10
|
|
|
8
|
-
|
|
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
|
-
|
|
28
|
-
pattern = new RegExp(`^${pattern.source}`, pattern.flags);
|
|
29
|
-
}
|
|
45
|
+
pattern = createPattern(pattern, { anchored: true });
|
|
30
46
|
|
|
31
|
-
match =
|
|
47
|
+
match = pattern.exec(value);
|
|
32
48
|
if (!match) {
|
|
33
|
-
match =
|
|
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
|
|
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
|
-
|
|
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;
|