@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.
- package/CHANGELOG.md +8 -4
- package/NEXT.CHANGELOG.json +1 -0
- package/lib/__tests__/editor.test.js +470 -0
- package/lib/__tests__/serialization.test.js +246 -0
- package/lib/__tests__/utils.js +106 -0
- package/lib/block-tags.js +25 -0
- package/lib/constants.js +16 -0
- package/lib/editor.js +349 -88
- package/lib/index.js +26 -10
- package/lib/parse-html.js +1 -1
- package/lib/plugins/characters/custom-popper.js +1 -1
- package/lib/plugins/characters/index.js +9 -4
- package/lib/plugins/characters/utils.js +13 -13
- package/lib/plugins/css/icons/index.js +37 -0
- package/lib/plugins/css/index.js +397 -0
- package/lib/plugins/customPlugin/index.js +114 -0
- package/lib/plugins/html/icons/index.js +1 -1
- package/lib/plugins/html/index.js +12 -8
- package/lib/plugins/image/__tests__/component.test.js +51 -0
- package/lib/plugins/image/__tests__/image-toolbar-logic.test.js +56 -0
- package/lib/plugins/image/__tests__/image-toolbar.test.js +26 -0
- package/lib/plugins/image/__tests__/index.test.js +98 -0
- package/lib/plugins/image/__tests__/insert-image-handler.test.js +125 -0
- package/lib/plugins/image/__tests__/mock-change.js +25 -0
- package/lib/plugins/image/alt-dialog.js +1 -1
- package/lib/plugins/image/component.js +1 -1
- package/lib/plugins/image/image-toolbar.js +1 -1
- package/lib/plugins/image/index.js +3 -2
- package/lib/plugins/image/insert-image-handler.js +14 -5
- package/lib/plugins/index.js +271 -12
- package/lib/plugins/list/__tests__/index.test.js +79 -0
- package/lib/plugins/list/index.js +131 -1
- package/lib/plugins/math/__tests__/index.test.js +300 -0
- package/lib/plugins/math/index.js +92 -57
- package/lib/plugins/media/__tests__/index.test.js +71 -0
- package/lib/plugins/media/index.js +6 -3
- package/lib/plugins/media/media-dialog.js +99 -58
- package/lib/plugins/media/media-toolbar.js +1 -1
- package/lib/plugins/media/media-wrapper.js +1 -1
- package/lib/plugins/rendering/index.js +46 -0
- package/lib/plugins/respArea/drag-in-the-blank/choice.js +6 -3
- package/lib/plugins/respArea/drag-in-the-blank/index.js +1 -1
- package/lib/plugins/respArea/explicit-constructed-response/index.js +12 -10
- package/lib/plugins/respArea/icons/index.js +1 -1
- package/lib/plugins/respArea/index.js +70 -22
- package/lib/plugins/respArea/inline-dropdown/index.js +11 -6
- package/lib/plugins/respArea/math-templated/index.js +130 -0
- package/lib/plugins/respArea/utils.js +17 -2
- package/lib/plugins/table/CustomTablePlugin.js +133 -0
- package/lib/plugins/table/__tests__/index.test.js +442 -0
- package/lib/plugins/table/__tests__/table-toolbar.test.js +54 -0
- package/lib/plugins/table/icons/index.js +1 -1
- package/lib/plugins/table/index.js +44 -60
- package/lib/plugins/table/table-toolbar.js +34 -5
- package/lib/plugins/textAlign/icons/index.js +226 -0
- package/lib/plugins/textAlign/index.js +34 -0
- package/lib/plugins/toolbar/__tests__/default-toolbar.test.js +128 -0
- package/lib/plugins/toolbar/__tests__/editor-and-toolbar.test.js +51 -0
- package/lib/plugins/toolbar/__tests__/toolbar-buttons.test.js +54 -0
- package/lib/plugins/toolbar/__tests__/toolbar.test.js +120 -0
- package/lib/plugins/toolbar/default-toolbar.js +83 -28
- package/lib/plugins/toolbar/done-button.js +6 -3
- package/lib/plugins/toolbar/editor-and-toolbar.js +19 -20
- package/lib/plugins/toolbar/index.js +1 -1
- package/lib/plugins/toolbar/toolbar-buttons.js +45 -12
- package/lib/plugins/toolbar/toolbar.js +36 -12
- package/lib/plugins/utils.js +1 -1
- package/lib/serialization.js +234 -45
- package/lib/theme.js +1 -1
- package/package.json +6 -6
- package/src/__tests__/editor.test.jsx +363 -0
- package/src/__tests__/serialization.test.js +291 -0
- package/src/__tests__/utils.js +36 -0
- package/src/block-tags.js +17 -0
- package/src/constants.js +7 -0
- package/src/editor.jsx +303 -49
- package/src/index.jsx +19 -10
- package/src/plugins/characters/index.jsx +11 -3
- package/src/plugins/characters/utils.js +12 -12
- package/src/plugins/css/icons/index.jsx +17 -0
- package/src/plugins/css/index.jsx +346 -0
- package/src/plugins/customPlugin/index.jsx +85 -0
- package/src/plugins/html/index.jsx +9 -6
- package/src/plugins/image/__tests__/__snapshots__/component.test.jsx.snap +51 -0
- package/src/plugins/image/__tests__/__snapshots__/image-toolbar-logic.test.jsx.snap +27 -0
- package/src/plugins/image/__tests__/__snapshots__/image-toolbar.test.jsx.snap +44 -0
- package/src/plugins/image/__tests__/component.test.jsx +41 -0
- package/src/plugins/image/__tests__/image-toolbar-logic.test.jsx +42 -0
- package/src/plugins/image/__tests__/image-toolbar.test.jsx +11 -0
- package/src/plugins/image/__tests__/index.test.js +95 -0
- package/src/plugins/image/__tests__/insert-image-handler.test.js +113 -0
- package/src/plugins/image/__tests__/mock-change.js +15 -0
- package/src/plugins/image/index.jsx +2 -1
- package/src/plugins/image/insert-image-handler.js +13 -6
- package/src/plugins/index.jsx +248 -5
- package/src/plugins/list/__tests__/index.test.js +54 -0
- package/src/plugins/list/index.jsx +130 -0
- package/src/plugins/math/__tests__/__snapshots__/index.test.jsx.snap +48 -0
- package/src/plugins/math/__tests__/index.test.jsx +245 -0
- package/src/plugins/math/index.jsx +87 -56
- package/src/plugins/media/__tests__/index.test.js +75 -0
- package/src/plugins/media/index.jsx +3 -2
- package/src/plugins/media/media-dialog.js +106 -57
- package/src/plugins/rendering/index.js +31 -0
- package/src/plugins/respArea/drag-in-the-blank/choice.jsx +4 -1
- package/src/plugins/respArea/explicit-constructed-response/index.jsx +10 -8
- package/src/plugins/respArea/index.jsx +53 -7
- package/src/plugins/respArea/inline-dropdown/index.jsx +13 -6
- package/src/plugins/respArea/math-templated/index.jsx +104 -0
- package/src/plugins/respArea/utils.jsx +11 -0
- package/src/plugins/table/CustomTablePlugin.js +113 -0
- package/src/plugins/table/__tests__/__snapshots__/table-toolbar.test.jsx.snap +44 -0
- package/src/plugins/table/__tests__/index.test.jsx +401 -0
- package/src/plugins/table/__tests__/table-toolbar.test.jsx +42 -0
- package/src/plugins/table/index.jsx +46 -59
- package/src/plugins/table/table-toolbar.jsx +39 -2
- package/src/plugins/textAlign/icons/index.jsx +139 -0
- package/src/plugins/textAlign/index.jsx +23 -0
- package/src/plugins/toolbar/__tests__/__snapshots__/default-toolbar.test.jsx.snap +923 -0
- package/src/plugins/toolbar/__tests__/__snapshots__/editor-and-toolbar.test.jsx.snap +20 -0
- package/src/plugins/toolbar/__tests__/__snapshots__/toolbar-buttons.test.jsx.snap +36 -0
- package/src/plugins/toolbar/__tests__/__snapshots__/toolbar.test.jsx.snap +46 -0
- package/src/plugins/toolbar/__tests__/default-toolbar.test.jsx +94 -0
- package/src/plugins/toolbar/__tests__/editor-and-toolbar.test.jsx +37 -0
- package/src/plugins/toolbar/__tests__/toolbar-buttons.test.jsx +51 -0
- package/src/plugins/toolbar/__tests__/toolbar.test.jsx +106 -0
- package/src/plugins/toolbar/default-toolbar.jsx +80 -20
- package/src/plugins/toolbar/done-button.jsx +3 -1
- package/src/plugins/toolbar/editor-and-toolbar.jsx +18 -13
- package/src/plugins/toolbar/toolbar-buttons.jsx +52 -11
- package/src/plugins/toolbar/toolbar.jsx +31 -8
- package/src/serialization.jsx +213 -38
- package/README.md +0 -45
- package/deploy.sh +0 -16
- package/lib/editor.js.map +0 -1
- package/lib/index.js.map +0 -1
- package/lib/parse-html.js.map +0 -1
- package/lib/plugins/characters/custom-popper.js.map +0 -1
- package/lib/plugins/characters/index.js.map +0 -1
- package/lib/plugins/characters/utils.js.map +0 -1
- package/lib/plugins/html/icons/index.js.map +0 -1
- package/lib/plugins/html/index.js.map +0 -1
- package/lib/plugins/image/alt-dialog.js.map +0 -1
- package/lib/plugins/image/component.js.map +0 -1
- package/lib/plugins/image/image-toolbar.js.map +0 -1
- package/lib/plugins/image/index.js.map +0 -1
- package/lib/plugins/image/insert-image-handler.js.map +0 -1
- package/lib/plugins/index.js.map +0 -1
- package/lib/plugins/list/index.js.map +0 -1
- package/lib/plugins/math/index.js.map +0 -1
- package/lib/plugins/media/index.js.map +0 -1
- package/lib/plugins/media/media-dialog.js.map +0 -1
- package/lib/plugins/media/media-toolbar.js.map +0 -1
- package/lib/plugins/media/media-wrapper.js.map +0 -1
- package/lib/plugins/respArea/drag-in-the-blank/choice.js.map +0 -1
- package/lib/plugins/respArea/drag-in-the-blank/index.js.map +0 -1
- package/lib/plugins/respArea/explicit-constructed-response/index.js.map +0 -1
- package/lib/plugins/respArea/icons/index.js.map +0 -1
- package/lib/plugins/respArea/index.js.map +0 -1
- package/lib/plugins/respArea/inline-dropdown/index.js.map +0 -1
- package/lib/plugins/respArea/utils.js.map +0 -1
- package/lib/plugins/table/icons/index.js.map +0 -1
- package/lib/plugins/table/index.js.map +0 -1
- package/lib/plugins/table/table-toolbar.js.map +0 -1
- package/lib/plugins/toolbar/default-toolbar.js.map +0 -1
- package/lib/plugins/toolbar/done-button.js.map +0 -1
- package/lib/plugins/toolbar/editor-and-toolbar.js.map +0 -1
- package/lib/plugins/toolbar/index.js.map +0 -1
- package/lib/plugins/toolbar/toolbar-buttons.js.map +0 -1
- package/lib/plugins/toolbar/toolbar.js.map +0 -1
- package/lib/plugins/utils.js.map +0 -1
- package/lib/serialization.js.map +0 -1
- package/lib/theme.js.map +0 -1
- package/playground/image/data.js +0 -59
- package/playground/image/index.html +0 -22
- package/playground/image/index.jsx +0 -81
- package/playground/index.html +0 -25
- package/playground/mathquill/index.html +0 -22
- package/playground/mathquill/index.jsx +0 -155
- package/playground/package.json +0 -15
- package/playground/prod-test/index.html +0 -22
- package/playground/prod-test/index.jsx +0 -28
- package/playground/schema-override/data.js +0 -29
- package/playground/schema-override/image-plugin.jsx +0 -41
- package/playground/schema-override/index.html +0 -21
- package/playground/schema-override/index.jsx +0 -97
- package/playground/serialization/data.js +0 -29
- package/playground/serialization/image-plugin.jsx +0 -41
- package/playground/serialization/index.html +0 -22
- package/playground/serialization/index.jsx +0 -12
- package/playground/static.json +0 -3
- package/playground/table-examples.html +0 -70
- package/playground/webpack.config.js +0 -42
- package/static.json +0 -1
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import debug from 'debug';
|
|
3
|
-
import
|
|
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
|
-
|
|
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
|
-
<
|
|
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
|
-
</
|
|
86
|
+
</button>
|
|
62
87
|
);
|
|
63
88
|
}
|
|
64
89
|
}
|
|
65
90
|
|
|
66
|
-
export const Button =
|
|
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
|
-
<
|
|
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
|
-
</
|
|
133
|
+
</button>
|
|
93
134
|
);
|
|
94
135
|
}
|
|
95
136
|
}
|
|
96
137
|
|
|
97
|
-
export const MarkButton =
|
|
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: '
|
|
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
|
-
|
|
322
|
+
opacity: 1,
|
|
323
|
+
pointerEvents: 'auto'
|
|
301
324
|
},
|
|
302
325
|
iconRoot: {
|
|
303
326
|
width: '28px',
|
package/src/serialization.jsx
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
392
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
|
570
|
+
if (emptyBlockCheck(child)) {
|
|
408
571
|
child.remove();
|
|
409
572
|
return;
|
|
410
573
|
}
|
|
411
574
|
|
|
412
575
|
if (hasParagraphs && child.nodeName === 'DIV') {
|
|
413
|
-
|
|
576
|
+
fixParagraphs(child);
|
|
577
|
+
}
|
|
414
578
|
|
|
415
|
-
|
|
416
|
-
|
|
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(
|
|
596
|
+
parseNode(wrapperEl);
|
|
424
597
|
|
|
425
|
-
return
|
|
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(
|
|
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