@pie-lib/editable-html 7.19.2 → 7.21.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.
@@ -1,4 +1,6 @@
1
1
  import React from 'react';
2
+ import { Data } from 'slate';
3
+ import Immutable from 'immutable';
2
4
  import PropTypes from 'prop-types';
3
5
  import EditList from 'slate-edit-list';
4
6
  import debug from 'debug';
@@ -44,15 +46,12 @@ export const serialization = {
44
46
  }
45
47
  };
46
48
 
47
- export default options => {
48
- const { type, icon } = options;
49
-
49
+ const createEditList = () => {
50
50
  const core = EditList({
51
51
  typeDefault: 'span'
52
52
  });
53
53
 
54
54
  // fix outdated schema
55
-
56
55
  if (core.schema && core.schema.blocks) {
57
56
  Object.keys(core.schema.blocks).forEach(key => {
58
57
  const block = core.schema.blocks[key];
@@ -65,6 +64,69 @@ export default options => {
65
64
  });
66
65
  }
67
66
 
67
+ /**
68
+ * This override of the core.changes.wrapInList is needed because the version
69
+ * of immutable that we have does not support getting the element at a specific
70
+ * index with a square bracket (list[0]). We have to use the list.get function instead
71
+ */
72
+
73
+ /**
74
+ * Returns the highest list of blocks that cover the current selection
75
+ */
76
+ const getHighestSelectedBlocks = value => {
77
+ const range = value.selection;
78
+ const document = value.document;
79
+
80
+ const startBlock = document.getClosestBlock(range.startKey);
81
+ const endBlock = document.getClosestBlock(range.endKey);
82
+
83
+ if (startBlock === endBlock) {
84
+ return Immutable.List([startBlock]);
85
+ }
86
+
87
+ const ancestor = document.getCommonAncestor(startBlock.key, endBlock.key);
88
+ const startPath = ancestor.getPath(startBlock.key);
89
+ const endPath = ancestor.getPath(endBlock.key);
90
+
91
+ return ancestor.nodes.slice(startPath.get(0), endPath.get(0) + 1);
92
+ };
93
+
94
+ /**
95
+ * Wrap the blocks in the current selection in a new list. Selected
96
+ * lists are merged together.
97
+ */
98
+ core.changes.wrapInList = function(change, type, data) {
99
+ const selectedBlocks = getHighestSelectedBlocks(change.value);
100
+
101
+ // Wrap in container
102
+ change.wrapBlock({ type: type, data: Data.create(data) }, { normalize: false });
103
+
104
+ // Wrap in list items
105
+ selectedBlocks.forEach(function(node) {
106
+ if (core.utils.isList(node)) {
107
+ // Merge its items with the created list
108
+ node.nodes.forEach(function(_ref) {
109
+ const key = _ref.key;
110
+ return change.unwrapNodeByKey(key, { normalize: false });
111
+ });
112
+ } else if (node.type !== 'list_item') {
113
+ change.wrapBlockByKey(node.key, 'list_item', {
114
+ normalize: false
115
+ });
116
+ }
117
+ });
118
+
119
+ return change.normalize();
120
+ };
121
+
122
+ return core;
123
+ };
124
+
125
+ export default options => {
126
+ const { type, icon } = options;
127
+
128
+ const core = createEditList();
129
+
68
130
  // eslint-disable-next-line react/display-name
69
131
  core.renderNode = props => {
70
132
  const { node, attributes, children } = props;
@@ -88,7 +150,7 @@ export default options => {
88
150
  return false;
89
151
  }
90
152
  const current = core.utils.getCurrentList(value);
91
- return current.type === type;
153
+ return current ? current.type === type : false;
92
154
  },
93
155
  onClick: (value, onChange) => {
94
156
  log('[onClick]', value);
@@ -20,14 +20,17 @@ const removeDialogs = () => {
20
20
  export const insertDialog = props => {
21
21
  const newEl = document.createElement('div');
22
22
  const { type, callback, ...rest } = props;
23
+ const initialBodyOverflow = document.body.style.overflow;
23
24
 
24
25
  removeDialogs();
25
26
 
26
27
  newEl.className = 'insert-media-dialog';
28
+ document.body.style.overflow = 'hidden';
27
29
 
28
30
  const handleClose = (val, data) => {
29
31
  callback(val, data);
30
32
  newEl.remove();
33
+ document.body.style.overflow = initialBodyOverflow;
31
34
  };
32
35
 
33
36
  const el = (
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
 
4
4
  const ExplicitConstructedResponse = props => {
5
- const { attributes, value } = props;
5
+ const { attributes, value, error } = props;
6
6
 
7
7
  return (
8
8
  <span
@@ -23,7 +23,7 @@ const ExplicitConstructedResponse = props => {
23
23
  minHeight: '36px',
24
24
  height: '36px',
25
25
  background: '#FFF',
26
- border: '1px solid #C0C3CF',
26
+ border: `1px solid ${error ? 'red' : '#C0C3CF'}`,
27
27
  boxSizing: 'border-box',
28
28
  borderRadius: '3px',
29
29
  overflow: 'hidden',
@@ -19,6 +19,8 @@ const elTypesMap = {
19
19
  const elTypesArray = Object.values(elTypesMap);
20
20
 
21
21
  export default function ResponseAreaPlugin(opts) {
22
+ const isOfCurrentType = d => d.type === opts.type || d.type === elTypesMap[opts.type];
23
+
22
24
  const toolbar = {
23
25
  icon: <ToolbarIcon />,
24
26
  buttonStyles: {
@@ -27,6 +29,12 @@ export default function ResponseAreaPlugin(opts) {
27
29
  onClick: (value, onChange) => {
28
30
  log('[toolbar] onClick');
29
31
  const change = value.change();
32
+ const currentRespAreaList = change.value.document.filterDescendants(isOfCurrentType);
33
+
34
+ if (currentRespAreaList.size >= opts.maxResponseAreas) {
35
+ return;
36
+ }
37
+
30
38
  const type = opts.type.replace(/-/g, '_');
31
39
  const prevIndex = lastIndexMap[type];
32
40
  const newIndex = prevIndex === 0 ? prevIndex : prevIndex + 1;
@@ -90,7 +98,13 @@ export default function ResponseAreaPlugin(opts) {
90
98
  if (n.type === 'explicit_constructed_response') {
91
99
  const data = n.data.toJSON();
92
100
 
93
- return <ExplicitConstructedResponse attributes={attributes} value={data.value} />;
101
+ return (
102
+ <ExplicitConstructedResponse
103
+ attributes={attributes}
104
+ value={data.value}
105
+ error={opts.error && opts.error[data.value]}
106
+ />
107
+ );
94
108
  }
95
109
 
96
110
  if (n.type === 'drag_in_the_blank') {
@@ -128,10 +142,15 @@ export default function ResponseAreaPlugin(opts) {
128
142
  return;
129
143
  }
130
144
 
131
- const isOfCurrentType = d => d.type === opts.type || d.type === elTypesMap[opts.type];
132
145
  const currentRespAreaList = change.value.document.filterDescendants(isOfCurrentType);
133
146
  const oldRespAreaList = editor.value.document.filterDescendants(isOfCurrentType);
134
147
 
148
+ if (currentRespAreaList.size >= opts.maxResponseAreas) {
149
+ toolbar.disabled = true;
150
+ } else {
151
+ toolbar.disabled = false;
152
+ }
153
+
135
154
  const arrayToFilter =
136
155
  oldRespAreaList.size > currentRespAreaList.size ? oldRespAreaList : currentRespAreaList;
137
156
  const arrayToUseForFilter =
@@ -14,25 +14,32 @@ const log = debug('@pie-lib:editable-html:plugins:toolbar');
14
14
  export const ToolbarButton = props => {
15
15
  const onToggle = () => {
16
16
  const c = props.onToggle(props.value.change(), props);
17
+
17
18
  props.onChange(c);
18
19
  };
19
20
 
20
21
  if (props.isMark) {
21
22
  const isActive = hasMark(props.value, props.type);
23
+
22
24
  log('[ToolbarButton] mark:isActive: ', isActive);
25
+
23
26
  return (
24
27
  <MarkButton active={isActive} label={props.type} onToggle={onToggle} mark={props.type}>
25
28
  {props.icon}
26
29
  </MarkButton>
27
30
  );
28
31
  } else {
32
+ const { disabled } = props;
29
33
  const isActive = props.isActive
30
34
  ? props.isActive(props.value, props.type)
31
35
  : hasBlock(props.value, props.type);
36
+
32
37
  log('[ToolbarButton] block:isActive: ', isActive);
38
+
33
39
  return (
34
40
  <Button
35
41
  active={isActive}
42
+ disabled={disabled}
36
43
  onClick={() => props.onClick(props.value, props.onChange)}
37
44
  extraStyles={props.buttonStyles}
38
45
  >
@@ -44,6 +51,7 @@ export const ToolbarButton = props => {
44
51
 
45
52
  const isActiveToolbarPlugin = props => plugin => {
46
53
  const isDisabled = (props[plugin.name] || {}).disabled;
54
+
47
55
  return plugin && plugin.toolbar && !isDisabled;
48
56
  };
49
57
 
@@ -28,7 +28,8 @@ export class EditorAndToolbar extends React.Component {
28
28
  pluginProps: PropTypes.object,
29
29
  toolbarOpts: PropTypes.shape({
30
30
  position: PropTypes.oneOf(['bottom', 'top']),
31
- alwaysVisible: PropTypes.bool
31
+ alwaysVisible: PropTypes.bool,
32
+ error: PropTypes.string
32
33
  })
33
34
  };
34
35
 
@@ -84,7 +85,7 @@ export class EditorAndToolbar extends React.Component {
84
85
  );
85
86
 
86
87
  return (
87
- <div className={classes.root}>
88
+ <div className={classNames(classes.root, toolbarOpts && toolbarOpts.error && classes.error)}>
88
89
  <div className={holderNames}>
89
90
  <div className={classes.children}>{clonedChildren}</div>
90
91
  </div>
@@ -215,6 +216,9 @@ const style = {
215
216
  backgroundColor: primary
216
217
  }
217
218
  }
219
+ },
220
+ error: {
221
+ border: '2px solid red'
218
222
  }
219
223
  };
220
224
 
@@ -15,6 +15,13 @@ const styles = () => ({
15
15
  },
16
16
  active: {
17
17
  color: 'black'
18
+ },
19
+ disabled: {
20
+ opacity: 0.7,
21
+ cursor: 'not-allowed',
22
+ '& :hover': {
23
+ color: 'grey'
24
+ }
18
25
  }
19
26
  });
20
27
 
@@ -26,6 +33,7 @@ export class RawButton extends React.Component {
26
33
  classes: PropTypes.object.isRequired,
27
34
  children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]).isRequired,
28
35
  active: PropTypes.bool,
36
+ disabled: PropTypes.bool,
29
37
  extraStyles: PropTypes.object
30
38
  };
31
39
 
@@ -41,8 +49,11 @@ export class RawButton extends React.Component {
41
49
  };
42
50
 
43
51
  render() {
44
- const { active, classes, children, extraStyles } = this.props;
45
- const names = classNames(classes.button, active && classes.active);
52
+ const { active, classes, children, disabled, extraStyles } = this.props;
53
+ const names = classNames(classes.button, {
54
+ [classes.active]: active,
55
+ [classes.disabled]: disabled
56
+ });
46
57
 
47
58
  return (
48
59
  <div style={extraStyles} className={names} onMouseDown={this.onClick}>