decap-cms-widget-markdown 3.4.1 → 3.6.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "decap-cms-widget-markdown",
3
3
  "description": "Widget for editing markdown in Decap CMS.",
4
- "version": "3.4.1",
4
+ "version": "3.6.0",
5
5
  "homepage": "https://www.decapcms.org/docs/widgets/#markdown",
6
6
  "repository": "https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-widget-markdown",
7
7
  "bugs": "https://github.com/decaporg/decap-cms/issues",
@@ -22,7 +22,7 @@
22
22
  },
23
23
  "dependencies": {
24
24
  "detab": "^2.0.4",
25
- "dompurify": "^2.2.6",
25
+ "dompurify": "^3.2.6",
26
26
  "is-hotkey": "^0.2.0",
27
27
  "is-url": "^1.2.4",
28
28
  "mdast-util-definitions": "^1.2.3",
@@ -35,12 +35,13 @@
35
35
  "remark-slate": "^1.8.6",
36
36
  "remark-slate-transformer": "^0.7.4",
37
37
  "remark-stringify": "^6.0.4",
38
- "slate": "^0.91.1",
38
+ "slate": "^0.118.1",
39
39
  "slate-base64-serializer": "^0.2.107",
40
- "slate-history": "^0.93.0",
41
- "slate-hyperscript": "^0.77.0",
42
- "slate-plain-serializer": "^0.7.1",
43
- "slate-react": "^0.91.2",
40
+ "slate-dom": "^0.118.1",
41
+ "slate-history": "^0.113.1",
42
+ "slate-hyperscript": "^0.100.0",
43
+ "slate-plain-serializer": "^0.7.3",
44
+ "slate-react": "^0.117.4",
44
45
  "slate-soft-break": "^0.9.0",
45
46
  "unified": "^9.2.0",
46
47
  "unist-builder": "^1.0.3",
@@ -62,5 +63,5 @@
62
63
  "commonmark": "^0.30.0",
63
64
  "commonmark-spec": "^0.30.0"
64
65
  },
65
- "gitHead": "bfe122aee875ceaaf9f132110db7f8797eedfc5b"
66
+ "gitHead": "826a300143ead63bd7515db66938a6b9ff8319ce"
66
67
  }
@@ -57,7 +57,7 @@ function RawEditor(props) {
57
57
  }
58
58
 
59
59
  return (
60
- <Slate editor={editor} value={value} onChange={handleChange}>
60
+ <Slate editor={editor} value={value} initialValue={value} onChange={handleChange}>
61
61
  <RawEditorContainer>
62
62
  <EditorControlBar>
63
63
  <Toolbar
@@ -160,6 +160,16 @@ export default class Toolbar extends React.Component {
160
160
  disabled={disabled}
161
161
  />
162
162
  )}
163
+ {isVisible('strikethrough') && (
164
+ <ToolbarButton
165
+ type="strikethrough"
166
+ label={t('editor.editorWidgets.markdown.strikethrough')}
167
+ icon="strikethrough"
168
+ onClick={this.handleMarkClick}
169
+ isActive={hasMark('delete')}
170
+ disabled={disabled}
171
+ />
172
+ )}
163
173
  {isVisible('code') && (
164
174
  <ToolbarButton
165
175
  type="code"
@@ -141,7 +141,8 @@ function Editor(props) {
141
141
 
142
142
  function handleMarkClick(format) {
143
143
  ReactEditor.focus(editor);
144
- toggleMark(editor, format);
144
+ const markFormat = format === 'strikethrough' ? 'delete' : format;
145
+ toggleMark(editor, markFormat);
145
146
  }
146
147
 
147
148
  function handleBlockClick(format) {
@@ -222,7 +223,7 @@ function Editor(props) {
222
223
  position: relative;
223
224
  `}
224
225
  >
225
- <Slate editor={editor} value={editorValue} onChange={handleChange}>
226
+ <Slate editor={editor} value={editorValue} initialValue={editorValue} onChange={handleChange}>
226
227
  <EditorControlBar>
227
228
  {
228
229
  <Toolbar
package/src/schema.js CHANGED
@@ -8,6 +8,7 @@ export default {
8
8
  enum: [
9
9
  'bold',
10
10
  'italic',
11
+ 'strikethrough',
11
12
  'code',
12
13
  'link',
13
14
  'heading-one',
@@ -0,0 +1,132 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`remarkParseShortcodes parse pattern with leading caret should be a remark shortcode node 1`] = `
4
+ Object {
5
+ "children": Array [
6
+ Object {
7
+ "data": Object {
8
+ "shortcode": "foo",
9
+ "shortcodeData": Object {
10
+ "bar": "baz",
11
+ },
12
+ },
13
+ "type": "shortcode",
14
+ },
15
+ ],
16
+ "type": "root",
17
+ }
18
+ `;
19
+
20
+ exports[`remarkParseShortcodes parse pattern with leading caret should parse multiple shortcodes 1`] = `
21
+ Object {
22
+ "children": Array [
23
+ Object {
24
+ "children": Array [
25
+ Object {
26
+ "type": "text",
27
+ "value": "paragraph",
28
+ },
29
+ ],
30
+ "type": "paragraph",
31
+ },
32
+ Object {
33
+ "data": Object {
34
+ "shortcode": "foo",
35
+ "shortcodeData": Object {
36
+ "bar": "bar",
37
+ },
38
+ },
39
+ "type": "shortcode",
40
+ },
41
+ Object {
42
+ "data": Object {
43
+ "shortcode": "foo",
44
+ "shortcodeData": Object {
45
+ "bar": "baz",
46
+ },
47
+ },
48
+ "type": "shortcode",
49
+ },
50
+ Object {
51
+ "children": Array [
52
+ Object {
53
+ "type": "text",
54
+ "value": "next para",
55
+ },
56
+ ],
57
+ "type": "paragraph",
58
+ },
59
+ ],
60
+ "type": "root",
61
+ }
62
+ `;
63
+
64
+ exports[`remarkParseShortcodes parse pattern without leading caret should handle pattern without leading caret 1`] = `
65
+ Object {
66
+ "children": Array [
67
+ Object {
68
+ "children": Array [
69
+ Object {
70
+ "type": "text",
71
+ "value": "paragraph",
72
+ },
73
+ ],
74
+ "type": "paragraph",
75
+ },
76
+ Object {
77
+ "data": Object {
78
+ "shortcode": "foo",
79
+ "shortcodeData": Object {
80
+ "bar": "baz",
81
+ },
82
+ },
83
+ "type": "shortcode",
84
+ },
85
+ ],
86
+ "type": "root",
87
+ }
88
+ `;
89
+
90
+ exports[`remarkParseShortcodes parse pattern without leading caret should parse multiple shortcodes 1`] = `
91
+ Object {
92
+ "children": Array [
93
+ Object {
94
+ "children": Array [
95
+ Object {
96
+ "type": "text",
97
+ "value": "paragraph",
98
+ },
99
+ ],
100
+ "type": "paragraph",
101
+ },
102
+ Object {
103
+ "data": Object {
104
+ "shortcode": "foo",
105
+ "shortcodeData": Object {
106
+ "bar": "bar",
107
+ },
108
+ },
109
+ "type": "shortcode",
110
+ },
111
+ Object {
112
+ "data": Object {
113
+ "shortcode": "foo",
114
+ "shortcodeData": Object {
115
+ "bar": "baz",
116
+ },
117
+ },
118
+ "type": "shortcode",
119
+ },
120
+ Object {
121
+ "children": Array [
122
+ Object {
123
+ "type": "text",
124
+ "value": "next para",
125
+ },
126
+ ],
127
+ "type": "paragraph",
128
+ },
129
+ ],
130
+ "type": "root",
131
+ }
132
+ `;
@@ -1,18 +1,14 @@
1
1
  import { Map, OrderedMap } from 'immutable';
2
+ import unified from 'unified';
3
+ import markdownToRemarkPlugin from 'remark-parse';
2
4
 
3
5
  import { remarkParseShortcodes, getLinesWithOffsets } from '../remarkShortcodes';
4
6
 
5
- // Stub of Remark Parser
6
- function process(value, plugins, processEat = () => {}) {
7
- function eat() {
8
- return processEat;
9
- }
10
-
11
- function Parser() {}
12
- Parser.prototype.blockTokenizers = {};
13
- Parser.prototype.blockMethods = [];
14
- remarkParseShortcodes.call({ Parser }, { plugins });
15
- Parser.prototype.blockTokenizers.shortcode(eat, value);
7
+ function process(value, plugins) {
8
+ return unified()
9
+ .use(markdownToRemarkPlugin, { fences: true, commonmark: true })
10
+ .use(remarkParseShortcodes, { plugins })
11
+ .parse(value);
16
12
  }
17
13
 
18
14
  function EditorComponent({ id = 'foo', fromBlock = jest.fn(), pattern }) {
@@ -25,16 +21,6 @@ function EditorComponent({ id = 'foo', fromBlock = jest.fn(), pattern }) {
25
21
 
26
22
  describe('remarkParseShortcodes', () => {
27
23
  describe('pattern matching', () => {
28
- it('should work', () => {
29
- const editorComponent = EditorComponent({ pattern: /bar/ });
30
- process('foo bar', Map({ [editorComponent.id]: editorComponent }));
31
- expect(editorComponent.fromBlock).toHaveBeenCalledWith(expect.arrayContaining(['bar']));
32
- });
33
- it('should match value surrounded in newlines', () => {
34
- const editorComponent = EditorComponent({ pattern: /^bar$/ });
35
- process('foo\n\nbar\n', Map({ [editorComponent.id]: editorComponent }));
36
- expect(editorComponent.fromBlock).toHaveBeenCalledWith(expect.arrayContaining(['bar']));
37
- });
38
24
  it('should match multiline shortcodes', () => {
39
25
  const editorComponent = EditorComponent({ pattern: /^foo\nbar$/ });
40
26
  process('foo\nbar', Map({ [editorComponent.id]: editorComponent }));
@@ -72,16 +58,69 @@ describe('remarkParseShortcodes', () => {
72
58
  expect(barEditorComponent.fromBlock).toHaveBeenCalledWith(expect.arrayContaining(['bar']));
73
59
  });
74
60
  });
75
- describe('output', () => {
76
- it('should be a remark shortcode node', () => {
77
- const processEat = jest.fn();
78
- const shortcodeData = { bar: 'baz' };
79
- const expectedNode = { type: 'shortcode', data: { shortcode: 'foo', shortcodeData } };
80
- const editorComponent = EditorComponent({ pattern: /bar/, fromBlock: () => shortcodeData });
81
- process('foo bar', Map({ [editorComponent.id]: editorComponent }), processEat);
82
- expect(processEat).toHaveBeenCalledWith(expectedNode);
61
+ describe('parse', () => {
62
+ describe('pattern with leading caret', () => {
63
+ it('should be a remark shortcode node', () => {
64
+ const editorComponent = EditorComponent({
65
+ pattern: /^foo (?<bar>.+)$/,
66
+ fromBlock: ({ groups }) => ({ bar: groups.bar }),
67
+ });
68
+ const mdast = process('foo baz', Map({ [editorComponent.id]: editorComponent }));
69
+ expect(removePositions(mdast)).toMatchSnapshot();
70
+ });
71
+ it('should parse multiple shortcodes', () => {
72
+ const editorComponent = EditorComponent({
73
+ pattern: /foo (?<bar>.+)/,
74
+ fromBlock: ({ groups }) => ({ bar: groups.bar }),
75
+ });
76
+ const mdast = process(
77
+ 'paragraph\n\nfoo bar\n\nfoo baz\n\nnext para',
78
+ Map({ [editorComponent.id]: editorComponent }),
79
+ );
80
+ expect(removePositions(mdast)).toMatchSnapshot();
81
+ });
82
+ });
83
+ describe('pattern without leading caret', () => {
84
+ it('should handle pattern without leading caret', () => {
85
+ const editorComponent = EditorComponent({
86
+ pattern: /foo (?<bar>.+)/,
87
+ fromBlock: ({ groups }) => ({ bar: groups.bar }),
88
+ });
89
+ const mdast = process(
90
+ 'paragraph\n\nfoo baz',
91
+ Map({ [editorComponent.id]: editorComponent }),
92
+ );
93
+ expect(removePositions(mdast)).toMatchSnapshot();
94
+ });
95
+ it('should parse multiple shortcodes', () => {
96
+ const editorComponent = EditorComponent({
97
+ pattern: /foo (?<bar>.+)/,
98
+ fromBlock: ({ groups }) => ({ bar: groups.bar }),
99
+ });
100
+ const mdast = process(
101
+ 'paragraph\n\nfoo bar\n\nfoo baz\n\nnext para',
102
+ Map({ [editorComponent.id]: editorComponent }),
103
+ );
104
+ expect(removePositions(mdast)).toMatchSnapshot();
105
+ });
83
106
  });
84
107
  });
108
+
109
+ function removePositions(obj) {
110
+ if (Array.isArray(obj)) {
111
+ return obj.map(removePositions);
112
+ }
113
+ if (obj && typeof obj === 'object') {
114
+ // eslint-disable-next-line no-unused-vars
115
+ const { position, ...rest } = obj;
116
+ const result = {};
117
+ for (const key in rest) {
118
+ result[key] = removePositions(rest[key]);
119
+ }
120
+ return result;
121
+ }
122
+ return obj;
123
+ }
85
124
  });
86
125
 
87
126
  describe('getLinesWithOffsets', () => {
@@ -33,36 +33,28 @@ export function getLinesWithOffsets(value) {
33
33
  return trimmedLines;
34
34
  }
35
35
 
36
- function matchFromLines({ trimmedLines, plugin }) {
37
- for (const { line, start } of trimmedLines) {
38
- const match = line.match(plugin.pattern);
39
- if (match) {
40
- match.index += start;
41
- return match;
42
- }
43
- }
44
- }
45
-
46
36
  function createShortcodeTokenizer({ plugins }) {
47
37
  return function tokenizeShortcode(eat, value, silent) {
48
- // Plugin patterns may rely on `^` and `$` tokens, even if they don't
49
- // use the multiline flag. To support this, we fall back to searching
50
- // through each line individually, trimming trailing whitespace and
51
- // newlines, if we don't initially match on a pattern. We keep track of
52
- // the starting position of each line so that we can sort correctly
53
- // across the full multiline matches.
54
- const trimmedLines = getLinesWithOffsets(value);
55
-
56
38
  // Attempt to find a regex match for each plugin's pattern, and then
57
39
  // select the first by its occurrence in `value`. This ensures we won't
58
40
  // skip a plugin that occurs later in the plugin registry, but earlier
59
41
  // in the `value`.
60
42
  const [{ plugin, match } = {}] = plugins
61
43
  .toArray()
62
- .map(plugin => ({
63
- match: value.match(plugin.pattern) || matchFromLines({ trimmedLines, plugin }),
64
- plugin,
65
- }))
44
+ .map(plugin => {
45
+ let { pattern } = plugin;
46
+ // Plugin patterns must start with a caret (^) to match the beginning of the line.
47
+ // If the pattern does not start with a caret, we add it
48
+ // to ensure that remark consumes only the shortcode, without any leading text.
49
+ if (!pattern.source.startsWith('^')) {
50
+ pattern = new RegExp(`^${pattern.source}`, pattern.flags);
51
+ }
52
+
53
+ return {
54
+ match: value.match(pattern),
55
+ plugin,
56
+ };
57
+ })
66
58
  .filter(({ match }) => !!match)
67
59
  .sort((a, b) => a.match.index - b.match.index);
68
60