@pie-lib/editable-html 11.1.2-next.0 → 11.2.0-beta.1
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 +43 -167
- package/NEXT.CHANGELOG.json +1 -0
- package/lib/block-tags.js +25 -0
- package/lib/block-tags.js.map +1 -0
- package/lib/constants.js +16 -0
- package/lib/constants.js.map +1 -0
- package/lib/editor.js +348 -87
- package/lib/editor.js.map +1 -1
- package/lib/index.js +25 -9
- package/lib/index.js.map +1 -1
- package/lib/plugins/characters/index.js +8 -3
- package/lib/plugins/characters/index.js.map +1 -1
- package/lib/plugins/characters/utils.js +12 -12
- package/lib/plugins/characters/utils.js.map +1 -1
- package/lib/plugins/css/icons/index.js +37 -0
- package/lib/plugins/css/icons/index.js.map +1 -0
- package/lib/plugins/css/index.js +397 -0
- package/lib/plugins/css/index.js.map +1 -0
- package/lib/plugins/customPlugin/index.js +114 -0
- package/lib/plugins/customPlugin/index.js.map +1 -0
- package/lib/plugins/html/index.js +11 -7
- package/lib/plugins/html/index.js.map +1 -1
- package/lib/plugins/image/index.js +2 -1
- package/lib/plugins/image/index.js.map +1 -1
- package/lib/plugins/image/insert-image-handler.js +13 -4
- package/lib/plugins/image/insert-image-handler.js.map +1 -1
- package/lib/plugins/index.js +270 -11
- package/lib/plugins/index.js.map +1 -1
- package/lib/plugins/list/index.js +130 -0
- package/lib/plugins/list/index.js.map +1 -1
- package/lib/plugins/math/index.js +91 -56
- package/lib/plugins/math/index.js.map +1 -1
- package/lib/plugins/media/index.js +5 -2
- package/lib/plugins/media/index.js.map +1 -1
- package/lib/plugins/media/media-dialog.js +98 -57
- package/lib/plugins/media/media-dialog.js.map +1 -1
- package/lib/plugins/rendering/index.js +46 -0
- package/lib/plugins/rendering/index.js.map +1 -0
- package/lib/plugins/respArea/drag-in-the-blank/choice.js +5 -2
- package/lib/plugins/respArea/drag-in-the-blank/choice.js.map +1 -1
- package/lib/plugins/respArea/explicit-constructed-response/index.js +11 -9
- package/lib/plugins/respArea/explicit-constructed-response/index.js.map +1 -1
- package/lib/plugins/respArea/index.js +69 -21
- package/lib/plugins/respArea/index.js.map +1 -1
- package/lib/plugins/respArea/inline-dropdown/index.js +10 -5
- package/lib/plugins/respArea/inline-dropdown/index.js.map +1 -1
- package/lib/plugins/respArea/math-templated/index.js +130 -0
- package/lib/plugins/respArea/math-templated/index.js.map +1 -0
- package/lib/plugins/respArea/utils.js +16 -1
- package/lib/plugins/respArea/utils.js.map +1 -1
- package/lib/plugins/table/CustomTablePlugin.js +133 -0
- package/lib/plugins/table/CustomTablePlugin.js.map +1 -0
- package/lib/plugins/table/index.js +43 -59
- package/lib/plugins/table/index.js.map +1 -1
- package/lib/plugins/table/table-toolbar.js +33 -4
- package/lib/plugins/table/table-toolbar.js.map +1 -1
- package/lib/plugins/textAlign/icons/index.js +226 -0
- package/lib/plugins/textAlign/icons/index.js.map +1 -0
- package/lib/plugins/textAlign/index.js +34 -0
- package/lib/plugins/textAlign/index.js.map +1 -0
- package/lib/plugins/toolbar/default-toolbar.js +82 -27
- package/lib/plugins/toolbar/default-toolbar.js.map +1 -1
- package/lib/plugins/toolbar/done-button.js +5 -2
- package/lib/plugins/toolbar/done-button.js.map +1 -1
- package/lib/plugins/toolbar/editor-and-toolbar.js +18 -19
- package/lib/plugins/toolbar/editor-and-toolbar.js.map +1 -1
- package/lib/plugins/toolbar/toolbar-buttons.js +44 -11
- package/lib/plugins/toolbar/toolbar-buttons.js.map +1 -1
- package/lib/plugins/toolbar/toolbar.js +35 -11
- package/lib/plugins/toolbar/toolbar.js.map +1 -1
- package/lib/serialization.js +233 -44
- package/lib/serialization.js.map +1 -1
- package/package.json +11 -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 +82 -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/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
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import debug from 'debug';
|
|
3
|
+
import MockChange from '../../image/__tests__/mock-change';
|
|
4
|
+
import { Data } from 'slate';
|
|
5
|
+
import MathPlugin, { serialization, inlineMath, CustomToolbarComp } from '../index';
|
|
6
|
+
import { shallow } from 'enzyme';
|
|
7
|
+
import { MathToolbar } from '@pie-lib/math-toolbar';
|
|
8
|
+
jest.mock('@pie-framework/mathquill', () => ({
|
|
9
|
+
StaticMath: jest.fn(),
|
|
10
|
+
getInterface: jest.fn().mockReturnThis(),
|
|
11
|
+
registerEmbed: jest.fn(),
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
jest.mock('@pie-lib/math-toolbar', () => ({
|
|
15
|
+
MathPreview: () => <div />,
|
|
16
|
+
MathToolbar: () => <div />,
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
jest.mock('@pie-lib/math-rendering', () => ({
|
|
20
|
+
...jest.requireActual('@pie-lib/math-rendering'),
|
|
21
|
+
mmlToLatex: jest.fn(() => {
|
|
22
|
+
return '/foobar/latex';
|
|
23
|
+
}),
|
|
24
|
+
}));
|
|
25
|
+
const log = debug('@pie-lib:editable-html:test:math');
|
|
26
|
+
|
|
27
|
+
// I believe @andrei is moving this stuff out.
|
|
28
|
+
describe('MathPlugin', () => {
|
|
29
|
+
describe('toolbar', () => {
|
|
30
|
+
describe('onClick', () => {
|
|
31
|
+
let plugin, mockChange, value, onChange;
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
plugin = MathPlugin({});
|
|
34
|
+
mockChange = new MockChange();
|
|
35
|
+
value = {
|
|
36
|
+
change: jest.fn(() => mockChange),
|
|
37
|
+
};
|
|
38
|
+
onChange = jest.fn();
|
|
39
|
+
plugin.toolbar.onClick(value, onChange);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('calls insertInline', () => {
|
|
43
|
+
expect(mockChange.insertInline).toBeCalledWith(expect.objectContaining({ data: inlineMath().data }));
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('it calls onChange', () => {
|
|
47
|
+
expect(onChange).toHaveBeenCalledWith(mockChange);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('renderNode', () => {
|
|
53
|
+
test('the component has props', () => {
|
|
54
|
+
const plugin = MathPlugin({});
|
|
55
|
+
const { props } = plugin.renderNode({ node: { type: 'math' } });
|
|
56
|
+
expect(props.node).toEqual({ type: 'math' });
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe('serialization', () => {
|
|
61
|
+
describe('deserialize', () => {
|
|
62
|
+
const assertDeserialize = (html, expected, wrapType) => {
|
|
63
|
+
it(`innerHTML: ${html} is deserialized to: ${expected} with wrapType: ${wrapType}`, () => {
|
|
64
|
+
const el = {
|
|
65
|
+
tagName: 'span',
|
|
66
|
+
childNodes: [],
|
|
67
|
+
getAttribute: jest.fn(() => ''),
|
|
68
|
+
hasAttribute: jest.fn(() => true),
|
|
69
|
+
innerHTML: html,
|
|
70
|
+
};
|
|
71
|
+
const next = jest.fn();
|
|
72
|
+
|
|
73
|
+
const out = serialization.deserialize(el, next);
|
|
74
|
+
expect(out).toEqual({
|
|
75
|
+
object: 'inline',
|
|
76
|
+
type: 'math',
|
|
77
|
+
isVoid: true,
|
|
78
|
+
nodes: [],
|
|
79
|
+
data: {
|
|
80
|
+
latex: expected,
|
|
81
|
+
wrapper: wrapType,
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
assertDeserialize('$$<$$', '<', MathPlugin.DOLLAR);
|
|
88
|
+
assertDeserialize('$<$', '<', MathPlugin.DOLLAR);
|
|
89
|
+
assertDeserialize('\\(<\\)', '<', MathPlugin.ROUND_BRACKETS);
|
|
90
|
+
assertDeserialize('\\[<\\]', '<', MathPlugin.ROUND_BRACKETS);
|
|
91
|
+
assertDeserialize('latex', 'latex', MathPlugin.ROUND_BRACKETS);
|
|
92
|
+
assertDeserialize('\\displaystyle foo', 'foo', MathPlugin.ROUND_BRACKETS);
|
|
93
|
+
|
|
94
|
+
it('should make mathml editable if MathPlugin.mathMlOptions.mmlEditing is true', () => {
|
|
95
|
+
MathPlugin.mathMlOptions.mmlEditing = true;
|
|
96
|
+
const el = {
|
|
97
|
+
tagName: 'math',
|
|
98
|
+
outerHTML:
|
|
99
|
+
'<math xmlns="http://www.w3.org/1998/Math/MathML"> <mn>2</mn> <mi>x</mi> <mtext> </mtext> <mo>≤</mo> <mn>4</mn> <mi>y</mi> <mtext> </mtext> <mo>+</mo> <mtext> </mtext> <mn>8</mn> <msqrt> <mi>h</mi> </msqrt></math>',
|
|
100
|
+
};
|
|
101
|
+
const next = jest.fn();
|
|
102
|
+
|
|
103
|
+
const out = serialization.deserialize(el, next);
|
|
104
|
+
|
|
105
|
+
expect(out).toEqual({
|
|
106
|
+
object: 'inline',
|
|
107
|
+
type: 'math',
|
|
108
|
+
isVoid: true,
|
|
109
|
+
nodes: [],
|
|
110
|
+
data: {
|
|
111
|
+
latex: '/foobar/latex',
|
|
112
|
+
wrapper: 'round_brackets',
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('should make mathml readOnly if MathPlugin.mathMlOptions.mmlEditing is false', () => {
|
|
118
|
+
MathPlugin.mathMlOptions.mmlEditing = false;
|
|
119
|
+
const el = {
|
|
120
|
+
tagName: 'math',
|
|
121
|
+
outerHTML:
|
|
122
|
+
'<math xmlns="http://www.w3.org/1998/Math/MathML"> <mn>2</mn> <mi>x</mi> <mtext> </mtext> <mo>≤</mo> <mn>4</mn> <mi>y</mi> <mtext> </mtext> <mo>+</mo> <mtext> </mtext> <mn>8</mn> <msqrt> <mi>h</mi> </msqrt></math>',
|
|
123
|
+
};
|
|
124
|
+
const next = jest.fn();
|
|
125
|
+
|
|
126
|
+
const out = serialization.deserialize(el, next);
|
|
127
|
+
|
|
128
|
+
expect(out).toEqual({
|
|
129
|
+
object: 'inline',
|
|
130
|
+
isVoid: true,
|
|
131
|
+
type: 'mathml',
|
|
132
|
+
data: {
|
|
133
|
+
html: el.outerHTML,
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
describe('serialize', () => {
|
|
140
|
+
const assertSerialize = (latex, expectedHtml, wrapper) => {
|
|
141
|
+
wrapper = wrapper || MathPlugin.ROUND_BRACKETS;
|
|
142
|
+
it(`${latex} is serialized to: ${expectedHtml}`, () => {
|
|
143
|
+
const object = {
|
|
144
|
+
kind: 'inline',
|
|
145
|
+
type: 'math',
|
|
146
|
+
isVoid: true,
|
|
147
|
+
nodes: [],
|
|
148
|
+
data: Data.create({ latex, wrapper }),
|
|
149
|
+
};
|
|
150
|
+
const children = [];
|
|
151
|
+
|
|
152
|
+
const out = serialization.serialize(object, children);
|
|
153
|
+
log('out: ', out);
|
|
154
|
+
expect(out).toEqual(
|
|
155
|
+
<span data-latex="" data-raw={latex}>
|
|
156
|
+
{expectedHtml}
|
|
157
|
+
</span>,
|
|
158
|
+
);
|
|
159
|
+
});
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
assertSerialize('latex', '\\(latex\\)', MathPlugin.ROUND_BRACKETS);
|
|
163
|
+
assertSerialize('latex', '\\(latex\\)', MathPlugin.SQUARE_BRACKETS);
|
|
164
|
+
assertSerialize('latex', '$latex$', MathPlugin.DOLLAR);
|
|
165
|
+
assertSerialize('latex', '$latex$', MathPlugin.DOUBLE_DOLLAR);
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Note that when this is converted to html it get's escaped - but that's an issue with the slate html-serializer.
|
|
169
|
+
*/
|
|
170
|
+
assertSerialize('<', '\\(<\\)');
|
|
171
|
+
assertSerialize('x<y', '\\(x<y\\)');
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
describe('CustomToolbarComp', () => {
|
|
177
|
+
let onDataChange;
|
|
178
|
+
let onToolbarDone;
|
|
179
|
+
|
|
180
|
+
const wrapper = (extras) => {
|
|
181
|
+
let mockChange = new MockChange();
|
|
182
|
+
const defaults = {
|
|
183
|
+
node: {
|
|
184
|
+
key: '1',
|
|
185
|
+
data: Data.create({ latex: 'foo' }),
|
|
186
|
+
equals: () => true,
|
|
187
|
+
},
|
|
188
|
+
value: {
|
|
189
|
+
document: {
|
|
190
|
+
getNextText: jest.fn().mockReturnValue({ key: 'nt' }),
|
|
191
|
+
},
|
|
192
|
+
change: jest.fn().mockReturnValue(mockChange),
|
|
193
|
+
},
|
|
194
|
+
onDataChange,
|
|
195
|
+
onToolbarDone,
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const props = {
|
|
199
|
+
...defaults,
|
|
200
|
+
...extras,
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
return shallow(<CustomToolbarComp {...props} />);
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
describe('render', () => {
|
|
207
|
+
it('renders without default keypadMode', () => {
|
|
208
|
+
const w = wrapper();
|
|
209
|
+
|
|
210
|
+
expect(w).toMatchSnapshot();
|
|
211
|
+
|
|
212
|
+
w.setProps({ pluginProps: { math: { keypadMode: 3 } } });
|
|
213
|
+
|
|
214
|
+
expect(w).toMatchSnapshot();
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('renders with default keypadMode', () => {
|
|
218
|
+
const w = wrapper({ pluginProps: { math: { keypadMode: 'geometry' } } });
|
|
219
|
+
|
|
220
|
+
expect(w).toMatchSnapshot();
|
|
221
|
+
|
|
222
|
+
w.setProps({ pluginProps: { math: { keypadMode: 3 } } });
|
|
223
|
+
|
|
224
|
+
expect(w).toMatchSnapshot();
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
describe('onDone', () => {
|
|
229
|
+
it('calls onToolbarDone', () => {
|
|
230
|
+
onToolbarDone = jest.fn();
|
|
231
|
+
const w = wrapper();
|
|
232
|
+
w.find(MathToolbar).prop('onDone')('oo');
|
|
233
|
+
expect(onToolbarDone).toHaveBeenCalledWith(expect.anything(), false);
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
describe('onChange', () => {
|
|
238
|
+
it('calls onDataChange', () => {
|
|
239
|
+
onDataChange = jest.fn();
|
|
240
|
+
const w = wrapper();
|
|
241
|
+
w.find(MathToolbar).prop('onChange')('oo');
|
|
242
|
+
expect(onDataChange).toHaveBeenCalledWith('1', { latex: 'oo' });
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
});
|
|
@@ -7,7 +7,7 @@ import debug from 'debug';
|
|
|
7
7
|
import SlatePropTypes from 'slate-prop-types';
|
|
8
8
|
import PropTypes from 'prop-types';
|
|
9
9
|
|
|
10
|
-
import { BLOCK_TAGS } from '../../
|
|
10
|
+
import { BLOCK_TAGS } from '../../block-tags';
|
|
11
11
|
import isEqual from 'lodash/isEqual';
|
|
12
12
|
|
|
13
13
|
const log = debug('@pie-lib:editable-html:plugins:math');
|
|
@@ -36,6 +36,7 @@ export const CustomToolbarComp = React.memo(
|
|
|
36
36
|
...node.data.toObject(),
|
|
37
37
|
latex,
|
|
38
38
|
};
|
|
39
|
+
|
|
39
40
|
const change = value.change().setNodeByKey(node.key, { data: update });
|
|
40
41
|
|
|
41
42
|
const nextText = value.document.getNextText(node.key);
|
|
@@ -105,6 +106,7 @@ export default function MathPlugin(opts) {
|
|
|
105
106
|
return {
|
|
106
107
|
name: 'math',
|
|
107
108
|
toolbar: {
|
|
109
|
+
ariaLabel: 'Math Toolbar',
|
|
108
110
|
icon: <Functions />,
|
|
109
111
|
onClick: (value, onChange) => {
|
|
110
112
|
log('[insertMath]');
|
|
@@ -191,35 +193,79 @@ const getTagName = (el) => {
|
|
|
191
193
|
* @param input
|
|
192
194
|
* @returns {*}
|
|
193
195
|
*/
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
/*
|
|
201
|
-
We check if this element resulted is:
|
|
202
|
-
div - continuation of a beginning of a HTML element
|
|
203
|
-
/div - closing of a HTML tag
|
|
204
|
-
br/> - beginning and closing of a html TAG
|
|
205
|
-
*/
|
|
206
|
-
if (part.match(/<[a-zA-Z/][\s\S]*>/gi)) {
|
|
207
|
-
return `${st}${st ? '<' : ''}${part}`;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
return `${st}${st ? '<' : ''}${part}`;
|
|
211
|
-
}, '');
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
return input;
|
|
196
|
+
const arrowHandlingCase = (input) => {
|
|
197
|
+
/*
|
|
198
|
+
If we have a < character followed by a letter
|
|
199
|
+
we make sure to replace it with a < sign instead
|
|
200
|
+
*/
|
|
201
|
+
return input.replace(/<([a-zA-Z]*)/g, '<$1');
|
|
215
202
|
};
|
|
216
203
|
|
|
217
|
-
function
|
|
204
|
+
function replaceLeftRight(latexInput) {
|
|
218
205
|
// for some reason, mmlToLatex parses () incorrectly - or at least in a way that our interpreter can not use them
|
|
219
206
|
// Replace '\\left.' and '\\right.' with an empty string
|
|
220
207
|
return latexInput.replace(/\\left\.\s*|\\right\.\s*/g, '');
|
|
221
208
|
}
|
|
222
209
|
|
|
210
|
+
const convertLatexToMathMl = ({ latex, decoded, wrapper }) => {
|
|
211
|
+
const removeEmptyMos = (mmlFromLatex) => {
|
|
212
|
+
// Regular expression to match <mo>⁡</mo> and <mo ...>⁡</mo>, which get added when using log with base
|
|
213
|
+
// not sure why they get added, but they add an extra space which is not needed
|
|
214
|
+
const regex = /<mo(?: [^>]*)?>⁡<\/mo>/g;
|
|
215
|
+
|
|
216
|
+
// Replace all occurrences of the matched patterns
|
|
217
|
+
return mmlFromLatex.replace(regex, '');
|
|
218
|
+
};
|
|
219
|
+
const handled = arrowHandlingCase(decoded);
|
|
220
|
+
|
|
221
|
+
const latexToConvert = `<span data-latex="" data-raw="${handled}">${wrapMath(handled, wrapper)}</span>`;
|
|
222
|
+
|
|
223
|
+
// use math rendering (MathJax) to convert latex to mathMl
|
|
224
|
+
let mathMlFromLatex = renderMath(latexToConvert, {
|
|
225
|
+
skipWaitForMathRenderingLib: true,
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// if renderMath returned the exact same string that we sent, it just means that the conversion could not be done
|
|
229
|
+
const conversionDidNotWork = isEqual(latexToConvert, mathMlFromLatex);
|
|
230
|
+
|
|
231
|
+
mathMlFromLatex = removeEmptyMos(mathMlFromLatex);
|
|
232
|
+
|
|
233
|
+
// we convert resulted mathml to latex to check if the resulted mathMl can be converted back to latex if user wants to edit it later
|
|
234
|
+
const latexFromMathMl = mathMlFromLatex ? mmlToLatex(mathMlFromLatex) : '';
|
|
235
|
+
|
|
236
|
+
// we need to remove all the spaces from the latex to be able to compare it
|
|
237
|
+
const strippedL = latex.replace(/\s/g, '');
|
|
238
|
+
const strippedNewL = latexFromMathMl.replace(/\s/g, '');
|
|
239
|
+
|
|
240
|
+
// we check if the latex keeps his form after being converted to mathml and back to latex
|
|
241
|
+
// if it does, we can safely convert it to mathml
|
|
242
|
+
if (!isEqual(strippedL, strippedNewL)) {
|
|
243
|
+
const correctedLatex = replaceLeftRight(latexFromMathMl);
|
|
244
|
+
|
|
245
|
+
// As George requested in PD-3167, I will set the new mathML anyway, and also log differences
|
|
246
|
+
// if it doesn't, we keep the latex version
|
|
247
|
+
// eslint-disable-next-line no-console
|
|
248
|
+
console.log('This latex can not be safely converted to mathml so we will keep the latex version!!!', {
|
|
249
|
+
initialLatex: latex,
|
|
250
|
+
newLatex: latexFromMathMl,
|
|
251
|
+
correctedLatex,
|
|
252
|
+
mathML: mathMlFromLatex,
|
|
253
|
+
conversionDidNotWork,
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return { mathMlFromLatex, conversionDidNotWork };
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
const convertMathMlToLatex = (mathMl) => {
|
|
261
|
+
const htmlWithRemovedSpaces = mathMl.replaceAll(' ', ' ');
|
|
262
|
+
const htmlToUse = mmlToLatex(htmlWithRemovedSpaces);
|
|
263
|
+
const latex = htmlDecode(htmlToUse);
|
|
264
|
+
|
|
265
|
+
// todo fix this in mathml-to-latex
|
|
266
|
+
return replaceLeftRight(latex);
|
|
267
|
+
};
|
|
268
|
+
|
|
223
269
|
export const serialization = {
|
|
224
270
|
deserialize(el) {
|
|
225
271
|
const tagName = getTagName(el);
|
|
@@ -237,16 +283,10 @@ export const serialization = {
|
|
|
237
283
|
* This is here in order to be able to render mathml content
|
|
238
284
|
*/
|
|
239
285
|
if (tagName === 'math' || (el.dataset && el.dataset.type === 'mathml') || hasMathChild) {
|
|
240
|
-
const
|
|
286
|
+
const mathMl = hasMathChild ? el.innerHTML : el.outerHTML;
|
|
241
287
|
|
|
242
288
|
if (MathPlugin.mathMlOptions.mmlEditing) {
|
|
243
|
-
|
|
244
|
-
const htmlWithRemovedSpaces = newHtml.replaceAll(" ", " ");
|
|
245
|
-
const htmlToUse = mmlToLatex(htmlWithRemovedSpaces);
|
|
246
|
-
const latex = htmlDecode(htmlToUse);
|
|
247
|
-
// todo fix this in mathml-to-latex
|
|
248
|
-
const correctedLatex = fixLatexExpression(latex);
|
|
249
|
-
const { unwrapped, wrapType } = unWrapMath(correctedLatex);
|
|
289
|
+
const { unwrapped, wrapType } = unWrapMath(convertMathMlToLatex(mathMl));
|
|
250
290
|
|
|
251
291
|
return {
|
|
252
292
|
object: 'inline',
|
|
@@ -265,7 +305,7 @@ export const serialization = {
|
|
|
265
305
|
isVoid: true,
|
|
266
306
|
type: 'mathml',
|
|
267
307
|
data: {
|
|
268
|
-
html:
|
|
308
|
+
html: mathMl,
|
|
269
309
|
},
|
|
270
310
|
};
|
|
271
311
|
}
|
|
@@ -298,35 +338,26 @@ export const serialization = {
|
|
|
298
338
|
},
|
|
299
339
|
serialize(object) {
|
|
300
340
|
if (object.type === 'math') {
|
|
301
|
-
const
|
|
341
|
+
const latex = object.data.get('latex');
|
|
302
342
|
const wrapper = object.data.get('wrapper');
|
|
303
|
-
|
|
304
|
-
|
|
343
|
+
const decoded = htmlDecode(arrowHandlingCase(latex));
|
|
344
|
+
|
|
345
|
+
log('[serialize] latex: ', latex);
|
|
305
346
|
|
|
306
347
|
if (MathPlugin.mathMlOptions.mmlOutput) {
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
const correctedLatex = fixLatexExpression(newLatex);
|
|
318
|
-
|
|
319
|
-
// As George requested in PD-3167, I will set the new mathML anyway, and also log differences
|
|
320
|
-
// if it doesn't we keep the latex version
|
|
321
|
-
console.log('This latex can not be safely converted to mathml so we will keep the latex version!!!', {
|
|
322
|
-
initialLatex: l,
|
|
323
|
-
newLatex: newLatex,
|
|
324
|
-
correctedLatex,
|
|
325
|
-
mathML: res,
|
|
326
|
-
});
|
|
348
|
+
const { mathMlFromLatex, conversionDidNotWork } = convertLatexToMathMl({ latex, decoded, wrapper });
|
|
349
|
+
|
|
350
|
+
if (conversionDidNotWork) {
|
|
351
|
+
// this means we could not convert latex to mathMl, so just return the latex version,
|
|
352
|
+
// same as we would do if mmlOutput would not be enabled
|
|
353
|
+
return (
|
|
354
|
+
<span data-latex="" data-raw={decoded}>
|
|
355
|
+
{wrapMath(decoded, wrapper)}
|
|
356
|
+
</span>
|
|
357
|
+
);
|
|
327
358
|
}
|
|
328
359
|
|
|
329
|
-
return <span data-type="mathml" dangerouslySetInnerHTML={{ __html:
|
|
360
|
+
return <span data-type="mathml" dangerouslySetInnerHTML={{ __html: mathMlFromLatex }} />;
|
|
330
361
|
}
|
|
331
362
|
|
|
332
363
|
return (
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import MediaPlugin from '../';
|
|
2
|
+
|
|
3
|
+
describe('media plugin', () => {
|
|
4
|
+
const imagePlugin = MediaPlugin('video');
|
|
5
|
+
|
|
6
|
+
describe('normalizeNode', () => {
|
|
7
|
+
it('should exit the function if the node is not of type document', () => {
|
|
8
|
+
const returnValue = imagePlugin.normalizeNode({ object: 'image' });
|
|
9
|
+
|
|
10
|
+
expect(returnValue).toEqual(undefined);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('should exit there are no changes needed', () => {
|
|
14
|
+
const nodes = [
|
|
15
|
+
{
|
|
16
|
+
object: 'text',
|
|
17
|
+
text: 'Before Media',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
type: 'video',
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
object: 'text',
|
|
24
|
+
text: 'After Media',
|
|
25
|
+
},
|
|
26
|
+
];
|
|
27
|
+
const returnValue = imagePlugin.normalizeNode({
|
|
28
|
+
object: 'document',
|
|
29
|
+
findDescendant: jest.fn((callback) => {
|
|
30
|
+
nodes.forEach((n) => callback(n));
|
|
31
|
+
}),
|
|
32
|
+
});
|
|
33
|
+
expect(returnValue).toEqual(undefined);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should return a function if there is a node with an empty text before a media element', () => {
|
|
37
|
+
const nodes = [
|
|
38
|
+
{
|
|
39
|
+
object: 'text',
|
|
40
|
+
text: '',
|
|
41
|
+
key: '1',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
type: 'video',
|
|
45
|
+
key: '2',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
object: 'text',
|
|
49
|
+
text: 'After Media',
|
|
50
|
+
key: '3',
|
|
51
|
+
},
|
|
52
|
+
];
|
|
53
|
+
const findDescendant = jest.fn((callback) => {
|
|
54
|
+
nodes.forEach((n) => callback(n));
|
|
55
|
+
});
|
|
56
|
+
const change = {
|
|
57
|
+
withoutNormalization: jest.fn((callback) => {
|
|
58
|
+
callback();
|
|
59
|
+
}),
|
|
60
|
+
insertTextByKey: jest.fn(),
|
|
61
|
+
};
|
|
62
|
+
const returnValue = imagePlugin.normalizeNode({
|
|
63
|
+
object: 'document',
|
|
64
|
+
findDescendant,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
expect(returnValue).toEqual(expect.any(Function));
|
|
68
|
+
|
|
69
|
+
returnValue(change);
|
|
70
|
+
|
|
71
|
+
expect(change.withoutNormalization).toHaveBeenCalledWith(expect.any(Function));
|
|
72
|
+
expect(change.insertTextByKey).toHaveBeenCalledWith('1', 0, ' ');
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
});
|
|
@@ -54,6 +54,7 @@ const types = ['audio', 'video'];
|
|
|
54
54
|
export default function MediaPlugin(type, opts) {
|
|
55
55
|
const toolbar = {
|
|
56
56
|
icon: type === 'audio' ? <VolumeUpIcon /> : <TheatersIcon />,
|
|
57
|
+
ariaLabel: type === 'audio' ? 'Insert audio' : 'Insert video',
|
|
57
58
|
onClick: (value, onChange) => {
|
|
58
59
|
log('[toolbar] onClick');
|
|
59
60
|
const inline = Inline.create({
|
|
@@ -162,7 +163,7 @@ export default function MediaPlugin(type, opts) {
|
|
|
162
163
|
if (tag === 'audio') {
|
|
163
164
|
return (
|
|
164
165
|
<MediaWrapper editor data-type={type} width={style.width} {...rest}>
|
|
165
|
-
<audio controls="controls">
|
|
166
|
+
<audio controls="controls" controlsList="nodownload">
|
|
166
167
|
<source type="audio/mp3" src={src} />
|
|
167
168
|
</audio>
|
|
168
169
|
<MediaToolbar hideEdit onRemove={handleDelete} />
|
|
@@ -303,7 +304,7 @@ export const serialization = {
|
|
|
303
304
|
|
|
304
305
|
if (tag === 'audio') {
|
|
305
306
|
return (
|
|
306
|
-
<audio controls="controls">
|
|
307
|
+
<audio controls="controls" controlsList="nodownload">
|
|
307
308
|
<source type="audio/mp3" src={src} />
|
|
308
309
|
</audio>
|
|
309
310
|
);
|