@contentful/field-editor-markdown 1.15.3 → 1.15.4-canary.25

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.
Files changed (36) hide show
  1. package/dist/cjs/MarkdownActions.spec.js +163 -0
  2. package/dist/cjs/MarkdownEditor.js +2 -2
  3. package/dist/cjs/components/HeadingSelector.js +9 -1
  4. package/dist/cjs/components/MarkdownBottomBar.js +3 -3
  5. package/dist/cjs/components/MarkdownConstraints.js +2 -2
  6. package/dist/cjs/components/MarkdownPreview.js +6 -6
  7. package/dist/cjs/components/MarkdownPreviewSkeleton.js +2 -2
  8. package/dist/cjs/components/MarkdownTabs.js +6 -6
  9. package/dist/cjs/components/MarkdownTextarea/MarkdownTextarea.js +6 -6
  10. package/dist/cjs/components/MarkdownToolbar.js +62 -32
  11. package/dist/cjs/components/MarkdownToolbar.spec.js +144 -0
  12. package/dist/cjs/components/icons.js +2 -2
  13. package/dist/cjs/dialogs/CheatsheetModalDialog.js +18 -18
  14. package/dist/cjs/dialogs/EmdebExternalContentDialog.js +4 -4
  15. package/dist/cjs/dialogs/SpecialCharacterModalDialog.js +8 -7
  16. package/dist/cjs/dialogs/ZenModeModalDialog.js +15 -15
  17. package/dist/esm/MarkdownActions.spec.js +159 -0
  18. package/dist/esm/MarkdownEditor.js +1 -1
  19. package/dist/esm/components/HeadingSelector.js +9 -1
  20. package/dist/esm/components/MarkdownBottomBar.js +1 -1
  21. package/dist/esm/components/MarkdownConstraints.js +1 -1
  22. package/dist/esm/components/MarkdownPreview.js +1 -1
  23. package/dist/esm/components/MarkdownPreviewSkeleton.js +1 -1
  24. package/dist/esm/components/MarkdownTabs.js +1 -1
  25. package/dist/esm/components/MarkdownTextarea/MarkdownTextarea.js +1 -1
  26. package/dist/esm/components/MarkdownToolbar.js +55 -25
  27. package/dist/esm/components/MarkdownToolbar.spec.js +94 -0
  28. package/dist/esm/components/icons.js +1 -1
  29. package/dist/esm/dialogs/CheatsheetModalDialog.js +1 -1
  30. package/dist/esm/dialogs/EmdebExternalContentDialog.js +1 -1
  31. package/dist/esm/dialogs/SpecialCharacterModalDialog.js +3 -2
  32. package/dist/esm/dialogs/ZenModeModalDialog.js +1 -1
  33. package/dist/types/MarkdownActions.spec.d.ts +1 -0
  34. package/dist/types/components/HeadingSelector.d.ts +4 -2
  35. package/dist/types/components/MarkdownToolbar.spec.d.ts +1 -0
  36. package/package.json +9 -8
@@ -0,0 +1,144 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ const _react = /*#__PURE__*/ _interop_require_wildcard(require("react"));
6
+ require("@testing-library/jest-dom/extend-expect");
7
+ const _react1 = require("@testing-library/react");
8
+ const _userevent = /*#__PURE__*/ _interop_require_default(require("@testing-library/user-event"));
9
+ const _MarkdownToolbar = require("./MarkdownToolbar");
10
+ function _interop_require_default(obj) {
11
+ return obj && obj.__esModule ? obj : {
12
+ default: obj
13
+ };
14
+ }
15
+ function _getRequireWildcardCache(nodeInterop) {
16
+ if (typeof WeakMap !== "function") return null;
17
+ var cacheBabelInterop = new WeakMap();
18
+ var cacheNodeInterop = new WeakMap();
19
+ return (_getRequireWildcardCache = function(nodeInterop) {
20
+ return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
21
+ })(nodeInterop);
22
+ }
23
+ function _interop_require_wildcard(obj, nodeInterop) {
24
+ if (!nodeInterop && obj && obj.__esModule) {
25
+ return obj;
26
+ }
27
+ if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
28
+ return {
29
+ default: obj
30
+ };
31
+ }
32
+ var cache = _getRequireWildcardCache(nodeInterop);
33
+ if (cache && cache.has(obj)) {
34
+ return cache.get(obj);
35
+ }
36
+ var newObj = {
37
+ __proto__: null
38
+ };
39
+ var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
40
+ for(var key in obj){
41
+ if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
42
+ var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
43
+ if (desc && (desc.get || desc.set)) {
44
+ Object.defineProperty(newObj, key, desc);
45
+ } else {
46
+ newObj[key] = obj[key];
47
+ }
48
+ }
49
+ }
50
+ newObj.default = obj;
51
+ if (cache) {
52
+ cache.set(obj, newObj);
53
+ }
54
+ return newObj;
55
+ }
56
+ (0, _react1.configure)({
57
+ testIdAttribute: 'data-test-id'
58
+ });
59
+ const createProps = ()=>({
60
+ canUploadAssets: true,
61
+ disabled: false,
62
+ mode: 'default',
63
+ actions: {
64
+ headings: {
65
+ h1: jest.fn(),
66
+ h2: jest.fn(),
67
+ h3: jest.fn()
68
+ },
69
+ simple: {
70
+ bold: jest.fn(),
71
+ italic: jest.fn(),
72
+ quote: jest.fn(),
73
+ ol: jest.fn(),
74
+ ul: jest.fn(),
75
+ strike: jest.fn(),
76
+ code: jest.fn(),
77
+ hr: jest.fn(),
78
+ indent: jest.fn(),
79
+ dedent: jest.fn()
80
+ },
81
+ history: {
82
+ undo: jest.fn(),
83
+ redo: jest.fn()
84
+ },
85
+ insertLink: jest.fn(),
86
+ insertSpecialCharacter: jest.fn(),
87
+ insertTable: jest.fn(),
88
+ organizeLinks: jest.fn(),
89
+ embedExternalContent: jest.fn(),
90
+ linkExistingMedia: jest.fn(),
91
+ addNewMedia: jest.fn(),
92
+ openZenMode: jest.fn(),
93
+ closeZenMode: jest.fn()
94
+ }
95
+ });
96
+ describe('MarkdownToolbar', ()=>{
97
+ beforeEach(()=>{
98
+ jest.clearAllMocks();
99
+ });
100
+ it('opens the heading menu and calls the selected heading action', async ()=>{
101
+ const props = createProps();
102
+ (0, _react1.render)(/*#__PURE__*/ _react.createElement(_MarkdownToolbar.MarkdownToolbar, props));
103
+ await _userevent.default.click(_react1.screen.getByRole('button', {
104
+ name: 'Headings'
105
+ }));
106
+ await _userevent.default.click(_react1.screen.getByRole('menuitem', {
107
+ name: 'Heading 1'
108
+ }));
109
+ expect(props.actions.headings.h1).toHaveBeenCalledTimes(1);
110
+ });
111
+ it('toggles additional actions and exposes the expanded state', async ()=>{
112
+ const props = createProps();
113
+ (0, _react1.render)(/*#__PURE__*/ _react.createElement(_MarkdownToolbar.MarkdownToolbar, props));
114
+ const toggle = _react1.screen.getByRole('button', {
115
+ name: 'More actions'
116
+ });
117
+ expect(toggle).toHaveAttribute('aria-expanded', 'false');
118
+ await _userevent.default.click(toggle);
119
+ expect(_react1.screen.getByRole('button', {
120
+ name: 'Hide additional actions'
121
+ })).toHaveAttribute('aria-expanded', 'true');
122
+ expect(_react1.screen.getByRole('button', {
123
+ name: 'Undo'
124
+ })).toBeInTheDocument();
125
+ });
126
+ it('triggers a toolbar action once per click', async ()=>{
127
+ const props = createProps();
128
+ (0, _react1.render)(/*#__PURE__*/ _react.createElement(_MarkdownToolbar.MarkdownToolbar, props));
129
+ await _userevent.default.click(_react1.screen.getByRole('button', {
130
+ name: 'Bold'
131
+ }));
132
+ expect(props.actions.simple.bold).toHaveBeenCalledTimes(1);
133
+ });
134
+ it('still supports keyboard-style activation', async ()=>{
135
+ const props = createProps();
136
+ (0, _react1.render)(/*#__PURE__*/ _react.createElement(_MarkdownToolbar.MarkdownToolbar, props));
137
+ const bold = _react1.screen.getByRole('button', {
138
+ name: 'Bold'
139
+ });
140
+ bold.focus();
141
+ await _userevent.default.keyboard('{Enter}');
142
+ expect(props.actions.simple.bold).toHaveBeenCalledTimes(1);
143
+ });
144
+ });
@@ -41,7 +41,7 @@ _export(exports, {
41
41
  }
42
42
  });
43
43
  const _react = /*#__PURE__*/ _interop_require_wildcard(require("react"));
44
- const _emotion = require("emotion");
44
+ const _css = require("@emotion/css");
45
45
  function _getRequireWildcardCache(nodeInterop) {
46
46
  if (typeof WeakMap !== "function") return null;
47
47
  var cacheBabelInterop = new WeakMap();
@@ -83,7 +83,7 @@ function _interop_require_wildcard(obj, nodeInterop) {
83
83
  }
84
84
  return newObj;
85
85
  }
86
- const srOnly = (0, _emotion.css)({
86
+ const srOnly = (0, _css.css)({
87
87
  position: 'absolute',
88
88
  width: '1px',
89
89
  height: '1px',
@@ -19,7 +19,7 @@ _export(exports, {
19
19
  const _react = /*#__PURE__*/ _interop_require_wildcard(require("react"));
20
20
  const _f36components = require("@contentful/f36-components");
21
21
  const _f36tokens = /*#__PURE__*/ _interop_require_default(require("@contentful/f36-tokens"));
22
- const _emotion = require("emotion");
22
+ const _css = require("@emotion/css");
23
23
  const _types = require("../types");
24
24
  function _interop_require_default(obj) {
25
25
  return obj && obj.__esModule ? obj : {
@@ -68,56 +68,56 @@ function _interop_require_wildcard(obj, nodeInterop) {
68
68
  return newObj;
69
69
  }
70
70
  const styles = {
71
- flexColumnContainer: (0, _emotion.css)({
71
+ flexColumnContainer: (0, _css.css)({
72
72
  display: 'flex',
73
73
  flexWrap: 'nowrap'
74
74
  }),
75
- flexColumn: (0, _emotion.css)({
75
+ flexColumn: (0, _css.css)({
76
76
  flexGrow: 1
77
77
  }),
78
- verticalDivider: (0, _emotion.css)({
78
+ verticalDivider: (0, _css.css)({
79
79
  borderRight: `1px solid ${_f36tokens.default.gray500}`,
80
80
  paddingRight: _f36tokens.default.spacing3Xl,
81
81
  marginRight: _f36tokens.default.spacing2Xl
82
82
  }),
83
- preview: (0, _emotion.css)({
83
+ preview: (0, _css.css)({
84
84
  display: 'inline-block',
85
85
  paddingRight: _f36tokens.default.spacingL,
86
86
  width: '50%'
87
87
  }),
88
- unOrderedListPreview: (0, _emotion.css)({
88
+ unOrderedListPreview: (0, _css.css)({
89
89
  listStyleType: 'disc',
90
90
  marginLeft: _f36tokens.default.spacingS
91
91
  }),
92
- orderedListPreview: (0, _emotion.css)({
92
+ orderedListPreview: (0, _css.css)({
93
93
  listStyleType: 'decimal',
94
94
  marginLeft: _f36tokens.default.spacingS
95
95
  }),
96
- markup: (0, _emotion.css)({
96
+ markup: (0, _css.css)({
97
97
  display: 'inline-block',
98
98
  color: _f36tokens.default.gray600,
99
99
  paddingLeft: _f36tokens.default.spacingL,
100
100
  width: '50%'
101
101
  }),
102
- h1: (0, _emotion.css)({
102
+ h1: (0, _css.css)({
103
103
  fontSize: _f36tokens.default.fontSize2Xl
104
104
  }),
105
- h2: (0, _emotion.css)({
105
+ h2: (0, _css.css)({
106
106
  fontSize: _f36tokens.default.fontSizeXl
107
107
  }),
108
- h3: (0, _emotion.css)({
108
+ h3: (0, _css.css)({
109
109
  fontSize: _f36tokens.default.fontSizeL
110
110
  }),
111
- helpItem: (0, _emotion.css)({
111
+ helpItem: (0, _css.css)({
112
112
  marginBottom: _f36tokens.default.spacingS,
113
113
  display: 'flex',
114
114
  alignItems: 'flex-end',
115
115
  height: _f36tokens.default.spacingXl
116
116
  }),
117
- helpLink: (0, _emotion.css)({
117
+ helpLink: (0, _css.css)({
118
118
  margin: 'auto'
119
119
  }),
120
- flexRowContainer: (0, _emotion.css)({
120
+ flexRowContainer: (0, _css.css)({
121
121
  display: 'flex',
122
122
  width: '100%',
123
123
  justifyContent: 'center',
@@ -130,13 +130,13 @@ const CheatsheetModalDialog = ()=>{
130
130
  }, /*#__PURE__*/ _react.createElement("div", {
131
131
  className: styles.flexColumnContainer
132
132
  }, /*#__PURE__*/ _react.createElement("div", {
133
- className: (0, _emotion.cx)(styles.flexColumn, styles.verticalDivider)
133
+ className: (0, _css.cx)(styles.flexColumn, styles.verticalDivider)
134
134
  }, /*#__PURE__*/ _react.createElement("div", {
135
135
  className: styles.helpItem
136
136
  }, /*#__PURE__*/ _react.createElement(_f36components.Heading, {
137
137
  marginBottom: "none",
138
138
  as: "h1",
139
- className: (0, _emotion.cx)(styles.preview, styles.h1)
139
+ className: (0, _css.cx)(styles.preview, styles.h1)
140
140
  }, "H1"), /*#__PURE__*/ _react.createElement("div", {
141
141
  className: styles.markup
142
142
  }, "# heading")), /*#__PURE__*/ _react.createElement("div", {
@@ -144,7 +144,7 @@ const CheatsheetModalDialog = ()=>{
144
144
  }, /*#__PURE__*/ _react.createElement(_f36components.Heading, {
145
145
  marginBottom: "none",
146
146
  as: "h2",
147
- className: (0, _emotion.cx)(styles.preview, styles.h2)
147
+ className: (0, _css.cx)(styles.preview, styles.h2)
148
148
  }, "H2"), /*#__PURE__*/ _react.createElement("div", {
149
149
  className: styles.markup
150
150
  }, "## heading")), /*#__PURE__*/ _react.createElement("div", {
@@ -152,7 +152,7 @@ const CheatsheetModalDialog = ()=>{
152
152
  }, /*#__PURE__*/ _react.createElement(_f36components.Heading, {
153
153
  marginBottom: "none",
154
154
  as: "h3",
155
- className: (0, _emotion.cx)(styles.preview, styles.h3)
155
+ className: (0, _css.cx)(styles.preview, styles.h3)
156
156
  }, "H3"), /*#__PURE__*/ _react.createElement("div", {
157
157
  className: styles.markup
158
158
  }, "### heading")), /*#__PURE__*/ _react.createElement("div", {
@@ -19,8 +19,8 @@ _export(exports, {
19
19
  const _react = /*#__PURE__*/ _interop_require_wildcard(require("react"));
20
20
  const _f36components = require("@contentful/f36-components");
21
21
  const _f36tokens = /*#__PURE__*/ _interop_require_default(require("@contentful/f36-tokens"));
22
+ const _css = require("@emotion/css");
22
23
  const _core = require("@lingui/core");
23
- const _emotion = require("emotion");
24
24
  const _types = require("../types");
25
25
  const _isValidUrl = require("../utils/isValidUrl");
26
26
  function _interop_require_default(obj) {
@@ -70,17 +70,17 @@ function _interop_require_wildcard(obj, nodeInterop) {
70
70
  return newObj;
71
71
  }
72
72
  const styles = {
73
- widthFiledGroup: (0, _emotion.css)({
73
+ widthFiledGroup: (0, _css.css)({
74
74
  display: 'flex',
75
75
  flexWrap: 'nowrap',
76
76
  alignItems: 'flex-start'
77
77
  }),
78
- radioButtonGroup: (0, _emotion.css)({
78
+ radioButtonGroup: (0, _css.css)({
79
79
  display: 'inline-flex',
80
80
  alignItems: 'flex-start',
81
81
  paddingTop: _f36tokens.default.spacingXl
82
82
  }),
83
- radioButton: (0, _emotion.css)({
83
+ radioButton: (0, _css.css)({
84
84
  marginLeft: _f36tokens.default.spacingM
85
85
  })
86
86
  };
@@ -19,7 +19,7 @@ _export(exports, {
19
19
  const _react = /*#__PURE__*/ _interop_require_wildcard(require("react"));
20
20
  const _f36components = require("@contentful/f36-components");
21
21
  const _f36tokens = /*#__PURE__*/ _interop_require_default(require("@contentful/f36-tokens"));
22
- const _emotion = require("emotion");
22
+ const _css = require("@emotion/css");
23
23
  const _types = require("../types");
24
24
  const _specialCharacters = require("../utils/specialCharacters");
25
25
  function _interop_require_default(obj) {
@@ -69,11 +69,11 @@ function _interop_require_wildcard(obj, nodeInterop) {
69
69
  return newObj;
70
70
  }
71
71
  const styles = {
72
- buttonPanel: (0, _emotion.css)({
72
+ buttonPanel: (0, _css.css)({
73
73
  display: 'flex',
74
74
  flexWrap: 'wrap'
75
75
  }),
76
- charButton: (0, _emotion.css)({
76
+ charButton: (0, _css.css)({
77
77
  border: `1px solid ${_f36tokens.default.gray500}`,
78
78
  width: '4.1rem',
79
79
  height: '4.1rem',
@@ -81,13 +81,14 @@ const styles = {
81
81
  marginTop: _f36tokens.default.spacing2Xs,
82
82
  marginRight: _f36tokens.default.spacing2Xs
83
83
  }),
84
- selectedCharButton: (0, _emotion.css)({
84
+ selectedCharButton: (0, _css.css)({
85
85
  backgroundColor: _f36tokens.default.gray100
86
86
  }),
87
- tooltip: (0, _emotion.css)({
88
- zIndex: 1000
87
+ tooltip: (0, _css.css)({
88
+ zIndex: 1000,
89
+ pointerEvents: 'none'
89
90
  }),
90
- button: (0, _emotion.css)({
91
+ button: (0, _css.css)({
91
92
  marginTop: _f36tokens.default.spacingM,
92
93
  marginRight: _f36tokens.default.spacingS
93
94
  })
@@ -20,7 +20,7 @@ const _react = /*#__PURE__*/ _interop_require_wildcard(require("react"));
20
20
  const _f36components = require("@contentful/f36-components");
21
21
  const _f36icons = require("@contentful/f36-icons");
22
22
  const _f36tokens = /*#__PURE__*/ _interop_require_default(require("@contentful/f36-tokens"));
23
- const _emotion = require("emotion");
23
+ const _css = require("@emotion/css");
24
24
  const _MarkdownBottomBar = require("../components/MarkdownBottomBar");
25
25
  const _MarkdownPreviewSkeleton = /*#__PURE__*/ _interop_require_default(require("../components/MarkdownPreviewSkeleton"));
26
26
  const _MarkdownTextarea = require("../components/MarkdownTextarea/MarkdownTextarea");
@@ -76,42 +76,42 @@ function _interop_require_wildcard(obj, nodeInterop) {
76
76
  }
77
77
  const MarkdownPreview = /*#__PURE__*/ _react.lazy(()=>Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("../components/MarkdownPreview"))));
78
78
  const styles = {
79
- root: (0, _emotion.css)({
79
+ root: (0, _css.css)({
80
80
  display: 'grid',
81
81
  gridTemplateRows: 'min-content 1fr min-content',
82
82
  gridTemplateColumns: '1fr 1px 1fr',
83
83
  height: '85vh'
84
84
  }),
85
- topSplit: (0, _emotion.css)({
85
+ topSplit: (0, _css.css)({
86
86
  gridRow: '1 / 2',
87
87
  gridColumn: '1 / 4'
88
88
  }),
89
- bottomSplit: (0, _emotion.css)({
89
+ bottomSplit: (0, _css.css)({
90
90
  gridRow: '3 / 4',
91
91
  gridColumn: '1 / 4'
92
92
  }),
93
- editorSplit: (0, _emotion.css)({
93
+ editorSplit: (0, _css.css)({
94
94
  gridRow: '2 / 3',
95
95
  gridColumn: '1 / 2',
96
96
  overflowY: 'scroll'
97
97
  }),
98
- editorSplitFullscreen: (0, _emotion.css)({
98
+ editorSplitFullscreen: (0, _css.css)({
99
99
  gridRow: '2 / 3',
100
100
  gridColumn: '1 / 4',
101
101
  overflowY: 'scroll'
102
102
  }),
103
- previewSplit: (0, _emotion.css)({
103
+ previewSplit: (0, _css.css)({
104
104
  gridRow: '2 / 3',
105
105
  gridColumn: '3 / 4',
106
106
  overflowY: 'scroll'
107
107
  }),
108
- separator: (0, _emotion.css)({
108
+ separator: (0, _css.css)({
109
109
  gridRow: '2 / 3',
110
110
  gridColumn: '2 / 3',
111
111
  backgroundColor: _f36tokens.default.gray400,
112
112
  width: '1px'
113
113
  }),
114
- button: (0, _emotion.css)({
114
+ button: (0, _css.css)({
115
115
  cursor: 'pointer',
116
116
  zIndex: 105,
117
117
  height: '30px',
@@ -119,19 +119,19 @@ const styles = {
119
119
  border: `1px solid ${_f36tokens.default.gray400}`,
120
120
  padding: 0
121
121
  }),
122
- hideButton: (0, _emotion.css)({
122
+ hideButton: (0, _css.css)({
123
123
  gridRow: '2 / 3',
124
124
  gridColumn: '2 / 3',
125
125
  justifySelf: 'end',
126
126
  alignSelf: 'center'
127
127
  }),
128
- showButton: (0, _emotion.css)({
128
+ showButton: (0, _css.css)({
129
129
  gridRow: '2 / 3',
130
130
  gridColumn: '3 / 4',
131
131
  justifySelf: 'end',
132
132
  alignSelf: 'center'
133
133
  }),
134
- icon: (0, _emotion.css)({
134
+ icon: (0, _css.css)({
135
135
  verticalAlign: 'middle'
136
136
  })
137
137
  };
@@ -177,7 +177,7 @@ const ZenModeModalDialog = (props)=>{
177
177
  canUploadAssets: false,
178
178
  actions: actions
179
179
  })), /*#__PURE__*/ _react.createElement(_f36components.Grid.Item, {
180
- className: (0, _emotion.cx)(styles.editorSplit, {
180
+ className: (0, _css.cx)(styles.editorSplit, {
181
181
  [styles.editorSplitFullscreen]: showPreview === false
182
182
  })
183
183
  }, /*#__PURE__*/ _react.createElement(_MarkdownTextarea.MarkdownTextarea, {
@@ -207,7 +207,7 @@ const ZenModeModalDialog = (props)=>{
207
207
  }))), showPreview && /*#__PURE__*/ _react.createElement(_f36components.Grid.Item, {
208
208
  className: styles.separator
209
209
  }), showPreview && /*#__PURE__*/ _react.createElement("button", {
210
- className: (0, _emotion.cx)(styles.button, styles.hideButton),
210
+ className: (0, _css.cx)(styles.button, styles.hideButton),
211
211
  "aria-label": "Hide preview",
212
212
  onClick: ()=>{
213
213
  setShowPreview(false);
@@ -217,7 +217,7 @@ const ZenModeModalDialog = (props)=>{
217
217
  size: "tiny",
218
218
  className: styles.icon
219
219
  })), !showPreview && /*#__PURE__*/ _react.createElement("button", {
220
- className: (0, _emotion.cx)(styles.button, styles.showButton),
220
+ className: (0, _css.cx)(styles.button, styles.showButton),
221
221
  "aria-label": "Show preview",
222
222
  onClick: ()=>{
223
223
  setShowPreview(true);
@@ -0,0 +1,159 @@
1
+ jest.mock('./dialogs/InsertLinkModalDialog', ()=>({
2
+ openInsertLinkDialog: jest.fn()
3
+ }));
4
+ jest.mock('./dialogs/SpecialCharacterModalDialog', ()=>({
5
+ openInsertSpecialCharacter: jest.fn()
6
+ }));
7
+ jest.mock('./dialogs/InsertTableModalDialog', ()=>({
8
+ openInsertTableDialog: jest.fn()
9
+ }));
10
+ jest.mock('./dialogs/ZenModeModalDialog', ()=>({
11
+ openZenMode: jest.fn()
12
+ }));
13
+ import { openInsertLinkDialog } from './dialogs/InsertLinkModalDialog';
14
+ import { openInsertTableDialog } from './dialogs/InsertTableModalDialog';
15
+ import { openInsertSpecialCharacter } from './dialogs/SpecialCharacterModalDialog';
16
+ import { openZenMode } from './dialogs/ZenModeModalDialog';
17
+ import { createMarkdownActions } from './MarkdownActions';
18
+ const mockedOpenInsertLinkDialog = openInsertLinkDialog;
19
+ const mockedOpenInsertSpecialCharacter = openInsertSpecialCharacter;
20
+ const mockedOpenInsertTableDialog = openInsertTableDialog;
21
+ const mockedOpenZenMode = openZenMode;
22
+ const createEditor = ()=>({
23
+ actions: {
24
+ h1: jest.fn(),
25
+ h2: jest.fn(),
26
+ h3: jest.fn(),
27
+ bold: jest.fn(),
28
+ italic: jest.fn(),
29
+ quote: jest.fn(),
30
+ ol: jest.fn(),
31
+ ul: jest.fn(),
32
+ strike: jest.fn(),
33
+ code: jest.fn(),
34
+ hr: jest.fn(),
35
+ indent: jest.fn(),
36
+ dedent: jest.fn(),
37
+ undo: jest.fn(),
38
+ redo: jest.fn(),
39
+ link: jest.fn(),
40
+ table: jest.fn()
41
+ },
42
+ usePrimarySelection: jest.fn(),
43
+ getSelectedText: jest.fn(),
44
+ insert: jest.fn(),
45
+ getContent: jest.fn(),
46
+ setContent: jest.fn(),
47
+ setCursor: jest.fn(),
48
+ focus: jest.fn()
49
+ });
50
+ const createSdk = ()=>({
51
+ dialogs: {},
52
+ locales: {
53
+ default: 'en-US',
54
+ fallbacks: {}
55
+ },
56
+ notifier: {
57
+ success: jest.fn()
58
+ }
59
+ });
60
+ describe('createMarkdownActions', ()=>{
61
+ beforeEach(()=>{
62
+ jest.clearAllMocks();
63
+ });
64
+ it('dispatches toolbar actions to the editor', ()=>{
65
+ const editor = createEditor();
66
+ const actions = createMarkdownActions({
67
+ sdk: createSdk(),
68
+ editor: editor,
69
+ locale: 'en-US'
70
+ });
71
+ actions.headings.h2();
72
+ actions.simple.bold();
73
+ actions.simple.ul();
74
+ actions.history.undo();
75
+ expect(editor.actions.h2).toHaveBeenCalledTimes(1);
76
+ expect(editor.actions.bold).toHaveBeenCalledTimes(1);
77
+ expect(editor.actions.ul).toHaveBeenCalledTimes(1);
78
+ expect(editor.actions.undo).toHaveBeenCalledTimes(1);
79
+ });
80
+ it('opens the link dialog with the selected text and inserts a titled link', async ()=>{
81
+ const editor = createEditor();
82
+ const sdk = createSdk();
83
+ editor.getSelectedText.mockReturnValue('Contentful');
84
+ mockedOpenInsertLinkDialog.mockResolvedValue({
85
+ url: 'https://contentful.com',
86
+ text: 'ignored',
87
+ title: 'The best headless CMS'
88
+ });
89
+ const actions = createMarkdownActions({
90
+ sdk,
91
+ editor: editor,
92
+ locale: 'en-US'
93
+ });
94
+ await actions.insertLink();
95
+ expect(editor.usePrimarySelection).toHaveBeenCalledTimes(1);
96
+ expect(mockedOpenInsertLinkDialog).toHaveBeenCalledWith(sdk.dialogs, {
97
+ selectedText: 'Contentful'
98
+ });
99
+ expect(editor.actions.link).toHaveBeenCalledWith('https://contentful.com', 'Contentful', 'The best headless CMS');
100
+ });
101
+ it('falls back to dialog text when no editor selection exists', async ()=>{
102
+ const editor = createEditor();
103
+ editor.getSelectedText.mockReturnValue('');
104
+ mockedOpenInsertLinkDialog.mockResolvedValue({
105
+ url: 'https://contentful.com',
106
+ text: 'Contentful',
107
+ title: ''
108
+ });
109
+ const actions = createMarkdownActions({
110
+ sdk: createSdk(),
111
+ editor: editor,
112
+ locale: 'en-US'
113
+ });
114
+ await actions.insertLink();
115
+ expect(editor.actions.link).toHaveBeenCalledWith('https://contentful.com', 'Contentful', '');
116
+ });
117
+ it('inserts special characters and tables from dialog results', async ()=>{
118
+ const editor = createEditor();
119
+ mockedOpenInsertSpecialCharacter.mockResolvedValue('€');
120
+ mockedOpenInsertTableDialog.mockResolvedValue({
121
+ rows: 2,
122
+ cols: 3
123
+ });
124
+ const actions = createMarkdownActions({
125
+ sdk: createSdk(),
126
+ editor: editor,
127
+ locale: 'en-US'
128
+ });
129
+ await actions.insertSpecialCharacter();
130
+ await actions.insertTable();
131
+ expect(editor.insert).toHaveBeenCalledWith('€');
132
+ expect(editor.actions.table).toHaveBeenCalledWith({
133
+ rows: 2,
134
+ cols: 3
135
+ });
136
+ });
137
+ it('updates content and restores cursor when zen mode closes', async ()=>{
138
+ const editor = createEditor();
139
+ mockedOpenZenMode.mockResolvedValue({
140
+ value: 'updated markdown',
141
+ cursor: {
142
+ line: 2,
143
+ ch: 4
144
+ }
145
+ });
146
+ const actions = createMarkdownActions({
147
+ sdk: createSdk(),
148
+ editor: editor,
149
+ locale: 'en-US'
150
+ });
151
+ await actions.openZenMode();
152
+ expect(editor.setContent).toHaveBeenCalledWith('updated markdown');
153
+ expect(editor.setCursor).toHaveBeenCalledWith({
154
+ line: 2,
155
+ ch: 4
156
+ });
157
+ expect(editor.focus).toHaveBeenCalledTimes(1);
158
+ });
159
+ });
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import tokens from '@contentful/f36-tokens';
3
3
  import { FieldConnector } from '@contentful/field-editor-shared';
4
- import { css } from 'emotion';
4
+ import { css } from '@emotion/css';
5
5
  import { MarkdownBottomBar, MarkdownHelp } from './components/MarkdownBottomBar';
6
6
  import { MarkdownConstraints } from './components/MarkdownConstraints';
7
7
  import MarkdownPreviewSkeleton from './components/MarkdownPreviewSkeleton';
@@ -1,10 +1,18 @@
1
1
  import * as React from 'react';
2
2
  import { Menu } from '@contentful/f36-components';
3
3
  export const HeadingSelector = (props)=>{
4
+ const [isOpen, setIsOpen] = React.useState(false);
4
5
  const handleMenuClick = (heading)=>{
6
+ setIsOpen(false);
5
7
  props.onSelect(heading);
6
8
  };
7
- return /*#__PURE__*/ React.createElement(Menu, null, /*#__PURE__*/ React.createElement(Menu.Trigger, null, props.children), /*#__PURE__*/ React.createElement(Menu.List, null, /*#__PURE__*/ React.createElement(Menu.Item, {
9
+ return /*#__PURE__*/ React.createElement(Menu, {
10
+ isOpen: isOpen,
11
+ onClose: ()=>setIsOpen(false)
12
+ }, /*#__PURE__*/ React.createElement(Menu.Trigger, null, props.renderTrigger({
13
+ isOpen,
14
+ onClick: ()=>setIsOpen((current)=>!current)
15
+ })), /*#__PURE__*/ React.createElement(Menu.List, null, /*#__PURE__*/ React.createElement(Menu.Item, {
8
16
  testId: "markdown-action-button-heading-h1",
9
17
  onClick: ()=>handleMenuClick('h1')
10
18
  }, "Heading 1"), /*#__PURE__*/ React.createElement(Menu.Item, {
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import { Paragraph, Stack, TextLink } from '@contentful/f36-components';
3
3
  import tokens from '@contentful/f36-tokens';
4
- import { css } from 'emotion';
4
+ import { css } from '@emotion/css';
5
5
  const SANITIZE_LINK = 'https://en.wikipedia.org/wiki/HTML_sanitization';
6
6
  const styles = {
7
7
  root: css({
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import tokens from '@contentful/f36-tokens';
3
3
  import { CharCounter, CharValidation, ConstraintsUtils } from '@contentful/field-editor-shared';
4
- import { css } from 'emotion';
4
+ import { css } from '@emotion/css';
5
5
  const styles = {
6
6
  root: css({
7
7
  display: 'flex',
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import ReactMarkdown from 'react-markdown';
3
3
  import tokens from '@contentful/f36-tokens';
4
- import { css, cx } from 'emotion';
4
+ import { css, cx } from '@emotion/css';
5
5
  import rehypeRaw from 'rehype-raw';
6
6
  import rehypeSanitize from 'rehype-sanitize';
7
7
  import remarkGfm from 'remark-gfm';