@pie-lib/editable-html-tip-tap 1.0.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 (167) hide show
  1. package/CHANGELOG.json +32 -0
  2. package/CHANGELOG.md +2280 -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 +1356 -0
  9. package/lib/extensions/MediaView.js +112 -0
  10. package/lib/extensions/characters.js +65 -0
  11. package/lib/extensions/component.js +325 -0
  12. package/lib/extensions/css.js +252 -0
  13. package/lib/extensions/custom-toolbar-wrapper.js +124 -0
  14. package/lib/extensions/image.js +106 -0
  15. package/lib/extensions/math.js +330 -0
  16. package/lib/extensions/media.js +276 -0
  17. package/lib/extensions/responseArea.js +278 -0
  18. package/lib/index.js +1213 -0
  19. package/lib/old-index.js +269 -0
  20. package/lib/parse-html.js +16 -0
  21. package/lib/plugins/characters/custom-popper.js +73 -0
  22. package/lib/plugins/characters/index.js +305 -0
  23. package/lib/plugins/characters/utils.js +381 -0
  24. package/lib/plugins/css/icons/index.js +37 -0
  25. package/lib/plugins/css/index.js +390 -0
  26. package/lib/plugins/customPlugin/index.js +114 -0
  27. package/lib/plugins/html/icons/index.js +38 -0
  28. package/lib/plugins/html/index.js +81 -0
  29. package/lib/plugins/image/__tests__/component.test.js +51 -0
  30. package/lib/plugins/image/__tests__/image-toolbar-logic.test.js +56 -0
  31. package/lib/plugins/image/__tests__/image-toolbar.test.js +26 -0
  32. package/lib/plugins/image/__tests__/index.test.js +98 -0
  33. package/lib/plugins/image/__tests__/insert-image-handler.test.js +125 -0
  34. package/lib/plugins/image/__tests__/mock-change.js +25 -0
  35. package/lib/plugins/image/alt-dialog.js +129 -0
  36. package/lib/plugins/image/component.js +419 -0
  37. package/lib/plugins/image/image-toolbar.js +177 -0
  38. package/lib/plugins/image/index.js +263 -0
  39. package/lib/plugins/image/insert-image-handler.js +117 -0
  40. package/lib/plugins/index.js +413 -0
  41. package/lib/plugins/list/__tests__/index.test.js +79 -0
  42. package/lib/plugins/list/index.js +334 -0
  43. package/lib/plugins/math/__tests__/index.test.js +300 -0
  44. package/lib/plugins/math/index.js +454 -0
  45. package/lib/plugins/media/__tests__/index.test.js +71 -0
  46. package/lib/plugins/media/index.js +387 -0
  47. package/lib/plugins/media/media-dialog.js +709 -0
  48. package/lib/plugins/media/media-toolbar.js +101 -0
  49. package/lib/plugins/media/media-wrapper.js +93 -0
  50. package/lib/plugins/rendering/index.js +46 -0
  51. package/lib/plugins/respArea/drag-in-the-blank/choice.js +289 -0
  52. package/lib/plugins/respArea/drag-in-the-blank/index.js +94 -0
  53. package/lib/plugins/respArea/explicit-constructed-response/index.js +120 -0
  54. package/lib/plugins/respArea/icons/index.js +95 -0
  55. package/lib/plugins/respArea/index.js +341 -0
  56. package/lib/plugins/respArea/inline-dropdown/index.js +126 -0
  57. package/lib/plugins/respArea/math-templated/index.js +130 -0
  58. package/lib/plugins/respArea/utils.js +125 -0
  59. package/lib/plugins/table/CustomTablePlugin.js +133 -0
  60. package/lib/plugins/table/__tests__/index.test.js +442 -0
  61. package/lib/plugins/table/__tests__/table-toolbar.test.js +54 -0
  62. package/lib/plugins/table/icons/index.js +69 -0
  63. package/lib/plugins/table/index.js +483 -0
  64. package/lib/plugins/table/table-toolbar.js +187 -0
  65. package/lib/plugins/textAlign/icons/index.js +194 -0
  66. package/lib/plugins/textAlign/index.js +34 -0
  67. package/lib/plugins/toolbar/__tests__/default-toolbar.test.js +128 -0
  68. package/lib/plugins/toolbar/__tests__/editor-and-toolbar.test.js +51 -0
  69. package/lib/plugins/toolbar/__tests__/toolbar-buttons.test.js +54 -0
  70. package/lib/plugins/toolbar/__tests__/toolbar.test.js +120 -0
  71. package/lib/plugins/toolbar/default-toolbar.js +229 -0
  72. package/lib/plugins/toolbar/done-button.js +53 -0
  73. package/lib/plugins/toolbar/editor-and-toolbar.js +286 -0
  74. package/lib/plugins/toolbar/index.js +34 -0
  75. package/lib/plugins/toolbar/toolbar-buttons.js +194 -0
  76. package/lib/plugins/toolbar/toolbar.js +376 -0
  77. package/lib/plugins/utils.js +62 -0
  78. package/lib/serialization.js +677 -0
  79. package/lib/shared/alert-dialog.js +75 -0
  80. package/lib/theme.js +9 -0
  81. package/package.json +69 -0
  82. package/src/__tests__/editor.test.jsx +363 -0
  83. package/src/__tests__/serialization.test.js +291 -0
  84. package/src/__tests__/utils.js +36 -0
  85. package/src/block-tags.js +17 -0
  86. package/src/constants.js +7 -0
  87. package/src/editor.jsx +1197 -0
  88. package/src/extensions/characters.js +46 -0
  89. package/src/extensions/component.jsx +294 -0
  90. package/src/extensions/css.js +217 -0
  91. package/src/extensions/custom-toolbar-wrapper.jsx +100 -0
  92. package/src/extensions/image.js +55 -0
  93. package/src/extensions/math.js +259 -0
  94. package/src/extensions/media.js +182 -0
  95. package/src/extensions/responseArea.js +205 -0
  96. package/src/index.jsx +1462 -0
  97. package/src/old-index.jsx +162 -0
  98. package/src/parse-html.js +8 -0
  99. package/src/plugins/README.md +27 -0
  100. package/src/plugins/characters/custom-popper.js +48 -0
  101. package/src/plugins/characters/index.jsx +284 -0
  102. package/src/plugins/characters/utils.js +447 -0
  103. package/src/plugins/css/icons/index.jsx +17 -0
  104. package/src/plugins/css/index.jsx +340 -0
  105. package/src/plugins/customPlugin/index.jsx +85 -0
  106. package/src/plugins/html/icons/index.jsx +19 -0
  107. package/src/plugins/html/index.jsx +72 -0
  108. package/src/plugins/image/__tests__/__snapshots__/component.test.jsx.snap +51 -0
  109. package/src/plugins/image/__tests__/__snapshots__/image-toolbar-logic.test.jsx.snap +27 -0
  110. package/src/plugins/image/__tests__/__snapshots__/image-toolbar.test.jsx.snap +44 -0
  111. package/src/plugins/image/__tests__/component.test.jsx +41 -0
  112. package/src/plugins/image/__tests__/image-toolbar-logic.test.jsx +42 -0
  113. package/src/plugins/image/__tests__/image-toolbar.test.jsx +11 -0
  114. package/src/plugins/image/__tests__/index.test.js +95 -0
  115. package/src/plugins/image/__tests__/insert-image-handler.test.js +113 -0
  116. package/src/plugins/image/__tests__/mock-change.js +15 -0
  117. package/src/plugins/image/alt-dialog.jsx +82 -0
  118. package/src/plugins/image/component.jsx +343 -0
  119. package/src/plugins/image/image-toolbar.jsx +100 -0
  120. package/src/plugins/image/index.jsx +227 -0
  121. package/src/plugins/image/insert-image-handler.js +79 -0
  122. package/src/plugins/index.jsx +377 -0
  123. package/src/plugins/list/__tests__/index.test.js +54 -0
  124. package/src/plugins/list/index.jsx +305 -0
  125. package/src/plugins/math/__tests__/__snapshots__/index.test.jsx.snap +48 -0
  126. package/src/plugins/math/__tests__/index.test.jsx +245 -0
  127. package/src/plugins/math/index.jsx +379 -0
  128. package/src/plugins/media/__tests__/index.test.js +75 -0
  129. package/src/plugins/media/index.jsx +325 -0
  130. package/src/plugins/media/media-dialog.js +624 -0
  131. package/src/plugins/media/media-toolbar.jsx +56 -0
  132. package/src/plugins/media/media-wrapper.jsx +43 -0
  133. package/src/plugins/rendering/index.js +31 -0
  134. package/src/plugins/respArea/drag-in-the-blank/choice.jsx +215 -0
  135. package/src/plugins/respArea/drag-in-the-blank/index.jsx +70 -0
  136. package/src/plugins/respArea/explicit-constructed-response/index.jsx +92 -0
  137. package/src/plugins/respArea/icons/index.jsx +71 -0
  138. package/src/plugins/respArea/index.jsx +299 -0
  139. package/src/plugins/respArea/inline-dropdown/index.jsx +108 -0
  140. package/src/plugins/respArea/math-templated/index.jsx +104 -0
  141. package/src/plugins/respArea/utils.jsx +90 -0
  142. package/src/plugins/table/CustomTablePlugin.js +113 -0
  143. package/src/plugins/table/__tests__/__snapshots__/table-toolbar.test.jsx.snap +44 -0
  144. package/src/plugins/table/__tests__/index.test.jsx +401 -0
  145. package/src/plugins/table/__tests__/table-toolbar.test.jsx +42 -0
  146. package/src/plugins/table/icons/index.jsx +53 -0
  147. package/src/plugins/table/index.jsx +427 -0
  148. package/src/plugins/table/table-toolbar.jsx +136 -0
  149. package/src/plugins/textAlign/icons/index.jsx +114 -0
  150. package/src/plugins/textAlign/index.jsx +23 -0
  151. package/src/plugins/toolbar/__tests__/__snapshots__/default-toolbar.test.jsx.snap +923 -0
  152. package/src/plugins/toolbar/__tests__/__snapshots__/editor-and-toolbar.test.jsx.snap +20 -0
  153. package/src/plugins/toolbar/__tests__/__snapshots__/toolbar-buttons.test.jsx.snap +36 -0
  154. package/src/plugins/toolbar/__tests__/__snapshots__/toolbar.test.jsx.snap +46 -0
  155. package/src/plugins/toolbar/__tests__/default-toolbar.test.jsx +94 -0
  156. package/src/plugins/toolbar/__tests__/editor-and-toolbar.test.jsx +37 -0
  157. package/src/plugins/toolbar/__tests__/toolbar-buttons.test.jsx +51 -0
  158. package/src/plugins/toolbar/__tests__/toolbar.test.jsx +106 -0
  159. package/src/plugins/toolbar/default-toolbar.jsx +206 -0
  160. package/src/plugins/toolbar/done-button.jsx +38 -0
  161. package/src/plugins/toolbar/editor-and-toolbar.jsx +257 -0
  162. package/src/plugins/toolbar/index.jsx +23 -0
  163. package/src/plugins/toolbar/toolbar-buttons.jsx +138 -0
  164. package/src/plugins/toolbar/toolbar.jsx +338 -0
  165. package/src/plugins/utils.js +31 -0
  166. package/src/serialization.jsx +621 -0
  167. package/src/theme.js +1 -0
@@ -0,0 +1,42 @@
1
+ import { configure, shallow } from 'enzyme';
2
+
3
+ import { Data, Block, Value } from 'slate';
4
+ import { ImageToolbar } from '../image-toolbar';
5
+ import MockChange from './mock-change';
6
+ import React from 'react';
7
+
8
+ describe('ImageToolbar', () => {
9
+ let onChange;
10
+
11
+ beforeEach(() => {
12
+ onChange = jest.fn();
13
+ });
14
+
15
+ const mkWrapper = (extras) => {
16
+ const props = {
17
+ onChange,
18
+ classes: {},
19
+ ...extras,
20
+ };
21
+
22
+ return shallow(<ImageToolbar {...props} />);
23
+ };
24
+
25
+ describe('onChange', () => {
26
+ it('renders', function() {
27
+ return expect(mkWrapper()).toMatchSnapshot();
28
+ });
29
+
30
+ it('calls onChange with alignment', () => {
31
+ const w = mkWrapper();
32
+ w.instance().onAlignmentClick('center');
33
+ expect(onChange).toHaveBeenCalledWith({ alignment: 'center' });
34
+ });
35
+
36
+ it('calls onChange with alt text', () => {
37
+ const w = mkWrapper();
38
+ w.instance().onAltTextDone('alt text');
39
+ expect(onChange).toHaveBeenCalledWith({ alt: 'alt text' }, true);
40
+ });
41
+ });
42
+ });
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ import Toolbar from '../image-toolbar';
3
+ import renderer from 'react-test-renderer';
4
+ import { Data, Block, Value } from 'slate';
5
+
6
+ it('renders correctly', () => {
7
+ const classes = { holder: 'holder' };
8
+
9
+ const tree = renderer.create(<Toolbar percent={50} classes={classes} onChange={jest.fn()} />).toJSON();
10
+ expect(tree).toMatchSnapshot();
11
+ });
@@ -0,0 +1,95 @@
1
+ import MockChange, { MockDocument } from './mock-change';
2
+
3
+ import { Data } from 'slate';
4
+ import ImageToolbar from '../';
5
+
6
+ describe('image plugin', () => {
7
+ let value = {};
8
+
9
+ const imageSupport = {
10
+ delete: jest.fn(),
11
+ add: jest.fn(),
12
+ };
13
+
14
+ const imagePlugin = ImageToolbar({
15
+ onDelete: (src, done) => {
16
+ imageSupport.delete(src, (e) => {
17
+ done(e, value);
18
+ });
19
+ },
20
+ insertImageRequested: (node, getHandler) => {
21
+ const handler = getHandler(() => value);
22
+ imageSupport.add(handler);
23
+ },
24
+ });
25
+
26
+ describe('normalizeNode', () => {
27
+ it('should exit the function if the node is not of type document', () => {
28
+ const returnValue = imagePlugin.normalizeNode({ object: 'image' });
29
+
30
+ expect(returnValue).toEqual(undefined);
31
+ });
32
+
33
+ it('should exit if the function if there are no changes needed', () => {
34
+ const nodes = [
35
+ {
36
+ object: 'text',
37
+ text: 'Before Image',
38
+ },
39
+ {
40
+ type: 'image',
41
+ },
42
+ {
43
+ object: 'text',
44
+ text: 'After Image',
45
+ },
46
+ ];
47
+ const returnValue = imagePlugin.normalizeNode({
48
+ object: 'document',
49
+ findDescendant: jest.fn((callback) => {
50
+ nodes.forEach((n) => callback(n));
51
+ }),
52
+ });
53
+ expect(returnValue).toEqual(undefined);
54
+ });
55
+
56
+ it('should return a function if there is a node with an empty text before an image', () => {
57
+ const nodes = [
58
+ {
59
+ object: 'text',
60
+ text: '',
61
+ key: '1',
62
+ },
63
+ {
64
+ type: 'image',
65
+ key: '2',
66
+ },
67
+ {
68
+ object: 'text',
69
+ text: 'After Image',
70
+ key: '3',
71
+ },
72
+ ];
73
+ const findDescendant = jest.fn((callback) => {
74
+ nodes.forEach((n) => callback(n));
75
+ });
76
+ const change = {
77
+ withoutNormalization: jest.fn((callback) => {
78
+ callback();
79
+ }),
80
+ insertTextByKey: jest.fn(),
81
+ };
82
+ const returnValue = imagePlugin.normalizeNode({
83
+ object: 'document',
84
+ findDescendant,
85
+ });
86
+
87
+ expect(returnValue).toEqual(expect.any(Function));
88
+
89
+ returnValue(change);
90
+
91
+ expect(change.withoutNormalization).toHaveBeenCalledWith(expect.any(Function));
92
+ expect(change.insertTextByKey).toHaveBeenCalledWith('1', 0, ' ');
93
+ });
94
+ });
95
+ });
@@ -0,0 +1,113 @@
1
+ import MockChange, { MockDocument } from './mock-change';
2
+
3
+ import { Data } from 'slate';
4
+ import InsertImageHandler from '../insert-image-handler';
5
+
6
+ expect.extend({
7
+ toMatchData: (received, argument) => {
8
+ const argData = Data.create(argument);
9
+ const pass = argData.equals(received.data);
10
+ if (pass) {
11
+ return {
12
+ message: () => `expected ${received.toJSON()} not to be divisible by ${argData.toJSON()}`,
13
+ pass: true,
14
+ };
15
+ } else {
16
+ return {
17
+ message: () => `expected ${received.toJSON()} to be divisible by ${argData.toJSON()}`,
18
+ pass: false,
19
+ };
20
+ }
21
+ },
22
+ });
23
+ describe('insert image handler', () => {
24
+ let change, document, value;
25
+ beforeEach(() => {
26
+ document = new MockDocument();
27
+ change = new MockChange();
28
+ value = {
29
+ change: () => change,
30
+ document,
31
+ };
32
+ });
33
+
34
+ const block = { key: 1 };
35
+ const onChange = jest.fn();
36
+
37
+ const handler = new InsertImageHandler(
38
+ block,
39
+ () => {},
40
+ () => value,
41
+ onChange,
42
+ );
43
+
44
+ test('it constructs', () => {
45
+ expect(handler).not.toEqual(undefined);
46
+ });
47
+
48
+ describe('fileChosen', () => {
49
+ let fileReader;
50
+ beforeEach(() => {
51
+ fileReader = {
52
+ readAsDataURL: jest.fn(),
53
+ };
54
+
55
+ global.FileReader = () => fileReader;
56
+ handler.fileChosen({});
57
+ });
58
+
59
+ test('calls readAsDataURL', () => {
60
+ expect(fileReader.readAsDataURL).toBeCalledWith({});
61
+ });
62
+
63
+ test('calls onChange with src -> dataUrl', () => {
64
+ fileReader.result = 'dataURL';
65
+ fileReader.onload();
66
+ expect(change.setNodeByKey).toBeCalledWith(block.key, expect.anything());
67
+ expect(change.setNodeByKey.mock.calls[0][1]).toMatchData({
68
+ src: 'dataURL',
69
+ });
70
+ expect(onChange).toBeCalledWith(change);
71
+ });
72
+ });
73
+
74
+ describe('progress', () => {
75
+ test('calls change w/ percent', () => {
76
+ handler.progress(40, 40, 100);
77
+ expect(change.setNodeByKey).toBeCalledWith(block.key, expect.anything());
78
+
79
+ expect(change.setNodeByKey.mock.calls[0][1].data.toJS()).toMatchObject({
80
+ percent: 40,
81
+ });
82
+ });
83
+ });
84
+
85
+ describe('done', () => {
86
+ test('calls setNodeByKey', () => {
87
+ handler.done(null, 'src');
88
+
89
+ expect(change.setNodeByKey).toBeCalledWith(block.key, expect.anything());
90
+
91
+ expect(change.setNodeByKey.mock.calls[0][1].data.toJS()).toMatchObject({
92
+ src: 'src',
93
+ loaded: true,
94
+ percent: 100,
95
+ });
96
+ });
97
+ });
98
+
99
+ describe('cancel', () => {
100
+ beforeEach(() => {
101
+ document.getChild = jest.fn().mockReturnValue({ data: Data.create({}), key: block.key });
102
+ handler.cancel();
103
+ });
104
+
105
+ test('calls onChange', () => {
106
+ expect(onChange).toBeCalled();
107
+ });
108
+
109
+ test('calls removeNodeByKey', () => {
110
+ expect(change.removeNodeByKey).toBeCalledWith(block.key);
111
+ });
112
+ });
113
+ });
@@ -0,0 +1,15 @@
1
+ import { Data } from 'slate';
2
+
3
+ export default function MockChange() {
4
+ this.setNodeByKey = jest.fn().mockReturnValue(this);
5
+ this.removeNodeByKey = jest.fn().mockReturnValue(this);
6
+ this.insertInline = jest.fn().mockReturnValue(this);
7
+ this.moveFocusTo = jest.fn().mockReturnValue(this);
8
+ this.moveAnchorTo = jest.fn().mockReturnValue(this);
9
+ }
10
+
11
+ export function MockDocument() {
12
+ this.getChild = jest.fn().mockReturnValue({ data: Data.create({}) });
13
+
14
+ this.getDescendant = jest.fn();
15
+ }
@@ -0,0 +1,82 @@
1
+ import React from 'react';
2
+ import DialogContent from '@material-ui/core/DialogContent';
3
+ import ArrowBackIos from '@material-ui/icons/ArrowBackIos';
4
+ import TextField from '@material-ui/core/TextField';
5
+ import DialogActions from '@material-ui/core/DialogActions';
6
+ import Button from '@material-ui/core/Button';
7
+ import Dialog from '@material-ui/core/Dialog';
8
+ import PropTypes from 'prop-types';
9
+
10
+ export class AltDialog extends React.Component {
11
+ static propTypes = {
12
+ onDone: PropTypes.func.isRequired,
13
+ alt: PropTypes.string,
14
+ };
15
+
16
+ constructor(props) {
17
+ super(props);
18
+
19
+ const { alt } = props;
20
+
21
+ this.state = {
22
+ value: alt,
23
+ };
24
+ }
25
+
26
+ closeDialog = () => {
27
+ const allDialogs = document.querySelectorAll('#text-dialog');
28
+
29
+ allDialogs.forEach(function(s) {
30
+ return s.remove();
31
+ });
32
+ };
33
+
34
+ onDone = () => {
35
+ const { onDone } = this.props;
36
+ const { value } = this.state;
37
+
38
+ onDone(value);
39
+ this.closeDialog();
40
+ };
41
+
42
+ handleOverflow = () => {
43
+ document.body.style.removeProperty('overflow');
44
+ };
45
+
46
+ render() {
47
+ const { value } = this.state;
48
+
49
+ return (
50
+ <Dialog
51
+ open
52
+ disablePortal
53
+ onClose={this.closeDialog}
54
+ id="text-dialog"
55
+ hideBackdrop
56
+ disableScrollLock
57
+ onEntered={this.handleOverflow}
58
+ >
59
+ <DialogContent>
60
+ <div style={{ display: 'flex' }}>
61
+ <ArrowBackIos style={{ paddingTop: '6px' }} />
62
+ <TextField
63
+ multiline
64
+ placeholder={'Enter an Alt Text description of this image'}
65
+ helperText={
66
+ 'Users with visual limitations rely on Alt Text, since screen readers cannot otherwise describe the contents of an image.'
67
+ }
68
+ value={value}
69
+ onChange={(event) => this.setState({ value: event.target.value })}
70
+ FormHelperTextProps={{ style: { fontSize: 14 } }}
71
+ />
72
+ </div>
73
+ </DialogContent>
74
+ <DialogActions>
75
+ <Button onClick={this.onDone}>Done</Button>
76
+ </DialogActions>
77
+ </Dialog>
78
+ );
79
+ }
80
+ }
81
+
82
+ export default AltDialog;
@@ -0,0 +1,343 @@
1
+ import LinearProgress from '@material-ui/core/LinearProgress';
2
+ import PropTypes from 'prop-types';
3
+ import React from 'react';
4
+ import classNames from 'classnames';
5
+ import debug from 'debug';
6
+ import { withStyles } from '@material-ui/core/styles';
7
+ import SlatePropTypes from 'slate-prop-types';
8
+
9
+ const log = debug('@pie-lib:editable-html:plugins:image:component');
10
+
11
+ const size = (s) => (s ? `${s}px` : 'auto');
12
+
13
+ export class Component extends React.Component {
14
+ static propTypes = {
15
+ node: SlatePropTypes.node.isRequired,
16
+ editor: PropTypes.shape({
17
+ change: PropTypes.func.isRequired,
18
+ value: PropTypes.object,
19
+ }).isRequired,
20
+ classes: PropTypes.object.isRequired,
21
+ attributes: PropTypes.object,
22
+ onFocus: PropTypes.func,
23
+ onBlur: PropTypes.func,
24
+ maxImageWidth: PropTypes.number,
25
+ maxImageHeight: PropTypes.number,
26
+ };
27
+
28
+ getWidth = (percent) => {
29
+ const multiplier = percent / 100;
30
+ return this.img.naturalWidth * multiplier;
31
+ };
32
+
33
+ getHeight = (percent) => {
34
+ const multiplier = percent / 100;
35
+ return this.img.naturalHeight * multiplier;
36
+ };
37
+
38
+ getPercentFromWidth = (width) => {
39
+ var floored = (width / this.img.naturalWidth) * 4;
40
+ return parseInt(floored.toFixed(0) * 25, 10);
41
+ };
42
+
43
+ applySizeData = () => {
44
+ const { node, editor } = this.props;
45
+
46
+ let update = node.data;
47
+
48
+ const w = update.get('width');
49
+ if (w) {
50
+ update = update.set('resizePercent', this.getPercentFromWidth(w));
51
+ }
52
+
53
+ log('[applySizeData] update: ', update);
54
+
55
+ if (!update.equals(node.data)) {
56
+ editor.change((c) => c.setNodeByKey(node.key, { data: update }));
57
+ }
58
+ };
59
+
60
+ initialiseResize = () => {
61
+ window.addEventListener('mousemove', this.startResizing, false);
62
+ window.addEventListener('mouseup', this.stopResizing, false);
63
+ };
64
+
65
+ componentDidMount() {
66
+ this.applySizeData();
67
+
68
+ const resizeHandle = this.resize;
69
+
70
+ if (resizeHandle) {
71
+ resizeHandle.addEventListener('mousedown', this.initialiseResize, false);
72
+ }
73
+ }
74
+
75
+ componentDidUpdate() {
76
+ this.applySizeData();
77
+ }
78
+
79
+ getSize(data) {
80
+ return {
81
+ width: size(data.get('width')),
82
+ height: size(data.get('height')),
83
+ objectFit: 'contain',
84
+ };
85
+ }
86
+
87
+ loadImage = () => {
88
+ let { maxImageWidth, maxImageHeight } = this.props || {};
89
+ maxImageWidth = maxImageWidth || 700;
90
+ maxImageHeight = maxImageHeight || 900;
91
+
92
+ const box = this.img;
93
+
94
+ //on first load
95
+ if (!box.style.width || box.style.width === 'auto') {
96
+ const dimensions = {
97
+ width: (box && box.naturalWidth) || 100,
98
+ height: (box && box.naturalHeight) || 100,
99
+ };
100
+
101
+ const { width, height } = this.updateImageDimensions(
102
+ dimensions,
103
+ {
104
+ width: dimensions.width < maxImageWidth ? dimensions.width : maxImageWidth,
105
+ height: dimensions.height < maxImageHeight ? dimensions.height : maxImageHeight,
106
+ },
107
+ true,
108
+ );
109
+
110
+ box.style.width = `${width}px`;
111
+ box.style.height = `${height}px`;
112
+
113
+ this.setState({
114
+ dimensions: { height: height, width: width },
115
+ });
116
+
117
+ const { node, editor } = this.props;
118
+
119
+ let update = node.data;
120
+
121
+ update = update.set('width', width);
122
+ update = update.set('height', height);
123
+
124
+ if (!update.equals(node.data)) {
125
+ editor.change((c) => c.setNodeByKey(node.key, { data: update }));
126
+ }
127
+ }
128
+ };
129
+
130
+ startResizing = (e) => {
131
+ const bounds = e.target.getBoundingClientRect();
132
+ const box = this.img;
133
+ const dimensions = {
134
+ width: (box && box.naturalWidth) || 100,
135
+ height: (box && box.naturalHeight) || 100,
136
+ };
137
+
138
+ const { width, height } = this.updateImageDimensions(
139
+ dimensions,
140
+ {
141
+ width: e.clientX - bounds.left,
142
+ height: e.clientY - bounds.top,
143
+ },
144
+ true,
145
+ );
146
+
147
+ const hasMinimumWidth = width > 50 && height > 50;
148
+ const hasDimensionsConstraints = width <= 700 && height <= 900;
149
+
150
+ if (hasMinimumWidth && hasDimensionsConstraints && box) {
151
+ box.style.width = `${width}px`;
152
+ box.style.height = `${height}px`;
153
+
154
+ this.setState({
155
+ dimensions: { height: height, width: width },
156
+ });
157
+
158
+ const { node, editor } = this.props;
159
+
160
+ let update = node.data;
161
+
162
+ update = update.set('width', width);
163
+ update = update.set('height', height);
164
+
165
+ if (!update.equals(node.data)) {
166
+ editor.change((c) => c.setNodeByKey(node.key, { data: update }));
167
+ }
168
+ }
169
+ };
170
+
171
+ stopResizing = () => {
172
+ window.removeEventListener('mousemove', this.startResizing, false);
173
+ window.removeEventListener('mouseup', this.stopResizing, false);
174
+ };
175
+
176
+ updateImageDimensions = (initialDim, nextDim, keepAspectRatio, resizeType) => {
177
+ // if we want to keep image aspect ratio
178
+ if (keepAspectRatio) {
179
+ const imageAspectRatio = initialDim.width / initialDim.height;
180
+
181
+ if (resizeType === 'height') {
182
+ // if we want to change image height => we update the width accordingly
183
+ return {
184
+ width: nextDim.height * imageAspectRatio,
185
+ height: nextDim.height,
186
+ };
187
+ }
188
+
189
+ // if we want to change image width => we update the height accordingly
190
+ return {
191
+ width: nextDim.width,
192
+ height: nextDim.width / imageAspectRatio,
193
+ };
194
+ }
195
+
196
+ // if we don't want to keep aspect ratio, we just update both values
197
+ return {
198
+ width: nextDim.width,
199
+ height: nextDim.height,
200
+ };
201
+ };
202
+
203
+ render() {
204
+ const { node, editor, classes, attributes, onFocus } = this.props;
205
+ const active = editor.value.isFocused && editor.value.selection.hasEdgeIn(node);
206
+ const src = node.data.get('src');
207
+ const loaded = node.data.get('loaded') !== false;
208
+ const deleteStatus = node.data.get('deleteStatus');
209
+ const alignment = node.data.get('alignment');
210
+ const percent = node.data.get('percent');
211
+ const alt = node.data.get('alt');
212
+ let justifyContent;
213
+
214
+ switch (alignment) {
215
+ case 'left':
216
+ justifyContent = 'flex-start';
217
+ break;
218
+
219
+ case 'center':
220
+ justifyContent = 'center';
221
+ break;
222
+
223
+ case 'right':
224
+ justifyContent = 'flex-end';
225
+ break;
226
+
227
+ default:
228
+ justifyContent = 'flex-start';
229
+ break;
230
+ }
231
+ log('[render] node.data:', node.data);
232
+
233
+ const size = this.getSize(node.data);
234
+
235
+ log('[render] style:', size);
236
+
237
+ const className = classNames(
238
+ classes.root,
239
+ !loaded && classes.loading,
240
+ deleteStatus === 'pending' && classes.pendingDelete,
241
+ );
242
+
243
+ const progressClasses = classNames(classes.progress, loaded && classes.hideProgress);
244
+
245
+ return [
246
+ <span key={'sp1'}>&nbsp;</span>,
247
+ <div key={'comp'} onFocus={onFocus} className={className} style={{ justifyContent }}>
248
+ <LinearProgress mode="determinate" value={percent > 0 ? percent : 0} className={progressClasses} />
249
+ <div className={classes.imageContainer}>
250
+ <img
251
+ {...attributes}
252
+ className={classNames(classes.image, active && classes.active)}
253
+ ref={(ref) => {
254
+ this.img = ref;
255
+ }}
256
+ src={src}
257
+ style={size}
258
+ onLoad={this.loadImage}
259
+ alt={alt}
260
+ />
261
+ <div
262
+ ref={(ref) => {
263
+ this.resize = ref;
264
+ }}
265
+ className={classNames(classes.resize, 'resize')}
266
+ />
267
+ </div>
268
+ </div>,
269
+ <span key={'sp2'}>&nbsp;</span>,
270
+ ];
271
+ }
272
+ }
273
+
274
+ const styles = (theme) => ({
275
+ portal: {
276
+ position: 'absolute',
277
+ opacity: 0,
278
+ transition: 'opacity 200ms linear',
279
+ },
280
+ floatingButtonRow: {
281
+ backgroundColor: theme.palette.background.paper,
282
+ borderRadius: '1px',
283
+ display: 'flex',
284
+ padding: '10px',
285
+ border: `solid 1px ${theme.palette.grey[200]}`,
286
+ boxShadow:
287
+ '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)',
288
+ },
289
+ progress: {
290
+ position: 'absolute',
291
+ left: '0',
292
+ width: 'fit-content',
293
+ top: '0%',
294
+ transition: 'opacity 200ms linear',
295
+ },
296
+ hideProgress: {
297
+ opacity: 0,
298
+ },
299
+ loading: {
300
+ opacity: 0.3,
301
+ },
302
+ pendingDelete: {
303
+ opacity: 0.3,
304
+ },
305
+ root: {
306
+ position: 'relative',
307
+ border: `solid 1px ${theme.palette.common.white}`,
308
+ display: 'flex',
309
+ transition: 'opacity 200ms linear',
310
+ },
311
+ delete: {
312
+ position: 'absolute',
313
+ right: 0,
314
+ },
315
+ imageContainer: {
316
+ position: 'relative',
317
+ width: 'fit-content',
318
+ display: 'flex',
319
+ alignItems: 'center',
320
+
321
+ '&&:hover > .resize': {
322
+ display: 'block',
323
+ },
324
+ },
325
+ active: {
326
+ border: `solid 1px ${theme.palette.primary.main}`,
327
+ },
328
+ resize: {
329
+ backgroundColor: theme.palette.primary.main,
330
+ cursor: 'col-resize',
331
+ height: '35px',
332
+ width: '5px',
333
+ borderRadius: 8,
334
+ marginLeft: '5px',
335
+ marginRight: '10px',
336
+ display: 'none',
337
+ },
338
+ drawableHeight: {
339
+ minHeight: 350,
340
+ },
341
+ });
342
+
343
+ export default withStyles(styles)(Component);