@contentful/field-editor-markdown 1.3.1 → 1.5.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.
@@ -22,7 +22,7 @@ const _fieldeditorshared = require("@contentful/field-editor-shared");
22
22
  const _emotion = require("emotion");
23
23
  const _MarkdownBottomBar = require("./components/MarkdownBottomBar");
24
24
  const _MarkdownConstraints = require("./components/MarkdownConstraints");
25
- const _MarkdownPreview = require("./components/MarkdownPreview");
25
+ const _MarkdownPreviewSkeleton = _interop_require_default(require("./components/MarkdownPreviewSkeleton"));
26
26
  const _MarkdownTabs = require("./components/MarkdownTabs");
27
27
  const _MarkdownTextarea = require("./components/MarkdownTextarea/MarkdownTextarea");
28
28
  const _MarkdownToolbar = require("./components/MarkdownToolbar");
@@ -72,6 +72,7 @@ function _interop_require_wildcard(obj, nodeInterop) {
72
72
  }
73
73
  return newObj;
74
74
  }
75
+ const MarkdownPreview = _react.lazy(()=>Promise.resolve().then(()=>_interop_require_wildcard(require("./components/MarkdownPreview"))));
75
76
  const styles = {
76
77
  container: (0, _emotion.css)({
77
78
  display: 'flex',
@@ -150,13 +151,16 @@ function MarkdownEditor(props) {
150
151
  setCurrentValue(value);
151
152
  });
152
153
  }
153
- }), selectedTab === 'preview' && _react.createElement(_MarkdownPreview.MarkdownPreview, {
154
+ }), selectedTab === 'preview' && _react.createElement(_react.Suspense, {
155
+ fallback: _react.createElement(_MarkdownPreviewSkeleton.default, null)
156
+ }, _react.createElement(MarkdownPreview, {
154
157
  direction: direction,
155
158
  minHeight: props.minHeight,
156
159
  mode: "default",
157
160
  value: currentValue,
158
161
  previewComponents: props.previewComponents
159
- }), _react.createElement(_MarkdownBottomBar.MarkdownBottomBar, null, _react.createElement(_MarkdownBottomBar.MarkdownHelp, {
162
+ })), _react.createElement(_MarkdownBottomBar.MarkdownBottomBar, null, _react.createElement(_MarkdownBottomBar.MarkdownHelp, {
163
+ mode: selectedTab,
160
164
  onClick: openMarkdownHelp
161
165
  })), _react.createElement(_MarkdownConstraints.MarkdownConstraints, {
162
166
  sdk: props.sdk,
@@ -165,7 +169,7 @@ function MarkdownEditor(props) {
165
169
  }
166
170
  function MarkdownEditorConnected(props) {
167
171
  return _react.createElement(_fieldeditorshared.FieldConnector, {
168
- throttle: 300,
172
+ debounce: 300,
169
173
  field: props.sdk.field,
170
174
  isInitiallyDisabled: props.isInitiallyDisabled
171
175
  }, ({ value , disabled , setValue , externalReset })=>{
@@ -67,6 +67,7 @@ function _interop_require_wildcard(obj, nodeInterop) {
67
67
  }
68
68
  return newObj;
69
69
  }
70
+ const SANITIZE_LINK = 'https://en.wikipedia.org/wiki/HTML_sanitization';
70
71
  const styles = {
71
72
  root: (0, _emotion.css)({
72
73
  display: 'flex',
@@ -80,7 +81,7 @@ const styles = {
80
81
  help: (0, _emotion.css)({
81
82
  color: _f36tokens.default.gray700,
82
83
  fontSize: _f36tokens.default.fontSizeS,
83
- button: {
84
+ '& button, & a': {
84
85
  fontSize: _f36tokens.default.fontSizeS,
85
86
  lineHeight: 'inherit'
86
87
  }
@@ -92,17 +93,49 @@ function MarkdownCounter(props) {
92
93
  className: styles.help
93
94
  }, props.words, " ", props.words !== 1 ? 'words' : 'word', ", ", props.characters, ' ', props.characters !== 1 ? 'characters' : 'character');
94
95
  }
96
+ function SanitizeMessage() {
97
+ return _react.createElement("span", null, "The preview of the content in this field will be sanitized.", ' ', _react.createElement(_f36components.TextLink, {
98
+ as: "a",
99
+ target: "_blank",
100
+ rel: "noopener noreferrer",
101
+ href: SANITIZE_LINK
102
+ }, "Learn more."));
103
+ }
104
+ function CheatSheetMessage({ onClick }) {
105
+ return _react.createElement("span", null, "Format your text like a pro with the", ' ', _react.createElement(_f36components.TextLink, {
106
+ as: "button",
107
+ testId: "open-markdown-cheatsheet-button",
108
+ onClick: onClick
109
+ }, "markdown cheatsheet"), ".");
110
+ }
95
111
  function MarkdownHelp(props) {
112
+ let content;
113
+ switch(props.mode){
114
+ case 'preview':
115
+ content = _react.createElement(SanitizeMessage, null);
116
+ break;
117
+ case 'editor':
118
+ content = _react.createElement(CheatSheetMessage, {
119
+ onClick: props.onClick
120
+ });
121
+ break;
122
+ case 'zen':
123
+ content = _react.createElement(_f36components.Stack, {
124
+ flexDirection: "column",
125
+ spacing: "spacing2Xs",
126
+ alignItems: "flex-start"
127
+ }, _react.createElement(CheatSheetMessage, {
128
+ onClick: props.onClick
129
+ }), _react.createElement(SanitizeMessage, null));
130
+ break;
131
+ default:
132
+ content = null;
133
+ throw new Error(`Invalid HelpMode provided in MarkdownHelp: ${props.mode}`);
134
+ }
96
135
  return _react.createElement(_f36components.Paragraph, {
97
136
  marginBottom: "none",
98
137
  className: styles.help
99
- }, "Format your text like a pro with the", ' ', _react.createElement(_f36components.TextLink, {
100
- as: "button",
101
- testId: "open-markdown-cheatsheet-button",
102
- onClick: ()=>{
103
- props.onClick();
104
- }
105
- }, "markdown cheatsheet"), ".");
138
+ }, content);
106
139
  }
107
140
  function MarkdownBottomBar(props) {
108
141
  return _react.createElement("div", {
@@ -2,17 +2,19 @@
2
2
  Object.defineProperty(exports, "__esModule", {
3
3
  value: true
4
4
  });
5
- Object.defineProperty(exports, "MarkdownPreview", {
5
+ Object.defineProperty(exports, "default", {
6
6
  enumerable: true,
7
7
  get: function() {
8
- return MarkdownPreview;
8
+ return _default;
9
9
  }
10
10
  });
11
11
  const _react = _interop_require_wildcard(require("react"));
12
+ const _reactmarkdown = _interop_require_default(require("react-markdown"));
12
13
  const _f36tokens = _interop_require_default(require("@contentful/f36-tokens"));
13
- const _dompurify = _interop_require_default(require("dompurify"));
14
14
  const _emotion = require("emotion");
15
- const _markdowntojsx = _interop_require_default(require("markdown-to-jsx"));
15
+ const _rehyperaw = _interop_require_default(require("rehype-raw"));
16
+ const _rehypesanitize = _interop_require_default(require("rehype-sanitize"));
17
+ const _remarkgfm = _interop_require_default(require("remark-gfm"));
16
18
  const _replaceMailtoAmp = require("../utils/replaceMailtoAmp");
17
19
  function _interop_require_default(obj) {
18
20
  return obj && obj.__esModule ? obj : {
@@ -70,7 +72,7 @@ const styles = {
70
72
  font-family: ${_f36tokens.default.fontStackPrimary};
71
73
  line-height: ${_f36tokens.default.lineHeightDefault};
72
74
  color: ${_f36tokens.default.gray700};
73
- white-space: pre-line;
75
+ white-space: normal;
74
76
 
75
77
  h1,
76
78
  h2,
@@ -212,7 +214,7 @@ function MarkdownLink(props) {
212
214
  const { Embedly , children , ...rest } = props;
213
215
  if (props.className === 'embedly-card' && Embedly) {
214
216
  return _react.createElement(Embedly, {
215
- url: props.href
217
+ url: props.href ?? ''
216
218
  });
217
219
  }
218
220
  return _react.createElement("a", {
@@ -222,28 +224,31 @@ function MarkdownLink(props) {
222
224
  }, children);
223
225
  }
224
226
  const MarkdownPreview = _react.memo((props)=>{
225
- const className = (0, _emotion.cx)(styles.root, props.minHeight !== undefined ? (0, _emotion.css)({
227
+ const className = (0, _emotion.cx)(props.minHeight !== undefined ? (0, _emotion.css)({
226
228
  minHeight: props.minHeight
227
229
  }) : undefined, props.mode === 'default' ? styles.framed : styles.zen, props.direction === 'rtl' ? styles.rtl : undefined);
228
- const cleanHTML = _react.useMemo(()=>{
229
- return (0, _replaceMailtoAmp.replaceMailtoAmp)(_dompurify.default.sanitize(props.value));
230
- }, [
231
- props.value
232
- ]);
233
230
  return _react.createElement("div", {
234
231
  className: className,
235
232
  "data-test-id": "markdown-preview"
236
- }, _react.createElement(_markdowntojsx.default, {
237
- options: {
238
- overrides: {
239
- a: {
240
- component: MarkdownLink,
241
- props: {
242
- Embedly: props.previewComponents?.embedly
243
- }
244
- }
245
- }
233
+ }, _react.createElement(_reactmarkdown.default, {
234
+ className: styles.root,
235
+ rehypePlugins: [
236
+ _rehyperaw.default,
237
+ _rehypesanitize.default
238
+ ],
239
+ remarkPlugins: [
240
+ _remarkgfm.default
241
+ ],
242
+ remarkRehypeOptions: {
243
+ allowDangerousHtml: true
244
+ },
245
+ components: {
246
+ a: (markdownProps)=>_react.createElement(MarkdownLink, {
247
+ ...markdownProps,
248
+ Embedly: props.previewComponents?.embedly
249
+ })
246
250
  }
247
- }, cleanHTML));
251
+ }, (0, _replaceMailtoAmp.replaceMailtoAmp)(props.value)));
248
252
  });
249
253
  MarkdownPreview.displayName = 'MarkdownPreview';
254
+ const _default = MarkdownPreview;
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ Object.defineProperty(exports, "default", {
6
+ enumerable: true,
7
+ get: function() {
8
+ return MarkdownPreviewSkeleton;
9
+ }
10
+ });
11
+ const _react = _interop_require_default(require("react"));
12
+ const _f36components = require("@contentful/f36-components");
13
+ const _f36tokens = _interop_require_default(require("@contentful/f36-tokens"));
14
+ const _emotion = require("emotion");
15
+ function _interop_require_default(obj) {
16
+ return obj && obj.__esModule ? obj : {
17
+ default: obj
18
+ };
19
+ }
20
+ const styles = {
21
+ root: (0, _emotion.css)({
22
+ border: `1px solid ${_f36tokens.default.gray400}`,
23
+ borderWidth: '0 1px',
24
+ minHeight: '300px',
25
+ padding: `${_f36tokens.default.spacingL}`
26
+ })
27
+ };
28
+ function MarkdownPreviewSkeleton() {
29
+ return _react.default.createElement(_f36components.Skeleton.Container, {
30
+ className: styles.root
31
+ }, _react.default.createElement(_f36components.Skeleton.DisplayText, null), _react.default.createElement(_f36components.Skeleton.BodyText, {
32
+ offsetTop: 37,
33
+ numberOfLines: 5
34
+ }));
35
+ }
@@ -105,6 +105,7 @@ const ToolbarButton = _react.forwardRef((props, ref)=>{
105
105
  const { tooltip , onClick , children , className , variant ='transparent' , tooltipPlace ='top' , isDisabled =false , ...otherProps } = props;
106
106
  return _react.createElement(_f36components.Tooltip, {
107
107
  className: styles.tooltip,
108
+ usePortal: true,
108
109
  placement: tooltipPlace,
109
110
  content: tooltip
110
111
  }, _react.createElement(_f36components.IconButton, {
@@ -337,8 +338,11 @@ function ZenMarkdownToolbar(props) {
337
338
  return _react.createElement("div", {
338
339
  className: styles.root
339
340
  }, _react.createElement(_f36components.Flex, {
340
- justifyContent: "space-between"
341
- }, _react.createElement(_f36components.Flex, null, _react.createElement(MainButtons, props), _react.createElement(AdditionalButtons, props)), _react.createElement(_f36components.Flex, null, _react.createElement(_InsertLinkSelector.InsertLinkSelector, {
341
+ justifyContent: "space-between",
342
+ alignItems: "flex-start"
343
+ }, _react.createElement(_f36components.Flex, {
344
+ flexWrap: "wrap"
345
+ }, _react.createElement(MainButtons, props), _react.createElement(AdditionalButtons, props)), _react.createElement(_f36components.Flex, null, _react.createElement(_InsertLinkSelector.InsertLinkSelector, {
342
346
  disabled: props.disabled,
343
347
  onSelectExisting: props.actions.linkExistingMedia,
344
348
  onAddNew: props.actions.addNewMedia,
@@ -17,11 +17,12 @@ _export(exports, {
17
17
  }
18
18
  });
19
19
  const _react = _interop_require_wildcard(require("react"));
20
+ const _f36components = require("@contentful/f36-components");
20
21
  const _f36icons = require("@contentful/f36-icons");
21
22
  const _f36tokens = _interop_require_default(require("@contentful/f36-tokens"));
22
23
  const _emotion = require("emotion");
23
24
  const _MarkdownBottomBar = require("../components/MarkdownBottomBar");
24
- const _MarkdownPreview = require("../components/MarkdownPreview");
25
+ const _MarkdownPreviewSkeleton = _interop_require_default(require("../components/MarkdownPreviewSkeleton"));
25
26
  const _MarkdownTextarea = require("../components/MarkdownTextarea/MarkdownTextarea");
26
27
  const _MarkdownToolbar = require("../components/MarkdownToolbar");
27
28
  const _CheatsheetModalDialog = require("../dialogs/CheatsheetModalDialog");
@@ -71,75 +72,62 @@ function _interop_require_wildcard(obj, nodeInterop) {
71
72
  }
72
73
  return newObj;
73
74
  }
75
+ const MarkdownPreview = _react.lazy(()=>Promise.resolve().then(()=>_interop_require_wildcard(require("../components/MarkdownPreview"))));
74
76
  const styles = {
75
77
  root: (0, _emotion.css)({
76
- position: 'fixed',
77
- left: 0,
78
- right: 0,
79
- top: 0,
80
- bottom: 0
78
+ display: 'grid',
79
+ gridTemplateRows: 'min-content 1fr min-content',
80
+ gridTemplateColumns: '1fr 1px 1fr',
81
+ height: '85vh'
81
82
  }),
82
83
  topSplit: (0, _emotion.css)({
83
- position: 'fixed',
84
- top: 0,
85
- height: '48px',
86
- left: 0,
87
- right: 0
84
+ gridRow: '1 / 2',
85
+ gridColumn: '1 / 4'
88
86
  }),
89
87
  bottomSplit: (0, _emotion.css)({
90
- position: 'fixed',
91
- bottom: 0,
92
- left: 0,
93
- right: 0,
94
- height: '36px'
88
+ gridRow: '3 / 4',
89
+ gridColumn: '1 / 4'
95
90
  }),
96
91
  editorSplit: (0, _emotion.css)({
97
- width: '50%',
98
- position: 'fixed',
99
- top: '48px',
100
- left: 0,
101
- bottom: '36px',
102
- overflowX: 'hidden',
92
+ gridRow: '2 / 3',
93
+ gridColumn: '1 / 2',
103
94
  overflowY: 'scroll'
104
95
  }),
105
96
  editorSplitFullscreen: (0, _emotion.css)({
106
- left: 0,
107
- right: 0,
108
- width: '100%'
97
+ gridRow: '2 / 3',
98
+ gridColumn: '1 / 4',
99
+ overflowY: 'scroll'
109
100
  }),
110
101
  previewSplit: (0, _emotion.css)({
111
- width: '50%',
112
- position: 'fixed',
113
- top: '48px',
114
- right: 0,
115
- bottom: '36px',
116
- overflowX: 'hidden',
102
+ gridRow: '2 / 3',
103
+ gridColumn: '3 / 4',
117
104
  overflowY: 'scroll'
118
105
  }),
119
106
  separator: (0, _emotion.css)({
120
- position: 'fixed',
121
- top: '48px',
122
- bottom: '36px',
123
- width: '1px',
124
- background: _f36tokens.default.gray400,
125
- left: '50%'
107
+ gridRow: '2 / 3',
108
+ gridColumn: '2 / 3',
109
+ backgroundColor: _f36tokens.default.gray400,
110
+ width: '1px'
126
111
  }),
127
112
  button: (0, _emotion.css)({
128
- position: 'fixed',
129
113
  cursor: 'pointer',
130
114
  zIndex: 105,
131
- top: '49%',
132
115
  height: '30px',
133
116
  backgroundColor: _f36tokens.default.gray100,
134
117
  border: `1px solid ${_f36tokens.default.gray400}`,
135
118
  padding: 0
136
119
  }),
137
120
  hideButton: (0, _emotion.css)({
138
- left: '50%'
121
+ gridRow: '2 / 3',
122
+ gridColumn: '2 / 3',
123
+ justifySelf: 'end',
124
+ alignSelf: 'center'
139
125
  }),
140
126
  showButton: (0, _emotion.css)({
141
- right: 0,
142
- borderRightWidth: 0
127
+ gridRow: '2 / 3',
128
+ gridColumn: '3 / 4',
129
+ justifySelf: 'end',
130
+ alignSelf: 'center'
143
131
  }),
144
132
  icon: (0, _emotion.css)({
145
133
  verticalAlign: 'middle'
@@ -176,17 +164,17 @@ const ZenModeModalDialog = (props)=>{
176
164
  });
177
165
  };
178
166
  const direction = props.sdk.locales.direction[props.locale] ?? 'ltr';
179
- return _react.createElement("div", {
167
+ return _react.createElement(_f36components.Grid, {
180
168
  className: styles.root,
181
169
  "data-test-id": "zen-mode-markdown-editor"
182
- }, _react.createElement("div", {
170
+ }, _react.createElement(_f36components.Grid.Item, {
183
171
  className: styles.topSplit
184
172
  }, _react.createElement(_MarkdownToolbar.MarkdownToolbar, {
185
173
  mode: "zen",
186
174
  disabled: false,
187
175
  canUploadAssets: false,
188
176
  actions: actions
189
- })), _react.createElement("div", {
177
+ })), _react.createElement(_f36components.Grid.Item, {
190
178
  className: (0, _emotion.cx)(styles.editorSplit, {
191
179
  [styles.editorSplitFullscreen]: showPreview === false
192
180
  })
@@ -205,14 +193,16 @@ const ZenModeModalDialog = (props)=>{
205
193
  props.saveValueToSDK(value);
206
194
  });
207
195
  }
208
- })), showPreview && _react.createElement("div", {
196
+ })), showPreview && _react.createElement(_f36components.Grid.Item, {
209
197
  className: styles.previewSplit
210
- }, _react.createElement(_MarkdownPreview.MarkdownPreview, {
198
+ }, _react.createElement(_react.Suspense, {
199
+ fallback: _react.createElement(_MarkdownPreviewSkeleton.default, null)
200
+ }, _react.createElement(MarkdownPreview, {
211
201
  direction: direction,
212
202
  mode: "zen",
213
203
  value: currentValue,
214
204
  previewComponents: props.previewComponents
215
- })), showPreview && _react.createElement("div", {
205
+ }))), showPreview && _react.createElement(_f36components.Grid.Item, {
216
206
  className: styles.separator
217
207
  }), showPreview && _react.createElement("button", {
218
208
  className: (0, _emotion.cx)(styles.button, styles.hideButton),
@@ -234,9 +224,10 @@ const ZenModeModalDialog = (props)=>{
234
224
  variant: "muted",
235
225
  size: "tiny",
236
226
  className: styles.icon
237
- })), _react.createElement("div", {
227
+ })), _react.createElement(_f36components.Grid.Item, {
238
228
  className: styles.bottomSplit
239
229
  }, _react.createElement(_MarkdownBottomBar.MarkdownBottomBar, null, _react.createElement(_MarkdownBottomBar.MarkdownHelp, {
230
+ mode: "zen",
240
231
  onClick: ()=>{
241
232
  (0, _CheatsheetModalDialog.openCheatsheetModal)(props.sdk.dialogs);
242
233
  }
@@ -244,10 +235,9 @@ const ZenModeModalDialog = (props)=>{
244
235
  };
245
236
  const openZenMode = (dialogs, options)=>{
246
237
  return dialogs.openCurrent({
247
- width: 'zen',
238
+ width: 'fullWidth',
248
239
  shouldCloseOnEscapePress: false,
249
- minHeight: '100vh',
250
- shouldCloseOnOverlayClick: false,
240
+ shouldCloseOnOverlayClick: true,
251
241
  parameters: {
252
242
  type: _types.MarkdownDialogType.zenMode,
253
243
  initialValue: options.initialValue,
package/dist/cjs/index.js CHANGED
@@ -13,7 +13,7 @@ _export(exports, {
13
13
  return _MarkdownEditor.MarkdownEditorConnected;
14
14
  },
15
15
  MarkdownPreview: function() {
16
- return _MarkdownPreview.MarkdownPreview;
16
+ return _MarkdownPreview.default;
17
17
  },
18
18
  openMarkdownDialog: function() {
19
19
  return _openMarkdownDialog.openMarkdownDialog;
@@ -24,6 +24,11 @@ _export(exports, {
24
24
  });
25
25
  require("./codemirrorImports");
26
26
  const _MarkdownEditor = require("./MarkdownEditor");
27
- const _MarkdownPreview = require("./components/MarkdownPreview");
27
+ const _MarkdownPreview = _interop_require_default(require("./components/MarkdownPreview"));
28
28
  const _openMarkdownDialog = require("./dialogs/openMarkdownDialog");
29
29
  const _renderMarkdownDialog = require("./dialogs/renderMarkdownDialog");
30
+ function _interop_require_default(obj) {
31
+ return obj && obj.__esModule ? obj : {
32
+ default: obj
33
+ };
34
+ }
@@ -4,12 +4,13 @@ import { FieldConnector } from '@contentful/field-editor-shared';
4
4
  import { css } from 'emotion';
5
5
  import { MarkdownBottomBar, MarkdownHelp } from './components/MarkdownBottomBar';
6
6
  import { MarkdownConstraints } from './components/MarkdownConstraints';
7
- import { MarkdownPreview } from './components/MarkdownPreview';
7
+ import MarkdownPreviewSkeleton from './components/MarkdownPreviewSkeleton';
8
8
  import { MarkdownTabs } from './components/MarkdownTabs';
9
9
  import { MarkdownTextarea } from './components/MarkdownTextarea/MarkdownTextarea';
10
10
  import { MarkdownToolbar } from './components/MarkdownToolbar';
11
11
  import { openCheatsheetModal } from './dialogs/CheatsheetModalDialog';
12
12
  import { createMarkdownActions } from './MarkdownActions';
13
+ const MarkdownPreview = React.lazy(()=>import('./components/MarkdownPreview'));
13
14
  const styles = {
14
15
  container: css({
15
16
  display: 'flex',
@@ -88,13 +89,16 @@ export function MarkdownEditor(props) {
88
89
  setCurrentValue(value);
89
90
  });
90
91
  }
91
- }), selectedTab === 'preview' && React.createElement(MarkdownPreview, {
92
+ }), selectedTab === 'preview' && React.createElement(React.Suspense, {
93
+ fallback: React.createElement(MarkdownPreviewSkeleton, null)
94
+ }, React.createElement(MarkdownPreview, {
92
95
  direction: direction,
93
96
  minHeight: props.minHeight,
94
97
  mode: "default",
95
98
  value: currentValue,
96
99
  previewComponents: props.previewComponents
97
- }), React.createElement(MarkdownBottomBar, null, React.createElement(MarkdownHelp, {
100
+ })), React.createElement(MarkdownBottomBar, null, React.createElement(MarkdownHelp, {
101
+ mode: selectedTab,
98
102
  onClick: openMarkdownHelp
99
103
  })), React.createElement(MarkdownConstraints, {
100
104
  sdk: props.sdk,
@@ -103,7 +107,7 @@ export function MarkdownEditor(props) {
103
107
  }
104
108
  export function MarkdownEditorConnected(props) {
105
109
  return React.createElement(FieldConnector, {
106
- throttle: 300,
110
+ debounce: 300,
107
111
  field: props.sdk.field,
108
112
  isInitiallyDisabled: props.isInitiallyDisabled
109
113
  }, ({ value , disabled , setValue , externalReset })=>{
@@ -1,7 +1,8 @@
1
1
  import * as React from 'react';
2
- import { Paragraph, TextLink } from '@contentful/f36-components';
2
+ import { Paragraph, Stack, TextLink } from '@contentful/f36-components';
3
3
  import tokens from '@contentful/f36-tokens';
4
4
  import { css } from 'emotion';
5
+ const SANITIZE_LINK = 'https://en.wikipedia.org/wiki/HTML_sanitization';
5
6
  const styles = {
6
7
  root: css({
7
8
  display: 'flex',
@@ -15,7 +16,7 @@ const styles = {
15
16
  help: css({
16
17
  color: tokens.gray700,
17
18
  fontSize: tokens.fontSizeS,
18
- button: {
19
+ '& button, & a': {
19
20
  fontSize: tokens.fontSizeS,
20
21
  lineHeight: 'inherit'
21
22
  }
@@ -27,17 +28,49 @@ export function MarkdownCounter(props) {
27
28
  className: styles.help
28
29
  }, props.words, " ", props.words !== 1 ? 'words' : 'word', ", ", props.characters, ' ', props.characters !== 1 ? 'characters' : 'character');
29
30
  }
31
+ function SanitizeMessage() {
32
+ return React.createElement("span", null, "The preview of the content in this field will be sanitized.", ' ', React.createElement(TextLink, {
33
+ as: "a",
34
+ target: "_blank",
35
+ rel: "noopener noreferrer",
36
+ href: SANITIZE_LINK
37
+ }, "Learn more."));
38
+ }
39
+ function CheatSheetMessage({ onClick }) {
40
+ return React.createElement("span", null, "Format your text like a pro with the", ' ', React.createElement(TextLink, {
41
+ as: "button",
42
+ testId: "open-markdown-cheatsheet-button",
43
+ onClick: onClick
44
+ }, "markdown cheatsheet"), ".");
45
+ }
30
46
  export function MarkdownHelp(props) {
47
+ let content;
48
+ switch(props.mode){
49
+ case 'preview':
50
+ content = React.createElement(SanitizeMessage, null);
51
+ break;
52
+ case 'editor':
53
+ content = React.createElement(CheatSheetMessage, {
54
+ onClick: props.onClick
55
+ });
56
+ break;
57
+ case 'zen':
58
+ content = React.createElement(Stack, {
59
+ flexDirection: "column",
60
+ spacing: "spacing2Xs",
61
+ alignItems: "flex-start"
62
+ }, React.createElement(CheatSheetMessage, {
63
+ onClick: props.onClick
64
+ }), React.createElement(SanitizeMessage, null));
65
+ break;
66
+ default:
67
+ content = null;
68
+ throw new Error(`Invalid HelpMode provided in MarkdownHelp: ${props.mode}`);
69
+ }
31
70
  return React.createElement(Paragraph, {
32
71
  marginBottom: "none",
33
72
  className: styles.help
34
- }, "Format your text like a pro with the", ' ', React.createElement(TextLink, {
35
- as: "button",
36
- testId: "open-markdown-cheatsheet-button",
37
- onClick: ()=>{
38
- props.onClick();
39
- }
40
- }, "markdown cheatsheet"), ".");
73
+ }, content);
41
74
  }
42
75
  export function MarkdownBottomBar(props) {
43
76
  return React.createElement("div", {
@@ -1,8 +1,10 @@
1
1
  import * as React from 'react';
2
+ import ReactMarkdown from 'react-markdown';
2
3
  import tokens from '@contentful/f36-tokens';
3
- import DOMPurify from 'dompurify';
4
4
  import { css, cx } from 'emotion';
5
- import Markdown from 'markdown-to-jsx';
5
+ import rehypeRaw from 'rehype-raw';
6
+ import rehypeSanitize from 'rehype-sanitize';
7
+ import remarkGfm from 'remark-gfm';
6
8
  import { replaceMailtoAmp } from '../utils/replaceMailtoAmp';
7
9
  const styles = {
8
10
  root: css`
@@ -16,7 +18,7 @@ const styles = {
16
18
  font-family: ${tokens.fontStackPrimary};
17
19
  line-height: ${tokens.lineHeightDefault};
18
20
  color: ${tokens.gray700};
19
- white-space: pre-line;
21
+ white-space: normal;
20
22
 
21
23
  h1,
22
24
  h2,
@@ -158,7 +160,7 @@ function MarkdownLink(props) {
158
160
  const { Embedly , children , ...rest } = props;
159
161
  if (props.className === 'embedly-card' && Embedly) {
160
162
  return React.createElement(Embedly, {
161
- url: props.href
163
+ url: props.href ?? ''
162
164
  });
163
165
  }
164
166
  return React.createElement("a", {
@@ -167,29 +169,32 @@ function MarkdownLink(props) {
167
169
  rel: "noopener noreferrer"
168
170
  }, children);
169
171
  }
170
- export const MarkdownPreview = React.memo((props)=>{
171
- const className = cx(styles.root, props.minHeight !== undefined ? css({
172
+ const MarkdownPreview = React.memo((props)=>{
173
+ const className = cx(props.minHeight !== undefined ? css({
172
174
  minHeight: props.minHeight
173
175
  }) : undefined, props.mode === 'default' ? styles.framed : styles.zen, props.direction === 'rtl' ? styles.rtl : undefined);
174
- const cleanHTML = React.useMemo(()=>{
175
- return replaceMailtoAmp(DOMPurify.sanitize(props.value));
176
- }, [
177
- props.value
178
- ]);
179
176
  return React.createElement("div", {
180
177
  className: className,
181
178
  "data-test-id": "markdown-preview"
182
- }, React.createElement(Markdown, {
183
- options: {
184
- overrides: {
185
- a: {
186
- component: MarkdownLink,
187
- props: {
188
- Embedly: props.previewComponents?.embedly
189
- }
190
- }
191
- }
179
+ }, React.createElement(ReactMarkdown, {
180
+ className: styles.root,
181
+ rehypePlugins: [
182
+ rehypeRaw,
183
+ rehypeSanitize
184
+ ],
185
+ remarkPlugins: [
186
+ remarkGfm
187
+ ],
188
+ remarkRehypeOptions: {
189
+ allowDangerousHtml: true
190
+ },
191
+ components: {
192
+ a: (markdownProps)=>React.createElement(MarkdownLink, {
193
+ ...markdownProps,
194
+ Embedly: props.previewComponents?.embedly
195
+ })
192
196
  }
193
- }, cleanHTML));
197
+ }, replaceMailtoAmp(props.value)));
194
198
  });
195
199
  MarkdownPreview.displayName = 'MarkdownPreview';
200
+ export default MarkdownPreview;
@@ -0,0 +1,20 @@
1
+ import React from 'react';
2
+ import { Skeleton } from '@contentful/f36-components';
3
+ import tokens from '@contentful/f36-tokens';
4
+ import { css } from 'emotion';
5
+ const styles = {
6
+ root: css({
7
+ border: `1px solid ${tokens.gray400}`,
8
+ borderWidth: '0 1px',
9
+ minHeight: '300px',
10
+ padding: `${tokens.spacingL}`
11
+ })
12
+ };
13
+ export default function MarkdownPreviewSkeleton() {
14
+ return React.createElement(Skeleton.Container, {
15
+ className: styles.root
16
+ }, React.createElement(Skeleton.DisplayText, null), React.createElement(Skeleton.BodyText, {
17
+ offsetTop: 37,
18
+ numberOfLines: 5
19
+ }));
20
+ }
@@ -40,6 +40,7 @@ const ToolbarButton = React.forwardRef((props, ref)=>{
40
40
  const { tooltip , onClick , children , className , variant ='transparent' , tooltipPlace ='top' , isDisabled =false , ...otherProps } = props;
41
41
  return React.createElement(Tooltip, {
42
42
  className: styles.tooltip,
43
+ usePortal: true,
43
44
  placement: tooltipPlace,
44
45
  content: tooltip
45
46
  }, React.createElement(IconButton, {
@@ -272,8 +273,11 @@ export function ZenMarkdownToolbar(props) {
272
273
  return React.createElement("div", {
273
274
  className: styles.root
274
275
  }, React.createElement(Flex, {
275
- justifyContent: "space-between"
276
- }, React.createElement(Flex, null, React.createElement(MainButtons, props), React.createElement(AdditionalButtons, props)), React.createElement(Flex, null, React.createElement(InsertLinkSelector, {
276
+ justifyContent: "space-between",
277
+ alignItems: "flex-start"
278
+ }, React.createElement(Flex, {
279
+ flexWrap: "wrap"
280
+ }, React.createElement(MainButtons, props), React.createElement(AdditionalButtons, props)), React.createElement(Flex, null, React.createElement(InsertLinkSelector, {
277
281
  disabled: props.disabled,
278
282
  onSelectExisting: props.actions.linkExistingMedia,
279
283
  onAddNew: props.actions.addNewMedia,
@@ -1,83 +1,71 @@
1
1
  import * as React from 'react';
2
+ import { Grid } from '@contentful/f36-components';
2
3
  import { ChevronLeftIcon, ChevronRightIcon } from '@contentful/f36-icons';
3
4
  import tokens from '@contentful/f36-tokens';
4
5
  import { css, cx } from 'emotion';
5
6
  import { MarkdownBottomBar, MarkdownHelp } from '../components/MarkdownBottomBar';
6
- import { MarkdownPreview } from '../components/MarkdownPreview';
7
+ import MarkdownPreviewSkeleton from '../components/MarkdownPreviewSkeleton';
7
8
  import { MarkdownTextarea } from '../components/MarkdownTextarea/MarkdownTextarea';
8
9
  import { MarkdownToolbar } from '../components/MarkdownToolbar';
9
10
  import { openCheatsheetModal } from '../dialogs/CheatsheetModalDialog';
10
11
  import { createMarkdownActions } from '../MarkdownActions';
11
12
  import { MarkdownDialogType } from '../types';
13
+ const MarkdownPreview = React.lazy(()=>import('../components/MarkdownPreview'));
12
14
  const styles = {
13
15
  root: css({
14
- position: 'fixed',
15
- left: 0,
16
- right: 0,
17
- top: 0,
18
- bottom: 0
16
+ display: 'grid',
17
+ gridTemplateRows: 'min-content 1fr min-content',
18
+ gridTemplateColumns: '1fr 1px 1fr',
19
+ height: '85vh'
19
20
  }),
20
21
  topSplit: css({
21
- position: 'fixed',
22
- top: 0,
23
- height: '48px',
24
- left: 0,
25
- right: 0
22
+ gridRow: '1 / 2',
23
+ gridColumn: '1 / 4'
26
24
  }),
27
25
  bottomSplit: css({
28
- position: 'fixed',
29
- bottom: 0,
30
- left: 0,
31
- right: 0,
32
- height: '36px'
26
+ gridRow: '3 / 4',
27
+ gridColumn: '1 / 4'
33
28
  }),
34
29
  editorSplit: css({
35
- width: '50%',
36
- position: 'fixed',
37
- top: '48px',
38
- left: 0,
39
- bottom: '36px',
40
- overflowX: 'hidden',
30
+ gridRow: '2 / 3',
31
+ gridColumn: '1 / 2',
41
32
  overflowY: 'scroll'
42
33
  }),
43
34
  editorSplitFullscreen: css({
44
- left: 0,
45
- right: 0,
46
- width: '100%'
35
+ gridRow: '2 / 3',
36
+ gridColumn: '1 / 4',
37
+ overflowY: 'scroll'
47
38
  }),
48
39
  previewSplit: css({
49
- width: '50%',
50
- position: 'fixed',
51
- top: '48px',
52
- right: 0,
53
- bottom: '36px',
54
- overflowX: 'hidden',
40
+ gridRow: '2 / 3',
41
+ gridColumn: '3 / 4',
55
42
  overflowY: 'scroll'
56
43
  }),
57
44
  separator: css({
58
- position: 'fixed',
59
- top: '48px',
60
- bottom: '36px',
61
- width: '1px',
62
- background: tokens.gray400,
63
- left: '50%'
45
+ gridRow: '2 / 3',
46
+ gridColumn: '2 / 3',
47
+ backgroundColor: tokens.gray400,
48
+ width: '1px'
64
49
  }),
65
50
  button: css({
66
- position: 'fixed',
67
51
  cursor: 'pointer',
68
52
  zIndex: 105,
69
- top: '49%',
70
53
  height: '30px',
71
54
  backgroundColor: tokens.gray100,
72
55
  border: `1px solid ${tokens.gray400}`,
73
56
  padding: 0
74
57
  }),
75
58
  hideButton: css({
76
- left: '50%'
59
+ gridRow: '2 / 3',
60
+ gridColumn: '2 / 3',
61
+ justifySelf: 'end',
62
+ alignSelf: 'center'
77
63
  }),
78
64
  showButton: css({
79
- right: 0,
80
- borderRightWidth: 0
65
+ gridRow: '2 / 3',
66
+ gridColumn: '3 / 4',
67
+ justifySelf: 'end',
68
+ alignSelf: 'center'
81
69
  }),
82
70
  icon: css({
83
71
  verticalAlign: 'middle'
@@ -114,17 +102,17 @@ export const ZenModeModalDialog = (props)=>{
114
102
  });
115
103
  };
116
104
  const direction = props.sdk.locales.direction[props.locale] ?? 'ltr';
117
- return React.createElement("div", {
105
+ return React.createElement(Grid, {
118
106
  className: styles.root,
119
107
  "data-test-id": "zen-mode-markdown-editor"
120
- }, React.createElement("div", {
108
+ }, React.createElement(Grid.Item, {
121
109
  className: styles.topSplit
122
110
  }, React.createElement(MarkdownToolbar, {
123
111
  mode: "zen",
124
112
  disabled: false,
125
113
  canUploadAssets: false,
126
114
  actions: actions
127
- })), React.createElement("div", {
115
+ })), React.createElement(Grid.Item, {
128
116
  className: cx(styles.editorSplit, {
129
117
  [styles.editorSplitFullscreen]: showPreview === false
130
118
  })
@@ -143,14 +131,16 @@ export const ZenModeModalDialog = (props)=>{
143
131
  props.saveValueToSDK(value);
144
132
  });
145
133
  }
146
- })), showPreview && React.createElement("div", {
134
+ })), showPreview && React.createElement(Grid.Item, {
147
135
  className: styles.previewSplit
136
+ }, React.createElement(React.Suspense, {
137
+ fallback: React.createElement(MarkdownPreviewSkeleton, null)
148
138
  }, React.createElement(MarkdownPreview, {
149
139
  direction: direction,
150
140
  mode: "zen",
151
141
  value: currentValue,
152
142
  previewComponents: props.previewComponents
153
- })), showPreview && React.createElement("div", {
143
+ }))), showPreview && React.createElement(Grid.Item, {
154
144
  className: styles.separator
155
145
  }), showPreview && React.createElement("button", {
156
146
  className: cx(styles.button, styles.hideButton),
@@ -172,9 +162,10 @@ export const ZenModeModalDialog = (props)=>{
172
162
  variant: "muted",
173
163
  size: "tiny",
174
164
  className: styles.icon
175
- })), React.createElement("div", {
165
+ })), React.createElement(Grid.Item, {
176
166
  className: styles.bottomSplit
177
167
  }, React.createElement(MarkdownBottomBar, null, React.createElement(MarkdownHelp, {
168
+ mode: "zen",
178
169
  onClick: ()=>{
179
170
  openCheatsheetModal(props.sdk.dialogs);
180
171
  }
@@ -182,10 +173,9 @@ export const ZenModeModalDialog = (props)=>{
182
173
  };
183
174
  export const openZenMode = (dialogs, options)=>{
184
175
  return dialogs.openCurrent({
185
- width: 'zen',
176
+ width: 'fullWidth',
186
177
  shouldCloseOnEscapePress: false,
187
- minHeight: '100vh',
188
- shouldCloseOnOverlayClick: false,
178
+ shouldCloseOnOverlayClick: true,
189
179
  parameters: {
190
180
  type: MarkdownDialogType.zenMode,
191
181
  initialValue: options.initialValue,
package/dist/esm/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import './codemirrorImports';
2
2
  export { MarkdownEditorConnected as MarkdownEditor } from './MarkdownEditor';
3
- export { MarkdownPreview } from './components/MarkdownPreview';
3
+ export { default as MarkdownPreview } from './components/MarkdownPreview';
4
4
  export { openMarkdownDialog } from './dialogs/openMarkdownDialog';
5
5
  export { renderMarkdownDialog } from './dialogs/renderMarkdownDialog';
@@ -1,11 +1,15 @@
1
1
  import * as React from 'react';
2
+ import { MarkdownTab } from 'types';
2
3
  export declare function MarkdownCounter(props: {
3
4
  words: number;
4
5
  characters: number;
5
6
  }): React.JSX.Element;
7
+ type HelpMode = MarkdownTab | 'zen';
6
8
  export declare function MarkdownHelp(props: {
7
9
  onClick: () => void;
10
+ mode: HelpMode;
8
11
  }): React.JSX.Element;
9
12
  export declare function MarkdownBottomBar(props: {
10
13
  children: React.ReactNode;
11
14
  }): React.JSX.Element;
15
+ export {};
@@ -10,5 +10,5 @@ type MarkdownPreviewProps = {
10
10
  value: string;
11
11
  previewComponents?: PreviewComponents;
12
12
  };
13
- export declare const MarkdownPreview: React.MemoExoticComponent<(props: MarkdownPreviewProps) => React.JSX.Element>;
14
- export {};
13
+ declare const MarkdownPreview: React.MemoExoticComponent<(props: MarkdownPreviewProps) => React.JSX.Element>;
14
+ export default MarkdownPreview;
@@ -0,0 +1,2 @@
1
+ import React from 'react';
2
+ export default function MarkdownPreviewSkeleton(): React.JSX.Element;
@@ -1,5 +1,5 @@
1
1
  import './codemirrorImports';
2
2
  export { MarkdownEditorConnected as MarkdownEditor } from './MarkdownEditor';
3
- export { MarkdownPreview } from './components/MarkdownPreview';
3
+ export { default as MarkdownPreview } from './components/MarkdownPreview';
4
4
  export { openMarkdownDialog } from './dialogs/openMarkdownDialog';
5
5
  export { renderMarkdownDialog } from './dialogs/renderMarkdownDialog';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contentful/field-editor-markdown",
3
- "version": "1.3.1",
3
+ "version": "1.5.0",
4
4
  "main": "dist/cjs/index.js",
5
5
  "module": "dist/esm/index.js",
6
6
  "types": "dist/types/index.d.ts",
@@ -38,25 +38,25 @@
38
38
  "@contentful/f36-components": "^4.0.27",
39
39
  "@contentful/f36-icons": "^4.1.0",
40
40
  "@contentful/f36-tokens": "^4.0.0",
41
- "@contentful/field-editor-shared": "^1.3.1",
41
+ "@contentful/field-editor-shared": "^1.4.0",
42
42
  "@types/codemirror": "0.0.109",
43
- "@types/markdown-to-jsx": "6.11.3",
44
43
  "codemirror": "^5.65.11",
45
44
  "constate": "^3.2.0",
46
- "dompurify": "^2.2.9",
47
45
  "emotion": "^10.0.17",
48
46
  "lodash": "^4.17.15",
49
- "markdown-to-jsx": "^7.1.1"
47
+ "react-markdown": "^8.0.7",
48
+ "rehype-raw": "^6.1.1",
49
+ "rehype-sanitize": "^6.0.0",
50
+ "remark-gfm": "^3.0.1"
50
51
  },
51
52
  "devDependencies": {
52
53
  "@babel/core": "^7.5.5",
53
54
  "@contentful/app-sdk": "^4.2.0",
54
- "@contentful/field-editor-test-utils": "^1.4.1",
55
- "@types/dompurify": "^2.2.2"
55
+ "@contentful/field-editor-test-utils": "^1.4.1"
56
56
  },
57
57
  "peerDependencies": {
58
58
  "@contentful/app-sdk": "^4.2.0",
59
59
  "react": ">=16.8.0"
60
60
  },
61
- "gitHead": "ca904b19ca794a2c40d82e1f7ede9e0be3560f22"
61
+ "gitHead": "2db95226af0417c42aae8d1be85796fc8774e283"
62
62
  }