decap-cms-widget-markdown 3.7.0 → 3.8.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.
@@ -7,6 +7,7 @@ import remarkToRehype from 'remark-rehype';
7
7
  import rehypeToHtml from 'rehype-stringify';
8
8
  import htmlToRehype from 'rehype-parse';
9
9
  import rehypeToRemark from 'rehype-remark';
10
+ import rehypeRemoveComments from 'rehype-remove-comments';
10
11
  import remarkToRehypeShortcodes from './remarkRehypeShortcodes';
11
12
  import rehypePaperEmoji from './rehypePaperEmoji';
12
13
  import remarkAssertParents from './remarkAssertParents';
@@ -154,10 +155,23 @@ export function markdownToHtml(markdown, {
154
155
  remarkPlugins = []
155
156
  } = {}) {
156
157
  const mdast = markdownToRemark(markdown, remarkPlugins);
158
+ const editorComponents = getEditorComponents();
159
+
160
+ /**
161
+ * Provide a `toHtml` callback so `remarkToRehypeShortcodes` can recursively
162
+ * render markdown/richtext sub-fields of container editor components.
163
+ */
164
+ function toHtml(md) {
165
+ return markdownToHtml(md, {
166
+ getAsset,
167
+ resolveWidget
168
+ });
169
+ }
157
170
  const hast = unified().use(remarkToRehypeShortcodes, {
158
- plugins: getEditorComponents(),
171
+ plugins: editorComponents,
159
172
  getAsset,
160
- resolveWidget
173
+ resolveWidget,
174
+ toHtml
161
175
  }).use(remarkToRehype, {
162
176
  allowDangerousHTML: true
163
177
  }).runSync(mdast);
@@ -180,7 +194,9 @@ export function htmlToSlate(html) {
180
194
  const hast = unified().use(htmlToRehype, {
181
195
  fragment: true
182
196
  }).parse(html);
183
- const mdast = unified().use(rehypePaperEmoji).use(rehypeToRemark, {
197
+ const mdast = unified().use(rehypePaperEmoji).use(rehypeRemoveComments, {
198
+ removeConditional: true
199
+ }).use(rehypeToRemark, {
184
200
  minify: false
185
201
  }).runSync(hast);
186
202
  const slateRaw = unified().use(remarkAssertParents).use(remarkPaddedLinks).use(remarkWrapHtml).use(remarkToSlate).runSync(mdast);
@@ -13,7 +13,8 @@ import u from 'unist-builder';
13
13
  export default function remarkToRehypeShortcodes({
14
14
  plugins,
15
15
  getAsset,
16
- resolveWidget
16
+ resolveWidget,
17
+ toHtml
17
18
  }) {
18
19
  return transform;
19
20
  function transform(root) {
@@ -68,13 +69,35 @@ export default function remarkToRehypeShortcodes({
68
69
  function getPreview(plugin, shortcodeData) {
69
70
  const {
70
71
  toPreview,
71
- widget,
72
72
  fields
73
73
  } = plugin;
74
74
  if (toPreview) {
75
75
  return toPreview(shortcodeData, getAsset, fields);
76
76
  }
77
- const preview = resolveWidget(widget);
77
+
78
+ /**
79
+ * For editor components without a custom `toPreview` (e.g. container
80
+ * components with nested markdown/richtext fields), render each sub-field
81
+ * value using the appropriate widget preview.
82
+ */
83
+ if (fields && fields.size > 0 && toHtml) {
84
+ const htmlParts = fields.map(field => {
85
+ const name = field.get('name');
86
+ const widget = field.get('widget') || 'string';
87
+ const fieldValue = shortcodeData ? shortcodeData[name] : '';
88
+ if (!fieldValue) return '';
89
+ if (widget === 'markdown' || widget === 'richtext') {
90
+ return toHtml(fieldValue);
91
+ }
92
+ return `<p>${fieldValue}</p>`;
93
+ }).toArray();
94
+ return htmlParts.join('');
95
+ }
96
+
97
+ /**
98
+ * Last resort fallback: try resolving the widget and rendering its preview.
99
+ */
100
+ const preview = resolveWidget(plugin.widget);
78
101
  return /*#__PURE__*/React.createElement(preview.preview, {
79
102
  value: shortcodeData,
80
103
  field: plugin,
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.7.0",
4
+ "version": "3.8.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,13 +22,14 @@
22
22
  },
23
23
  "dependencies": {
24
24
  "detab": "^2.0.4",
25
- "dompurify": "^3.3.3",
25
+ "dompurify": "^3.4.0",
26
26
  "is-hotkey": "^0.2.0",
27
27
  "is-url": "^1.2.4",
28
28
  "mdast-util-definitions": "^1.2.3",
29
29
  "mdast-util-to-string": "^1.0.5",
30
30
  "rehype-parse": "^6.0.0",
31
31
  "rehype-remark": "^8.0.0",
32
+ "rehype-remove-comments": "^4.0.2",
32
33
  "rehype-stringify": "^7.0.0",
33
34
  "remark-parse": "^6.0.3",
34
35
  "remark-rehype": "^4.0.0",
@@ -63,5 +64,5 @@
63
64
  "commonmark": "^0.30.0",
64
65
  "commonmark-spec": "^0.30.0"
65
66
  },
66
- "gitHead": "af84ddd0532948c38a9da26026404518a59d0903"
67
+ "gitHead": "45c9f5b9a1a12f74321ce4658b71ec88d6365ec1"
67
68
  }
@@ -47,7 +47,11 @@ export default class MarkdownControl extends React.Component {
47
47
  _getEditorComponents = props.getEditorComponents;
48
48
  this.state = {
49
49
  mode:
50
- this.getAllowedModes().indexOf(preferredMode) !== -1
50
+ // When used inside a container/shortcode editor component, default to
51
+ // raw mode — the widget type already implies the editing surface.
52
+ props.isEditorComponent
53
+ ? 'raw'
54
+ : this.getAllowedModes().indexOf(preferredMode) !== -1
51
55
  ? preferredMode
52
56
  : this.getAllowedModes()[0],
53
57
  pendingFocus: false,
@@ -57,6 +61,12 @@ export default class MarkdownControl extends React.Component {
57
61
  componentDidMount() {
58
62
  // Manually validate PropTypes - React 19 breaking change
59
63
  PropTypes.checkPropTypes(MarkdownControl.propTypes, this.props, 'prop', 'MarkdownControl');
64
+
65
+ // Ensure containerised widgets start in the correct mode even if the
66
+ // constructor ran before the prop was available (e.g. HMR / late prop).
67
+ if (this.props.isEditorComponent && this.state.mode !== 'raw') {
68
+ this.setState({ mode: 'raw' });
69
+ }
60
70
  }
61
71
 
62
72
  handleMode = mode => {
@@ -92,7 +102,8 @@ export default class MarkdownControl extends React.Component {
92
102
  } = this.props;
93
103
 
94
104
  const { mode, pendingFocus } = this.state;
95
- const isShowModeToggle = this.getAllowedModes().length > 1;
105
+ const isEditorComponent = this.props.isEditorComponent;
106
+ const isShowModeToggle = this.getAllowedModes().length > 1 && !isEditorComponent;
96
107
  const visualEditor = (
97
108
  <div className="cms-editor-visual" ref={this.processRef}>
98
109
  <VisualEditor
@@ -37,7 +37,7 @@ const INLINE_STYLES = {
37
37
 
38
38
  function deserialize(el) {
39
39
  if (el.nodeType === 3) {
40
- return el.textContent;
40
+ return el.textContent.replace(/(\r)?\n/g, '');
41
41
  } else if (el.nodeType !== 1) {
42
42
  return null;
43
43
  } else if (el.nodeName === 'BR') {
@@ -6,8 +6,7 @@ function insertShortcode(editor, pluginConfig) {
6
6
  const defaultValues = pluginConfig.fields
7
7
  .toMap()
8
8
  .mapKeys((_, field) => field.get('name'))
9
- .filter(field => field.has('default'))
10
- .map(field => field.get('default'));
9
+ .map(field => field.get('default', ''));
11
10
 
12
11
  const nodeData = {
13
12
  type: 'shortcode',
@@ -97,7 +97,7 @@ const StyledTable = styled.table`
97
97
  `;
98
98
 
99
99
  const StyledTd = styled.td`
100
- border: 2px solid black;
100
+ border: 1px solid black;
101
101
  padding: 8px;
102
102
  text-align: left;
103
103
  `;
@@ -49,4 +49,19 @@ describe('htmlToSlate', () => {
49
49
  ],
50
50
  });
51
51
  });
52
+
53
+ it('should remove HTML comments', () => {
54
+ const html = `<!--[if gte mso 9]><xml><o:OfficeDocumentSettings><o:AllowPNG/></o:OfficeDocumentSettings></xml><![endif]--><span>regular text</span>`;
55
+
56
+ const actual = htmlToSlate(html);
57
+ expect(actual).toEqual({
58
+ type: 'root',
59
+ children: [
60
+ {
61
+ type: 'paragraph',
62
+ children: [{ text: 'regular text' }],
63
+ },
64
+ ],
65
+ });
66
+ });
52
67
  });
@@ -7,6 +7,7 @@ import remarkToRehype from 'remark-rehype';
7
7
  import rehypeToHtml from 'rehype-stringify';
8
8
  import htmlToRehype from 'rehype-parse';
9
9
  import rehypeToRemark from 'rehype-remark';
10
+ import rehypeRemoveComments from 'rehype-remove-comments';
10
11
 
11
12
  import remarkToRehypeShortcodes from './remarkRehypeShortcodes';
12
13
  import rehypePaperEmoji from './rehypePaperEmoji';
@@ -157,8 +158,23 @@ export function remarkToMarkdown(obj, remarkPlugins) {
157
158
  export function markdownToHtml(markdown, { getAsset, resolveWidget, remarkPlugins = [] } = {}) {
158
159
  const mdast = markdownToRemark(markdown, remarkPlugins);
159
160
 
161
+ const editorComponents = getEditorComponents();
162
+
163
+ /**
164
+ * Provide a `toHtml` callback so `remarkToRehypeShortcodes` can recursively
165
+ * render markdown/richtext sub-fields of container editor components.
166
+ */
167
+ function toHtml(md) {
168
+ return markdownToHtml(md, { getAsset, resolveWidget });
169
+ }
170
+
160
171
  const hast = unified()
161
- .use(remarkToRehypeShortcodes, { plugins: getEditorComponents(), getAsset, resolveWidget })
172
+ .use(remarkToRehypeShortcodes, {
173
+ plugins: editorComponents,
174
+ getAsset,
175
+ resolveWidget,
176
+ toHtml,
177
+ })
162
178
  .use(remarkToRehype, { allowDangerousHTML: true })
163
179
  .runSync(mdast);
164
180
 
@@ -183,6 +199,7 @@ export function htmlToSlate(html) {
183
199
 
184
200
  const mdast = unified()
185
201
  .use(rehypePaperEmoji)
202
+ .use(rehypeRemoveComments, { removeConditional: true })
186
203
  .use(rehypeToRemark, { minify: false })
187
204
  .runSync(hast);
188
205
 
@@ -222,5 +239,6 @@ export function markdownToSlate(markdown, { voidCodeBlock, remarkPlugins = [] }
222
239
  export function slateToMarkdown(raw, { voidCodeBlock, remarkPlugins = [] } = {}) {
223
240
  const mdast = slateToRemark(raw, { voidCodeBlock });
224
241
  const markdown = remarkToMarkdown(mdast, remarkPlugins);
242
+
225
243
  return markdown;
226
244
  }
@@ -10,7 +10,7 @@ import u from 'unist-builder';
10
10
  * conversion by replacing the shortcode text with stringified HTML for
11
11
  * previewing the shortcode output.
12
12
  */
13
- export default function remarkToRehypeShortcodes({ plugins, getAsset, resolveWidget }) {
13
+ export default function remarkToRehypeShortcodes({ plugins, getAsset, resolveWidget, toHtml }) {
14
14
  return transform;
15
15
 
16
16
  function transform(root) {
@@ -54,11 +54,40 @@ export default function remarkToRehypeShortcodes({ plugins, getAsset, resolveWid
54
54
  * Retrieve the shortcode preview component.
55
55
  */
56
56
  function getPreview(plugin, shortcodeData) {
57
- const { toPreview, widget, fields } = plugin;
57
+ const { toPreview, fields } = plugin;
58
58
  if (toPreview) {
59
59
  return toPreview(shortcodeData, getAsset, fields);
60
60
  }
61
- const preview = resolveWidget(widget);
61
+
62
+ /**
63
+ * For editor components without a custom `toPreview` (e.g. container
64
+ * components with nested markdown/richtext fields), render each sub-field
65
+ * value using the appropriate widget preview.
66
+ */
67
+ if (fields && fields.size > 0 && toHtml) {
68
+ const htmlParts = fields
69
+ .map(field => {
70
+ const name = field.get('name');
71
+ const widget = field.get('widget') || 'string';
72
+ const fieldValue = shortcodeData ? shortcodeData[name] : '';
73
+
74
+ if (!fieldValue) return '';
75
+
76
+ if (widget === 'markdown' || widget === 'richtext') {
77
+ return toHtml(fieldValue);
78
+ }
79
+
80
+ return `<p>${fieldValue}</p>`;
81
+ })
82
+ .toArray();
83
+
84
+ return htmlParts.join('');
85
+ }
86
+
87
+ /**
88
+ * Last resort fallback: try resolving the widget and rendering its preview.
89
+ */
90
+ const preview = resolveWidget(plugin.widget);
62
91
  return React.createElement(preview.preview, {
63
92
  value: shortcodeData,
64
93
  field: plugin,