decap-cms-widget-markdown 3.12.0 → 3.12.1
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 +6 -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/index.js +2 -2
- package/dist/esm/MarkdownPreview.js +37 -3
- package/dist/esm/serializers/remarkRehypeShortcodes.js +2 -3
- package/package.json +23 -13
- package/src/MarkdownControl/Toolbar.js +2 -2
- package/src/MarkdownControl/index.js +2 -2
- package/src/MarkdownPreview.js +38 -3
- package/src/__tests__/renderer.spec.js +68 -0
- package/src/serializers/remarkRehypeShortcodes.js +2 -3
- 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
|
@@ -207,6 +207,74 @@ I get 10 times more traffic from [Google] than from [Yahoo] or [MSN].
|
|
|
207
207
|
expect(img).not.toHaveAttribute('onerror');
|
|
208
208
|
});
|
|
209
209
|
|
|
210
|
+
it('should preserve same-origin blob: URLs used for not-yet-committed asset previews', () => {
|
|
211
|
+
// Editors preview a selected-but-not-yet-committed image via URL.createObjectURL(),
|
|
212
|
+
// which produces a blob: URL - DOMPurify's default ALLOWED_URI_REGEXP doesn't include
|
|
213
|
+
// that scheme, so it silently stripped `src` here once sanitize_preview defaulted to
|
|
214
|
+
// true, even though the image itself is entirely local and safe. A real
|
|
215
|
+
// URL.createObjectURL() call always embeds the current document's own origin, so the
|
|
216
|
+
// fixture must too - a cross-origin value isn't something this exception should (or, per
|
|
217
|
+
// the test below, does) preserve.
|
|
218
|
+
const blobUrl = `blob:${window.location.origin}/1234-5678-90ab`;
|
|
219
|
+
const value = `<img src="${blobUrl}">`;
|
|
220
|
+
const field = Map({ sanitize_preview: true });
|
|
221
|
+
|
|
222
|
+
const { container } = render(
|
|
223
|
+
<MarkdownPreview
|
|
224
|
+
value={value}
|
|
225
|
+
getAsset={jest.fn()}
|
|
226
|
+
resolveWidget={jest.fn()}
|
|
227
|
+
field={field}
|
|
228
|
+
/>,
|
|
229
|
+
);
|
|
230
|
+
const img = container.querySelector('img');
|
|
231
|
+
expect(img).toHaveAttribute('src', blobUrl);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('should NOT preserve blob: URLs on non-<img> attributes', () => {
|
|
235
|
+
// The blob: exception above must be scoped to <img src>. Widening DOMPurify's global
|
|
236
|
+
// ALLOWED_URI_REGEXP instead of using a node-scoped hook would let blob: through on any
|
|
237
|
+
// URI-bearing attribute - including <a href> and <form action>, both of which could be
|
|
238
|
+
// used to smuggle attacker-controlled blob content (see PR review discussion). Uses a
|
|
239
|
+
// same-origin blob: URL so this test isolates the tag/attribute scoping specifically,
|
|
240
|
+
// independent of the separate cross-origin check below.
|
|
241
|
+
const blobUrl = `blob:${window.location.origin}/should-be-stripped`;
|
|
242
|
+
const value = [`<a href="${blobUrl}">click</a>`, `<form action="${blobUrl}"></form>`].join(
|
|
243
|
+
'',
|
|
244
|
+
);
|
|
245
|
+
const field = Map({ sanitize_preview: true });
|
|
246
|
+
|
|
247
|
+
const { container } = render(
|
|
248
|
+
<MarkdownPreview
|
|
249
|
+
value={value}
|
|
250
|
+
getAsset={jest.fn()}
|
|
251
|
+
resolveWidget={jest.fn()}
|
|
252
|
+
field={field}
|
|
253
|
+
/>,
|
|
254
|
+
);
|
|
255
|
+
expect(container.querySelector('a')).not.toHaveAttribute('href');
|
|
256
|
+
expect(container.querySelector('form')).not.toHaveAttribute('action');
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('should NOT preserve a cross-origin blob: URL even on <img src>', () => {
|
|
260
|
+
// A blob: URL embeds the origin that created it and a browser already refuses to
|
|
261
|
+
// dereference one from a different origin, but the sanitizer should reject it outright
|
|
262
|
+
// rather than pass through a value that could not be one of this CMS instance's own
|
|
263
|
+
// asset previews.
|
|
264
|
+
const value = '<img src="blob:https://attacker.example/1234-5678-90ab">';
|
|
265
|
+
const field = Map({ sanitize_preview: true });
|
|
266
|
+
|
|
267
|
+
const { container } = render(
|
|
268
|
+
<MarkdownPreview
|
|
269
|
+
value={value}
|
|
270
|
+
getAsset={jest.fn()}
|
|
271
|
+
resolveWidget={jest.fn()}
|
|
272
|
+
field={field}
|
|
273
|
+
/>,
|
|
274
|
+
);
|
|
275
|
+
expect(container.querySelector('img')).not.toHaveAttribute('src');
|
|
276
|
+
});
|
|
277
|
+
|
|
210
278
|
it('should sanitize dangerous link protocols', () => {
|
|
211
279
|
const value = '<a href="javascript:alert(1)">click</a>';
|
|
212
280
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
1
|
import map from 'lodash/map';
|
|
3
2
|
import has from 'lodash/has';
|
|
4
3
|
import { renderToString } from 'react-dom/server';
|
|
5
4
|
import u from 'unist-builder';
|
|
5
|
+
import { createElement } from 'react';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* This plugin doesn't actually transform Remark (MDAST) nodes to Rehype
|
|
@@ -58,7 +58,6 @@ export default function remarkToRehypeShortcodes({ plugins, getAsset, resolveWid
|
|
|
58
58
|
if (toPreview) {
|
|
59
59
|
return toPreview(shortcodeData, getAsset, fields);
|
|
60
60
|
}
|
|
61
|
-
|
|
62
61
|
/**
|
|
63
62
|
* For editor components without a custom `toPreview` (e.g. container
|
|
64
63
|
* components with nested markdown/richtext fields), render each sub-field
|
|
@@ -88,7 +87,7 @@ export default function remarkToRehypeShortcodes({ plugins, getAsset, resolveWid
|
|
|
88
87
|
* Last resort fallback: try resolving the widget and rendering its preview.
|
|
89
88
|
*/
|
|
90
89
|
const preview = resolveWidget(plugin.widget);
|
|
91
|
-
return
|
|
90
|
+
return createElement(preview.preview, {
|
|
92
91
|
value: shortcodeData,
|
|
93
92
|
field: plugin,
|
|
94
93
|
getAsset,
|
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;
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
function Link({
|
|
2
|
-
type
|
|
3
|
-
}) {
|
|
4
|
-
return {
|
|
5
|
-
commands: {
|
|
6
|
-
toggleLink(editor, getUrl) {
|
|
7
|
-
const selection = editor.value.selection;
|
|
8
|
-
const isCollapsed = selection && selection.isCollapsed;
|
|
9
|
-
if (editor.hasInline(type)) {
|
|
10
|
-
const inlines = editor.value.inlines.toJSON();
|
|
11
|
-
const link = inlines.find(item => item.type === type);
|
|
12
|
-
const url = getUrl(link.data.url);
|
|
13
|
-
if (url) {
|
|
14
|
-
// replace the old link
|
|
15
|
-
return editor.setInlines({
|
|
16
|
-
data: {
|
|
17
|
-
url
|
|
18
|
-
}
|
|
19
|
-
});
|
|
20
|
-
} else {
|
|
21
|
-
// remove url if it was removed by the user
|
|
22
|
-
return editor.unwrapInline(type);
|
|
23
|
-
}
|
|
24
|
-
} else {
|
|
25
|
-
const url = getUrl();
|
|
26
|
-
if (!url) {
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
return isCollapsed ? editor.insertInline({
|
|
30
|
-
type,
|
|
31
|
-
data: {
|
|
32
|
-
url
|
|
33
|
-
},
|
|
34
|
-
nodes: [{
|
|
35
|
-
object: 'text',
|
|
36
|
-
text: url
|
|
37
|
-
}]
|
|
38
|
-
}) : editor.wrapInline({
|
|
39
|
-
type,
|
|
40
|
-
data: {
|
|
41
|
-
url
|
|
42
|
-
}
|
|
43
|
-
}).moveToEnd();
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
export default Link;
|
package/dist/esm/MarkdownControl/plugins/lists/locations/isCursorInItemContainingNestedList.js
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import getListContainedInListItem from '../selectors/getListContainedInListItem';
|
|
2
|
-
function isCursorInItemContainingNestedList(editor) {
|
|
3
|
-
const nestedList = getListContainedInListItem(editor);
|
|
4
|
-
return !!nestedList && `${nestedList[0].type}`.endsWith('-list');
|
|
5
|
-
}
|
|
6
|
-
export default isCursorInItemContainingNestedList;
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import castArray from 'lodash/castArray';
|
|
2
|
-
import isArray from 'lodash/isArray';
|
|
3
|
-
export function assertType(nodes, type) {
|
|
4
|
-
const nodesArray = castArray(nodes);
|
|
5
|
-
const validate = isArray(type) ? node => type.includes(node.type) : node => type === node.type;
|
|
6
|
-
const invalidNode = nodesArray.find(node => !validate(node));
|
|
7
|
-
if (invalidNode) {
|
|
8
|
-
throw Error(`Expected node of type "${type}", received "${invalidNode.type}".`);
|
|
9
|
-
}
|
|
10
|
-
return true;
|
|
11
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Images must be parsed as shortcodes for asset proxying. This plugin converts
|
|
3
|
-
* MDAST image nodes back to text to allow shortcode pattern matching. Note that
|
|
4
|
-
* this transformation only occurs for images that are the sole child of a top
|
|
5
|
-
* level paragraph - any other image is left alone and treated as an inline
|
|
6
|
-
* image.
|
|
7
|
-
*/
|
|
8
|
-
export default function remarkImagesToText() {
|
|
9
|
-
return transform;
|
|
10
|
-
function transform(node) {
|
|
11
|
-
const children = node.children.map(child => {
|
|
12
|
-
if (child.type === 'paragraph' && child.children.length === 1 && child.children[0].type === 'image') {
|
|
13
|
-
const {
|
|
14
|
-
alt,
|
|
15
|
-
url,
|
|
16
|
-
title
|
|
17
|
-
} = child.children[0];
|
|
18
|
-
const value = ``;
|
|
19
|
-
child.children = [{
|
|
20
|
-
type: 'text',
|
|
21
|
-
value
|
|
22
|
-
}];
|
|
23
|
-
}
|
|
24
|
-
return child;
|
|
25
|
-
});
|
|
26
|
-
return {
|
|
27
|
-
...node,
|
|
28
|
-
children
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
}
|
package/dist/esm/types.js
DELETED