@pie-lib/editable-html 11.1.1 → 11.3.0-beta.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.
Files changed (194) hide show
  1. package/CHANGELOG.md +8 -4
  2. package/NEXT.CHANGELOG.json +1 -0
  3. package/lib/__tests__/editor.test.js +470 -0
  4. package/lib/__tests__/serialization.test.js +246 -0
  5. package/lib/__tests__/utils.js +106 -0
  6. package/lib/block-tags.js +25 -0
  7. package/lib/constants.js +16 -0
  8. package/lib/editor.js +349 -88
  9. package/lib/index.js +26 -10
  10. package/lib/parse-html.js +1 -1
  11. package/lib/plugins/characters/custom-popper.js +1 -1
  12. package/lib/plugins/characters/index.js +9 -4
  13. package/lib/plugins/characters/utils.js +13 -13
  14. package/lib/plugins/css/icons/index.js +37 -0
  15. package/lib/plugins/css/index.js +397 -0
  16. package/lib/plugins/customPlugin/index.js +114 -0
  17. package/lib/plugins/html/icons/index.js +1 -1
  18. package/lib/plugins/html/index.js +12 -8
  19. package/lib/plugins/image/__tests__/component.test.js +51 -0
  20. package/lib/plugins/image/__tests__/image-toolbar-logic.test.js +56 -0
  21. package/lib/plugins/image/__tests__/image-toolbar.test.js +26 -0
  22. package/lib/plugins/image/__tests__/index.test.js +98 -0
  23. package/lib/plugins/image/__tests__/insert-image-handler.test.js +125 -0
  24. package/lib/plugins/image/__tests__/mock-change.js +25 -0
  25. package/lib/plugins/image/alt-dialog.js +1 -1
  26. package/lib/plugins/image/component.js +1 -1
  27. package/lib/plugins/image/image-toolbar.js +1 -1
  28. package/lib/plugins/image/index.js +3 -2
  29. package/lib/plugins/image/insert-image-handler.js +14 -5
  30. package/lib/plugins/index.js +271 -12
  31. package/lib/plugins/list/__tests__/index.test.js +79 -0
  32. package/lib/plugins/list/index.js +131 -1
  33. package/lib/plugins/math/__tests__/index.test.js +300 -0
  34. package/lib/plugins/math/index.js +92 -57
  35. package/lib/plugins/media/__tests__/index.test.js +71 -0
  36. package/lib/plugins/media/index.js +6 -3
  37. package/lib/plugins/media/media-dialog.js +99 -58
  38. package/lib/plugins/media/media-toolbar.js +1 -1
  39. package/lib/plugins/media/media-wrapper.js +1 -1
  40. package/lib/plugins/rendering/index.js +46 -0
  41. package/lib/plugins/respArea/drag-in-the-blank/choice.js +6 -3
  42. package/lib/plugins/respArea/drag-in-the-blank/index.js +1 -1
  43. package/lib/plugins/respArea/explicit-constructed-response/index.js +12 -10
  44. package/lib/plugins/respArea/icons/index.js +1 -1
  45. package/lib/plugins/respArea/index.js +70 -22
  46. package/lib/plugins/respArea/inline-dropdown/index.js +11 -6
  47. package/lib/plugins/respArea/math-templated/index.js +130 -0
  48. package/lib/plugins/respArea/utils.js +17 -2
  49. package/lib/plugins/table/CustomTablePlugin.js +133 -0
  50. package/lib/plugins/table/__tests__/index.test.js +442 -0
  51. package/lib/plugins/table/__tests__/table-toolbar.test.js +54 -0
  52. package/lib/plugins/table/icons/index.js +1 -1
  53. package/lib/plugins/table/index.js +44 -60
  54. package/lib/plugins/table/table-toolbar.js +34 -5
  55. package/lib/plugins/textAlign/icons/index.js +226 -0
  56. package/lib/plugins/textAlign/index.js +34 -0
  57. package/lib/plugins/toolbar/__tests__/default-toolbar.test.js +128 -0
  58. package/lib/plugins/toolbar/__tests__/editor-and-toolbar.test.js +51 -0
  59. package/lib/plugins/toolbar/__tests__/toolbar-buttons.test.js +54 -0
  60. package/lib/plugins/toolbar/__tests__/toolbar.test.js +120 -0
  61. package/lib/plugins/toolbar/default-toolbar.js +83 -28
  62. package/lib/plugins/toolbar/done-button.js +6 -3
  63. package/lib/plugins/toolbar/editor-and-toolbar.js +19 -20
  64. package/lib/plugins/toolbar/index.js +1 -1
  65. package/lib/plugins/toolbar/toolbar-buttons.js +45 -12
  66. package/lib/plugins/toolbar/toolbar.js +36 -12
  67. package/lib/plugins/utils.js +1 -1
  68. package/lib/serialization.js +234 -45
  69. package/lib/theme.js +1 -1
  70. package/package.json +6 -6
  71. package/src/__tests__/editor.test.jsx +363 -0
  72. package/src/__tests__/serialization.test.js +291 -0
  73. package/src/__tests__/utils.js +36 -0
  74. package/src/block-tags.js +17 -0
  75. package/src/constants.js +7 -0
  76. package/src/editor.jsx +303 -49
  77. package/src/index.jsx +19 -10
  78. package/src/plugins/characters/index.jsx +11 -3
  79. package/src/plugins/characters/utils.js +12 -12
  80. package/src/plugins/css/icons/index.jsx +17 -0
  81. package/src/plugins/css/index.jsx +346 -0
  82. package/src/plugins/customPlugin/index.jsx +85 -0
  83. package/src/plugins/html/index.jsx +9 -6
  84. package/src/plugins/image/__tests__/__snapshots__/component.test.jsx.snap +51 -0
  85. package/src/plugins/image/__tests__/__snapshots__/image-toolbar-logic.test.jsx.snap +27 -0
  86. package/src/plugins/image/__tests__/__snapshots__/image-toolbar.test.jsx.snap +44 -0
  87. package/src/plugins/image/__tests__/component.test.jsx +41 -0
  88. package/src/plugins/image/__tests__/image-toolbar-logic.test.jsx +42 -0
  89. package/src/plugins/image/__tests__/image-toolbar.test.jsx +11 -0
  90. package/src/plugins/image/__tests__/index.test.js +95 -0
  91. package/src/plugins/image/__tests__/insert-image-handler.test.js +113 -0
  92. package/src/plugins/image/__tests__/mock-change.js +15 -0
  93. package/src/plugins/image/index.jsx +2 -1
  94. package/src/plugins/image/insert-image-handler.js +13 -6
  95. package/src/plugins/index.jsx +248 -5
  96. package/src/plugins/list/__tests__/index.test.js +54 -0
  97. package/src/plugins/list/index.jsx +130 -0
  98. package/src/plugins/math/__tests__/__snapshots__/index.test.jsx.snap +48 -0
  99. package/src/plugins/math/__tests__/index.test.jsx +245 -0
  100. package/src/plugins/math/index.jsx +87 -56
  101. package/src/plugins/media/__tests__/index.test.js +75 -0
  102. package/src/plugins/media/index.jsx +3 -2
  103. package/src/plugins/media/media-dialog.js +106 -57
  104. package/src/plugins/rendering/index.js +31 -0
  105. package/src/plugins/respArea/drag-in-the-blank/choice.jsx +4 -1
  106. package/src/plugins/respArea/explicit-constructed-response/index.jsx +10 -8
  107. package/src/plugins/respArea/index.jsx +53 -7
  108. package/src/plugins/respArea/inline-dropdown/index.jsx +13 -6
  109. package/src/plugins/respArea/math-templated/index.jsx +104 -0
  110. package/src/plugins/respArea/utils.jsx +11 -0
  111. package/src/plugins/table/CustomTablePlugin.js +113 -0
  112. package/src/plugins/table/__tests__/__snapshots__/table-toolbar.test.jsx.snap +44 -0
  113. package/src/plugins/table/__tests__/index.test.jsx +401 -0
  114. package/src/plugins/table/__tests__/table-toolbar.test.jsx +42 -0
  115. package/src/plugins/table/index.jsx +46 -59
  116. package/src/plugins/table/table-toolbar.jsx +39 -2
  117. package/src/plugins/textAlign/icons/index.jsx +139 -0
  118. package/src/plugins/textAlign/index.jsx +23 -0
  119. package/src/plugins/toolbar/__tests__/__snapshots__/default-toolbar.test.jsx.snap +923 -0
  120. package/src/plugins/toolbar/__tests__/__snapshots__/editor-and-toolbar.test.jsx.snap +20 -0
  121. package/src/plugins/toolbar/__tests__/__snapshots__/toolbar-buttons.test.jsx.snap +36 -0
  122. package/src/plugins/toolbar/__tests__/__snapshots__/toolbar.test.jsx.snap +46 -0
  123. package/src/plugins/toolbar/__tests__/default-toolbar.test.jsx +94 -0
  124. package/src/plugins/toolbar/__tests__/editor-and-toolbar.test.jsx +37 -0
  125. package/src/plugins/toolbar/__tests__/toolbar-buttons.test.jsx +51 -0
  126. package/src/plugins/toolbar/__tests__/toolbar.test.jsx +106 -0
  127. package/src/plugins/toolbar/default-toolbar.jsx +80 -20
  128. package/src/plugins/toolbar/done-button.jsx +3 -1
  129. package/src/plugins/toolbar/editor-and-toolbar.jsx +18 -13
  130. package/src/plugins/toolbar/toolbar-buttons.jsx +52 -11
  131. package/src/plugins/toolbar/toolbar.jsx +31 -8
  132. package/src/serialization.jsx +213 -38
  133. package/README.md +0 -45
  134. package/deploy.sh +0 -16
  135. package/lib/editor.js.map +0 -1
  136. package/lib/index.js.map +0 -1
  137. package/lib/parse-html.js.map +0 -1
  138. package/lib/plugins/characters/custom-popper.js.map +0 -1
  139. package/lib/plugins/characters/index.js.map +0 -1
  140. package/lib/plugins/characters/utils.js.map +0 -1
  141. package/lib/plugins/html/icons/index.js.map +0 -1
  142. package/lib/plugins/html/index.js.map +0 -1
  143. package/lib/plugins/image/alt-dialog.js.map +0 -1
  144. package/lib/plugins/image/component.js.map +0 -1
  145. package/lib/plugins/image/image-toolbar.js.map +0 -1
  146. package/lib/plugins/image/index.js.map +0 -1
  147. package/lib/plugins/image/insert-image-handler.js.map +0 -1
  148. package/lib/plugins/index.js.map +0 -1
  149. package/lib/plugins/list/index.js.map +0 -1
  150. package/lib/plugins/math/index.js.map +0 -1
  151. package/lib/plugins/media/index.js.map +0 -1
  152. package/lib/plugins/media/media-dialog.js.map +0 -1
  153. package/lib/plugins/media/media-toolbar.js.map +0 -1
  154. package/lib/plugins/media/media-wrapper.js.map +0 -1
  155. package/lib/plugins/respArea/drag-in-the-blank/choice.js.map +0 -1
  156. package/lib/plugins/respArea/drag-in-the-blank/index.js.map +0 -1
  157. package/lib/plugins/respArea/explicit-constructed-response/index.js.map +0 -1
  158. package/lib/plugins/respArea/icons/index.js.map +0 -1
  159. package/lib/plugins/respArea/index.js.map +0 -1
  160. package/lib/plugins/respArea/inline-dropdown/index.js.map +0 -1
  161. package/lib/plugins/respArea/utils.js.map +0 -1
  162. package/lib/plugins/table/icons/index.js.map +0 -1
  163. package/lib/plugins/table/index.js.map +0 -1
  164. package/lib/plugins/table/table-toolbar.js.map +0 -1
  165. package/lib/plugins/toolbar/default-toolbar.js.map +0 -1
  166. package/lib/plugins/toolbar/done-button.js.map +0 -1
  167. package/lib/plugins/toolbar/editor-and-toolbar.js.map +0 -1
  168. package/lib/plugins/toolbar/index.js.map +0 -1
  169. package/lib/plugins/toolbar/toolbar-buttons.js.map +0 -1
  170. package/lib/plugins/toolbar/toolbar.js.map +0 -1
  171. package/lib/plugins/utils.js.map +0 -1
  172. package/lib/serialization.js.map +0 -1
  173. package/lib/theme.js.map +0 -1
  174. package/playground/image/data.js +0 -59
  175. package/playground/image/index.html +0 -22
  176. package/playground/image/index.jsx +0 -81
  177. package/playground/index.html +0 -25
  178. package/playground/mathquill/index.html +0 -22
  179. package/playground/mathquill/index.jsx +0 -155
  180. package/playground/package.json +0 -15
  181. package/playground/prod-test/index.html +0 -22
  182. package/playground/prod-test/index.jsx +0 -28
  183. package/playground/schema-override/data.js +0 -29
  184. package/playground/schema-override/image-plugin.jsx +0 -41
  185. package/playground/schema-override/index.html +0 -21
  186. package/playground/schema-override/index.jsx +0 -97
  187. package/playground/serialization/data.js +0 -29
  188. package/playground/serialization/image-plugin.jsx +0 -41
  189. package/playground/serialization/index.html +0 -22
  190. package/playground/serialization/index.jsx +0 -12
  191. package/playground/static.json +0 -3
  192. package/playground/table-examples.html +0 -70
  193. package/playground/webpack.config.js +0 -42
  194. package/static.json +0 -1
@@ -1,17 +1,23 @@
1
1
  import React from 'react';
2
2
  import debug from 'debug';
3
- import injectSheet from 'react-jss';
3
+ import { withStyles } from '@material-ui/core/styles';
4
4
  import classNames from 'classnames';
5
5
  import PropTypes from 'prop-types';
6
6
 
7
- const styles = () => ({
7
+ const styles = (theme) => ({
8
8
  button: {
9
9
  color: 'grey',
10
10
  display: 'inline-flex',
11
11
  padding: '2px',
12
- '& :hover': {
12
+ background: 'none',
13
+ border: 'none',
14
+ cursor: 'pointer',
15
+ '&:hover': {
13
16
  color: 'black',
14
17
  },
18
+ '&:focus': {
19
+ outline: `2px solid ${theme.palette.grey[700]}`,
20
+ },
15
21
  },
16
22
  active: {
17
23
  color: 'black',
@@ -48,27 +54,47 @@ export class RawButton extends React.Component {
48
54
  onClick(e);
49
55
  };
50
56
 
57
+ onKeyDown = (e) => {
58
+ if (e.key === 'Enter' || e.key === ' ') {
59
+ log('[onKeyDown]');
60
+ e.preventDefault();
61
+ const { onClick } = this.props;
62
+ onClick(e);
63
+ }
64
+ };
65
+
51
66
  render() {
52
- const { active, classes, children, disabled, extraStyles } = this.props;
67
+ const { active, classes, children, disabled, extraStyles, ariaLabel } = this.props;
68
+
53
69
  const names = classNames(classes.button, {
54
70
  [classes.active]: active,
55
71
  [classes.disabled]: disabled,
56
72
  });
57
73
 
58
74
  return (
59
- <div style={extraStyles} className={names} onMouseDown={this.onClick}>
75
+ <button
76
+ style={extraStyles}
77
+ className={names}
78
+ onMouseDown={this.onClick}
79
+ onKeyDown={this.onKeyDown}
80
+ disabled={disabled}
81
+ aria-label={ariaLabel}
82
+ aria-pressed={active}
83
+ tabIndex={0}
84
+ >
60
85
  {children}
61
- </div>
86
+ </button>
62
87
  );
63
88
  }
64
89
  }
65
90
 
66
- export const Button = injectSheet(styles())(RawButton);
91
+ export const Button = withStyles(styles)(RawButton);
67
92
 
68
93
  export class RawMarkButton extends React.Component {
69
94
  static propTypes = {
70
95
  onToggle: PropTypes.func.isRequired,
71
96
  mark: PropTypes.string,
97
+ label: PropTypes.string.isRequired,
72
98
  children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]).isRequired,
73
99
  classes: PropTypes.object.isRequired,
74
100
  active: PropTypes.bool,
@@ -83,15 +109,30 @@ export class RawMarkButton extends React.Component {
83
109
  this.props.onToggle(this.props.mark);
84
110
  };
85
111
 
112
+ onKeyDown = (e) => {
113
+ if (e.key === 'Enter' || e.key === ' ') {
114
+ e.preventDefault();
115
+ this.props.onToggle(this.props.mark);
116
+ }
117
+ };
118
+
86
119
  render() {
87
- const { classes, children, active } = this.props;
120
+ const { classes, children, active, label } = this.props;
121
+
88
122
  const names = classNames(classes.button, active && classes.active);
89
123
  return (
90
- <span className={names} onMouseDown={this.onToggle}>
124
+ <button
125
+ className={names}
126
+ onMouseDown={this.onToggle}
127
+ aria-pressed={active}
128
+ onKeyDown={this.onKeyDown}
129
+ aria-label={label}
130
+ tabIndex={0}
131
+ >
91
132
  {children}
92
- </span>
133
+ </button>
93
134
  );
94
135
  }
95
136
  }
96
137
 
97
- export const MarkButton = injectSheet(styles())(RawMarkButton);
138
+ export const MarkButton = withStyles(styles)(RawMarkButton);
@@ -14,16 +14,19 @@ import { findSingleNode, findParentNode } from '../utils';
14
14
  import { withStyles } from '@material-ui/core/styles';
15
15
  import DefaultToolbar from './default-toolbar';
16
16
  import { removeDialogs as removeCharacterDialogs } from '../characters';
17
+ import { PIE_TOOLBAR__CLASS } from '../../constants';
17
18
 
18
19
  const log = debug('@pie-lib:editable-html:plugins:toolbar');
19
20
 
20
- const getCustomToolbar = (plugin, node, value, handleDone, onDataChange) => {
21
+ const getCustomToolbar = (plugin, node, value, handleDone, getFocusedValue, onDataChange) => {
21
22
  if (!plugin) {
22
23
  return;
23
24
  }
25
+
24
26
  if (!plugin.toolbar) {
25
27
  return;
26
28
  }
29
+
27
30
  if (plugin.toolbar.CustomToolbarComp) {
28
31
  /**
29
32
  * Using a pre-defined Component should be preferred
@@ -34,7 +37,7 @@ const getCustomToolbar = (plugin, node, value, handleDone, onDataChange) => {
34
37
  return plugin.toolbar.CustomToolbarComp;
35
38
  } else if (typeof plugin.toolbar.customToolbar === 'function') {
36
39
  log('deprecated - use CustomToolbarComp');
37
- return plugin.toolbar.customToolbar(node, value, handleDone, onDataChange);
40
+ return plugin.toolbar.customToolbar(node, value, handleDone, getFocusedValue, onDataChange);
38
41
  }
39
42
  };
40
43
 
@@ -59,8 +62,13 @@ export class Toolbar extends React.Component {
59
62
  alwaysVisible: PropTypes.bool,
60
63
  ref: PropTypes.func,
61
64
  showDone: PropTypes.bool,
65
+ minWidth: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
66
+ isHidden: PropTypes.bool,
62
67
  }),
63
68
  onDataChange: PropTypes.func,
69
+ doneButtonRef: PropTypes.func,
70
+ onBlur: PropTypes.func,
71
+ onFocus: PropTypes.func,
64
72
  };
65
73
 
66
74
  constructor(props) {
@@ -144,6 +152,9 @@ export class Toolbar extends React.Component {
144
152
  isFocused,
145
153
  onDone,
146
154
  toolbarRef,
155
+ doneButtonRef,
156
+ onBlur,
157
+ onFocus,
147
158
  } = this.props;
148
159
 
149
160
  const node = findSingleNode(value);
@@ -191,7 +202,7 @@ export class Toolbar extends React.Component {
191
202
  this.props.onDataChange(key, data);
192
203
  };
193
204
 
194
- const CustomToolbar = getCustomToolbar(plugin, node, value, handleDone, this.props.onDataChange);
205
+ const CustomToolbar = getCustomToolbar(plugin, node, value, handleDone, getFocusedValue, this.props.onDataChange);
195
206
 
196
207
  const filteredPlugins = plugin && plugin.filterPlugins ? plugin.filterPlugins(node, plugins) : plugins;
197
208
 
@@ -213,21 +224,24 @@ export class Toolbar extends React.Component {
213
224
 
214
225
  const hasDoneButton = defaultToolbarShowDone || customToolbarShowDone;
215
226
 
216
- const names = classNames(classes.toolbar, {
227
+ const names = classNames(classes.toolbar, PIE_TOOLBAR__CLASS, {
217
228
  [classes.toolbarWithNoDone]: !hasDoneButton,
218
229
  [classes.toolbarTop]: toolbarOpts.position === 'top',
219
230
  [classes.toolbarRight]: toolbarOpts.alignment === 'right',
220
231
  [classes.focused]: toolbarOpts.alwaysVisible || isFocused,
221
232
  [classes.autoWidth]: autoWidth,
222
233
  [classes.fullWidth]: !autoWidth,
234
+ [classes.hidden]: toolbarOpts.isHidden === true
223
235
  });
236
+ const customStyles = toolbarOpts.minWidth !== undefined ? { minWidth: toolbarOpts.minWidth } : {};
224
237
 
225
238
  return (
226
- <div className={names} style={extraStyles} onClick={this.onClick} ref={toolbarRef}>
239
+ <div className={names} style={{ ...extraStyles, ...customStyles }} onClick={this.onClick} ref={toolbarRef}>
227
240
  {CustomToolbar ? (
228
241
  <CustomToolbar
229
242
  node={node}
230
243
  value={value}
244
+ getFocusedValue={getFocusedValue}
231
245
  onToolbarDone={this.onToolbarDone}
232
246
  onDataChange={handleDataChange}
233
247
  pluginProps={pluginProps}
@@ -243,6 +257,9 @@ export class Toolbar extends React.Component {
243
257
  onDone={handleDone}
244
258
  deletable={deletable}
245
259
  isHtmlMode={toolbarOpts.isHtmlMode}
260
+ onFocus={onFocus}
261
+ doneButtonRef={doneButtonRef}
262
+ onBlur={onBlur}
246
263
  />
247
264
  )}
248
265
 
@@ -259,7 +276,7 @@ export class Toolbar extends React.Component {
259
276
  <Delete />
260
277
  </IconButton>
261
278
  )}
262
- {customToolbarShowDone && <DoneButton onClick={handleDone} />}
279
+ {customToolbarShowDone && <DoneButton doneButtonRef={doneButtonRef} onClick={handleDone} />}
263
280
  </div>
264
281
  </div>
265
282
  );
@@ -279,7 +296,9 @@ const style = {
279
296
  boxShadow:
280
297
  '0px 1px 5px 0px rgba(0, 0, 0, 0.2), 0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 3px 1px -2px rgba(0, 0, 0, 0.12)',
281
298
  boxSizing: 'border-box',
282
- display: 'none',
299
+ display: 'flex',
300
+ opacity: 0,
301
+ pointerEvents: 'none'
283
302
  },
284
303
  toolbarWithNoDone: {
285
304
  minWidth: '265px',
@@ -293,11 +312,15 @@ const style = {
293
312
  fullWidth: {
294
313
  width: '100%',
295
314
  },
315
+ hidden: {
316
+ visibility: 'hidden'
317
+ },
296
318
  autoWidth: {
297
319
  width: 'auto',
298
320
  },
299
321
  focused: {
300
- display: 'flex',
322
+ opacity: 1,
323
+ pointerEvents: 'auto'
301
324
  },
302
325
  iconRoot: {
303
326
  width: '28px',
@@ -2,6 +2,7 @@ import Html from 'slate-html-serializer';
2
2
  import React from 'react';
3
3
  import debug from 'debug';
4
4
  import { object as toStyleObject } from 'to-style';
5
+ import isEmpty from 'lodash/isEmpty';
5
6
 
6
7
  import { serialization as imgSerialization } from './plugins/image';
7
8
  import { serialization as mathSerialization } from './plugins/math';
@@ -10,29 +11,10 @@ import { serialization as listSerialization } from './plugins/list';
10
11
  import { serialization as tableSerialization } from './plugins/table';
11
12
  import { serialization as responseAreaSerialization } from './plugins/respArea';
12
13
  import { Mark, Value } from 'slate';
14
+ import { BLOCK_TAGS } from './block-tags';
13
15
 
14
16
  const log = debug('@pie-lib:editable-html:serialization');
15
17
 
16
- /**
17
- * Tags to blocks.
18
- *
19
- * @type {Object}
20
- */
21
-
22
- export const BLOCK_TAGS = {
23
- div: 'div',
24
- span: 'span',
25
- p: 'paragraph',
26
- blockquote: 'quote',
27
- pre: 'code',
28
- h1: 'heading-one',
29
- h2: 'heading-two',
30
- h3: 'heading-three',
31
- h4: 'heading-four',
32
- h5: 'heading-five',
33
- h6: 'heading-six',
34
- };
35
-
36
18
  /**
37
19
  * Tags to marks.
38
20
  *
@@ -46,6 +28,10 @@ const MARK_TAGS = {
46
28
  s: 'strikethrough',
47
29
  code: 'code',
48
30
  strong: 'bold',
31
+ blockquote: 'blockquote',
32
+ h3: 'h3',
33
+ sup: 'sup',
34
+ sub: 'sub',
49
35
  };
50
36
 
51
37
  export const parseStyleString = (s) => {
@@ -83,7 +69,7 @@ const attributesToMap = (el) => (acc, attribute) => {
83
69
  return acc;
84
70
  };
85
71
 
86
- const attributes = ['border', 'cellpadding', 'cellspacing', 'class', 'style'];
72
+ const attributes = ['border', 'cellpadding', 'cellspacing', 'class', 'style', 'align'];
87
73
 
88
74
  /**
89
75
  * Serializer rules.
@@ -134,23 +120,128 @@ const blocks = {
134
120
  },
135
121
  };
136
122
 
123
+ export const INLINE_TAGS = {
124
+ span: 'span',
125
+ };
126
+
127
+ const inlines = {
128
+ deserialize(el, next) {
129
+ log('[inlines:deserialize] inline: ', el);
130
+ const inlineTag = INLINE_TAGS[el.tagName.toLowerCase()];
131
+ if (!inlineTag) return;
132
+ log('[inlines:deserialize] inline: ', inlineTag);
133
+
134
+ if (el.childNodes.length === 1) {
135
+ const cn = el.childNodes[0];
136
+ if (cn && cn.tagName && cn.tagName.toLowerCase() === inlineTag) {
137
+ log('[we have a child node of the same]...');
138
+ return;
139
+ }
140
+ }
141
+
142
+ return {
143
+ object: 'inline',
144
+ type: inlineTag,
145
+ /**
146
+ * Here for rendering styles for all inline elements
147
+ */
148
+ data: { attributes: attributes.reduce(attributesToMap(el), {}) },
149
+ nodes: next(el.childNodes),
150
+ };
151
+ },
152
+ serialize: (object, children) => {
153
+ if (object.object !== 'inline') return;
154
+
155
+ const jsonData = object.data.toJSON();
156
+
157
+ log('[inlines:serialize] object: ', object, children);
158
+ let key;
159
+
160
+ for (key in INLINE_TAGS) {
161
+ if (INLINE_TAGS[key] === object.type) {
162
+ const Tag = key;
163
+
164
+ return <Tag {...jsonData.attributes}>{children}</Tag>;
165
+ }
166
+ }
167
+ },
168
+ };
169
+
170
+ export const extraCSSRulesOpts = {};
171
+
172
+ const STYLES_MAP = {
173
+ h3: {
174
+ fontSize: 'inherit',
175
+ fontWeight: 'inherit',
176
+ },
177
+ blockquote: {
178
+ background: '#f9f9f9',
179
+ borderLeft: '5px solid #ccc',
180
+ margin: '1.5em 10px',
181
+ padding: '.5em 10px',
182
+ },
183
+ };
184
+
185
+ const reactToHTMLAttributesMap = {
186
+ class: 'className',
187
+ };
188
+
137
189
  const marks = {
138
190
  deserialize(el, next) {
139
191
  const mark = MARK_TAGS[el.tagName.toLowerCase()];
140
- if (!mark) return;
192
+ const elClasses = el.getAttribute('class') || '';
193
+ const hasCSSRule = (extraCSSRulesOpts?.names || []).find((name) => elClasses?.includes(name));
194
+
195
+ if (!mark && !hasCSSRule) {
196
+ return;
197
+ }
198
+
141
199
  log('[deserialize] mark: ', mark);
200
+ const attrs = attributes.reduce(attributesToMap(el), {});
201
+ const data = isEmpty(attrs) ? undefined : { attributes: attrs };
202
+
142
203
  return {
143
204
  object: 'mark',
144
- type: mark,
205
+ type: hasCSSRule ? 'css' : mark,
206
+ /**
207
+ * Here for rendering styles for all elements
208
+ */
209
+ data,
145
210
  nodes: next(el.childNodes),
146
211
  };
147
212
  },
148
213
  serialize(object, children) {
214
+ const jsonData = object.data?.toJSON() || {};
215
+ const elClasses = jsonData.attributes?.class || '';
216
+ const hasCSSRule = (extraCSSRulesOpts?.names || []).find((name) => elClasses?.includes(name));
217
+
218
+ if (hasCSSRule) {
219
+ const htmlAttrs = Object.keys(jsonData.attributes).reduce((obj, key) => {
220
+ obj[reactToHTMLAttributesMap[key] || key] = jsonData.attributes[key];
221
+ return obj;
222
+ }, {});
223
+
224
+ return <span {...htmlAttrs}>{children}</span>;
225
+ }
226
+
149
227
  if (Mark.isMark(object)) {
150
228
  for (var key in MARK_TAGS) {
151
229
  if (MARK_TAGS[key] === object.type) {
152
230
  const Tag = key;
153
- return <Tag>{children}</Tag>;
231
+ const additionalStyles = STYLES_MAP[Tag];
232
+
233
+ if (additionalStyles) {
234
+ if (!jsonData.attributes) {
235
+ jsonData.attributes = {};
236
+ }
237
+
238
+ jsonData.attributes.style = {
239
+ ...jsonData.attributes.style,
240
+ ...additionalStyles,
241
+ };
242
+ }
243
+
244
+ return <Tag {...jsonData.attributes}>{children}</Tag>;
154
245
  }
155
246
  }
156
247
  }
@@ -224,6 +315,7 @@ const RULES = [
224
315
  tableSerialization,
225
316
  responseAreaSerialization,
226
317
  TEXT_RULE,
318
+ inlines,
227
319
  blocks,
228
320
  marks,
229
321
  ];
@@ -247,7 +339,11 @@ function defaultParseHtml(html) {
247
339
  var n = textNodes.nextNode();
248
340
 
249
341
  while (n) {
250
- if (allWhitespace(n) || n.nodeValue === '\u200B') {
342
+ const isWhiteSpace = allWhitespace(n);
343
+ const isNotNearMarkup =
344
+ !MARK_TAGS[n.nextSibling?.tagName?.toLowerCase()] && !MARK_TAGS[n.previousSibling?.tagName?.toLowerCase()];
345
+
346
+ if ((isWhiteSpace && isNotNearMarkup) || n.nodeValue === '\u200B') {
251
347
  n.parentNode.removeChild(n);
252
348
  }
253
349
  n = textNodes.nextNode();
@@ -388,46 +484,125 @@ const reduceRedundantNewLineCharacters = (markup) => {
388
484
  return markup;
389
485
  };
390
486
 
391
- const wrapHtmlProperly = (markup) => {
392
- const el = document.createElement('div');
487
+ /**
488
+ * Makes sure that the html provided respects the schema
489
+ * rules for the slate editor.
490
+ * @param markup
491
+ * @returns {string}
492
+ */
493
+ const fixHtmlCode = (markup) => {
494
+ const wrapperEl = document.createElement('div');
393
495
 
394
- el.innerHTML = markup;
496
+ wrapperEl.innerHTML = markup;
395
497
 
396
498
  /**
397
499
  * DIV elements that are at the same level as paragraphs
398
500
  * are replaced with P elements for normalizing purposes
399
- * @param el
501
+ * @param child
502
+ */
503
+ const fixParagraphs = (child) => {
504
+ const p = document.createElement('p');
505
+
506
+ p.innerHTML = child.innerHTML;
507
+
508
+ Array.from(child.attributes).forEach((attr) => {
509
+ p.setAttribute(attr.name, attr.value);
510
+ });
511
+ child.replaceWith(p);
512
+ };
513
+
514
+ /**
515
+ * @summary Makes sure that tables are placed in the root document.
516
+ * @description This function removes the tables from the nodes that are
517
+ * placed inside the root element and places them exactly near
518
+ * the parent element.
519
+ * @param tableArray
400
520
  */
521
+ const fixTables = (tableArray) => {
522
+ tableArray.forEach((el) => {
523
+ const { index, child, parent } = el;
524
+ const nodesBefore = [];
525
+ const nodesAfter = [];
526
+ const allNodes = Array.from(parent.childNodes);
527
+ let i;
528
+
529
+ for (i = 0; i < allNodes.length; i++) {
530
+ const node = allNodes[i];
531
+
532
+ if (i < index) {
533
+ nodesBefore.push(node);
534
+ } else if (i > index) {
535
+ nodesAfter.push(node);
536
+ }
537
+ }
538
+
539
+ // creating the element that is going to be placed instead of the parent
540
+ const beforeEl = document.createElement(parent.nodeName);
541
+
542
+ beforeEl.append(...nodesBefore);
543
+
544
+ // replacing parent with the beforeElement
545
+ parent.replaceWith(beforeEl);
546
+
547
+ // adding the table right after the before element
548
+ beforeEl.after(child);
549
+
550
+ // creating the element that is going to be placed after the table
551
+ const afterEl = document.createElement(parent.nodeName);
552
+
553
+ afterEl.append(...nodesAfter);
554
+
555
+ // adding the after element near the table
556
+ child.after(afterEl);
557
+ });
558
+ };
559
+
560
+ const emptyBlockCheck = (node) =>
561
+ (node.nodeName === 'DIV' || node.nodeName === 'P' || node.nodeName === 'LI') && node.childNodes.length === 0;
562
+
401
563
  const parseNode = (el) => {
402
- const childArray = Array.from(el.children);
564
+ const childArray = Array.from(el.childNodes);
403
565
  const hasParagraphs = childArray.find((child) => child.nodeName === 'P');
566
+ const tables = [];
404
567
 
405
- childArray.forEach((child) => {
568
+ childArray.forEach((child, index) => {
406
569
  // removing empty blocks
407
- if ((child.nodeName === 'DIV' || child.nodeName === 'P') && child.childNodes.length === 0) {
570
+ if (emptyBlockCheck(child)) {
408
571
  child.remove();
409
572
  return;
410
573
  }
411
574
 
412
575
  if (hasParagraphs && child.nodeName === 'DIV') {
413
- const p = document.createElement('p');
576
+ fixParagraphs(child);
577
+ }
414
578
 
415
- p.innerHTML = child.innerHTML;
416
- child.replaceWith(p);
579
+ if (wrapperEl !== el && child.nodeName === 'TABLE') {
580
+ // we don't need to fix tables in the root element
581
+ tables.push({
582
+ index,
583
+ child,
584
+ parent: el,
585
+ });
417
586
  }
418
587
 
419
588
  parseNode(child);
420
589
  });
590
+
591
+ if (tables.length) {
592
+ fixTables(tables);
593
+ }
421
594
  };
422
595
 
423
- parseNode(el);
596
+ parseNode(wrapperEl);
424
597
 
425
- return el.innerHTML;
598
+ return wrapperEl.innerHTML;
426
599
  };
427
600
 
601
+ export const handleHtml = (html) => fixHtmlCode(reduceRedundantNewLineCharacters(reduceMultipleBrs(html)));
602
+
428
603
  export const htmlToValue = (html) => {
429
604
  try {
430
- return serializer.deserialize(wrapHtmlProperly(reduceRedundantNewLineCharacters(reduceMultipleBrs(html))));
605
+ return serializer.deserialize(handleHtml(html));
431
606
  } catch (e) {
432
607
  // eslint-disable-next-line no-console
433
608
  console.log("Couldn't parse html: ", e);
package/README.md DELETED
@@ -1,45 +0,0 @@
1
- # editable-html
2
-
3
- `editable-html` is an inline HTML editor, based on [`slate`](https://github.com/ianstormtaylor/slate), for use within PIE configuration panels.
4
-
5
- > It's pretty rough at the moment (UI + logic), but can't spend too much time on it right now.
6
-
7
- ## Demo
8
-
9
- ```bash
10
- npm install
11
- cd demo
12
- ../node_modules/.bin/webpack-dev-server --hot --inline
13
- # go to http://localhost:8080
14
- ```
15
-
16
- ## Usage
17
-
18
- Install:
19
-
20
- ```bash
21
- npm install --save @pie-lib/editable-html
22
- ```
23
-
24
- Import:
25
-
26
- ```js
27
- import EditableHTML from '@pie-lib/editable-html';
28
- ```
29
-
30
- Declare:
31
-
32
- ```jsx
33
- <EditableHTML onChange={this.htmlChanged.bind(this)} markup={markup} />
34
- ```
35
-
36
- ### In production
37
-
38
- If you are running in production and have an external `React` and `ReactDOM`, you will also need to include `ReactDOMServer`.
39
-
40
- ```html
41
- <script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
42
- <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
43
- <!-- this must be added -->
44
- <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom-server.browser.production.min.js"></script>
45
- ```
package/deploy.sh DELETED
@@ -1,16 +0,0 @@
1
- #!/usr/bin/env bash
2
-
3
- rm -fr demo/.git
4
-
5
- echo "did you run webpack?"
6
-
7
- cd demo
8
-
9
-
10
-
11
- git init
12
- git add .
13
- git commit . -m "commit"
14
- git remote add heroku git@heroku.com:editable-html-demo.git
15
- git push --force heroku master
16
-