@wiris/mathtype-ckeditor5 7.26.0 → 7.27.2

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/src/plugin.js CHANGED
@@ -1,443 +1,419 @@
1
- /* globals window */
2
-
3
- // CKEditor imports
4
- import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
5
- import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
6
- import ClickObserver from '@ckeditor/ckeditor5-engine/src/view/observer/clickobserver';
7
- import HtmlDataProcessor from '@ckeditor/ckeditor5-engine/src/dataprocessor/htmldataprocessor';
8
- import XmlDataProcessor from '@ckeditor/ckeditor5-engine/src/dataprocessor/xmldataprocessor';
9
- import UpcastWriter from '@ckeditor/ckeditor5-engine/src/view/upcastwriter'
10
- import { toWidget, viewToModelPositionOutsideModelElement } from '@ckeditor/ckeditor5-widget/src/utils';
11
- import Widget from '@ckeditor/ckeditor5-widget/src/widget';
12
-
13
- // MathType API imports
14
- import { integrationModelProperties } from '@wiris/mathtype-html-integration-devkit/src/integrationmodel';
15
- import Core from '@wiris/mathtype-html-integration-devkit/src/core.src.js';
16
- import Parser from '@wiris/mathtype-html-integration-devkit/src/parser.js';
17
- import Util from '@wiris/mathtype-html-integration-devkit/src/util.js';
18
- import Image from '@wiris/mathtype-html-integration-devkit/src/image.js';
19
- import Configuration from '@wiris/mathtype-html-integration-devkit/src/configuration.js';
20
- import Listeners from '@wiris/mathtype-html-integration-devkit/src/listeners';
21
- import IntegrationModel from '@wiris/mathtype-html-integration-devkit/src/integrationmodel.js';
22
- import MathML from '@wiris/mathtype-html-integration-devkit/src/mathml.js';
23
- import Latex from '@wiris/mathtype-html-integration-devkit/src/latex';
24
-
25
- // Local imports
26
- import { MathTypeCommand, ChemTypeCommand } from './commands';
27
- import CKEditor5Integration from './integration';
28
-
29
- import mathIcon from '../theme/icons/formula.svg';
30
- import chemIcon from '../theme/icons/chem.svg';
31
-
32
- export var currentInstance = null;
33
-
34
- export default class MathType extends Plugin {
35
-
36
- static get requires() {
37
- return [ Widget ];
38
- }
39
-
40
- static get pluginName() {
41
- return 'MathType';
42
- }
43
-
44
- init() {
45
-
46
- // Create the MathType API Integration object
47
- const integration = this._addIntegration();
48
- currentInstance = integration
49
-
50
- // Add the MathType and ChemType commands to the editor
51
- this._addCommands();
52
-
53
- // Add the buttons for MathType and ChemType
54
- this._addViews( integration );
55
-
56
- // Registers the <mathml> element in the schema
57
- this._addSchema();
58
-
59
- // Add the downcast and upcast converters
60
- this._addConverters();
61
-
62
- // Expose the WirisPlugin variable to the window
63
- this._exposeWiris();
64
-
65
- }
66
-
67
- /**
68
- * Create the MathType API Integration object
69
- * @returns {CKEditor5Integration} the integration object
70
- */
71
- _addIntegration() {
72
-
73
- const editor = this.editor;
74
-
75
- /**
76
- * Integration model constructor attributes.
77
- * @type {integrationModelProperties}
78
- */
79
- const integrationProperties = {};
80
- integrationProperties.environment = {};
81
- integrationProperties.environment.editor = 'CKEditor5';
82
- integrationProperties.environment.editorVersion = '5' + '.x';
83
- integrationProperties.editorObject = editor;
84
- integrationProperties.serviceProviderProperties = {};
85
- integrationProperties.serviceProviderProperties.URI = 'https://www.wiris.net/demo/plugins/app';
86
- integrationProperties.serviceProviderProperties.server = 'java';
87
- integrationProperties.target = editor.sourceElement;
88
- integrationProperties.scriptName = 'bundle.js';
89
- integrationProperties.managesLanguage = true;
90
- // etc
91
-
92
- // There are platforms like Drupal that initialize CKEditor but they hide or remove the container element.
93
- // To avoid a wrong behaviour, this integration only starts if the workspace container exists.
94
- let integration;
95
- if ( integrationProperties.target ) {
96
- // Instance of the integration associated to this editor instance
97
- integration = new CKEditor5Integration( integrationProperties );
98
- integration.init();
99
- integration.listeners.fire( 'onTargetReady', {} );
100
-
101
- integration.checkElement();
102
-
103
- this.listenTo( editor.editing.view.document, 'click', ( evt, data ) => {
104
- // Is double click
105
- if ( data.domEvent.detail == 2 ) {
106
- integration.doubleClickHandler( data.domTarget, data.domEvent );
107
- evt.stop();
108
- }
109
- }, { priority: 'highest' } );
110
-
111
- }
112
-
113
- return integration;
114
-
115
- }
116
-
117
- /**
118
- * Add the MathType and ChemType commands to the editor
119
- */
120
- _addCommands() {
121
-
122
- const editor = this.editor;
123
-
124
- // Add command to open the formula editor
125
- editor.commands.add( 'MathType', new MathTypeCommand( editor ) );
126
-
127
- // Add command to open the chemistry formula editor
128
- editor.commands.add( 'ChemType', new ChemTypeCommand( editor ) );
129
-
130
- }
131
-
132
- /**
133
- * Add the buttons for MathType and ChemType
134
- * @param {CKEditor5Integration} integration the integration object
135
- */
136
- _addViews( integration ) {
137
-
138
- const editor = this.editor;
139
-
140
- // Add button for the formula editor
141
- editor.ui.componentFactory.add( 'MathType', locale => {
142
- const view = new ButtonView( locale );
143
-
144
- // View is enabled iff command is enabled
145
- view.bind( 'isEnabled' ).to( editor.commands.get( 'MathType' ), 'isEnabled' );
146
-
147
- view.set( {
148
- label: 'Insert a math equation - MathType',
149
- icon: mathIcon,
150
- tooltip: true
151
- } );
152
-
153
- // Callback executed once the image is clicked.
154
- view.on( 'execute', () => {
155
- editor.execute( 'MathType', {
156
- 'integration': integration, // Pass integration as parameter
157
- } );
158
- } );
159
-
160
- return view;
161
- } );
162
-
163
- // Add button for the chemistry formula editor
164
- editor.ui.componentFactory.add( 'ChemType', locale => {
165
- const view = new ButtonView( locale );
166
-
167
- // View is enabled iff command is enabled
168
- view.bind( 'isEnabled' ).to( editor.commands.get( 'ChemType' ), 'isEnabled' );
169
-
170
- view.set( {
171
- label: 'Insert a chemistry formula - ChemType',
172
- icon: chemIcon,
173
- tooltip: true
174
- } );
175
-
176
- // Callback executed once the image is clicked.
177
- view.on( 'execute', () => {
178
- editor.execute( 'ChemType', {
179
- 'integration': integration, // Pass integration as parameter
180
- } );
181
- } );
182
-
183
- return view;
184
- } );
185
-
186
- // Observer for the double click event
187
- editor.editing.view.addObserver( ClickObserver );
188
-
189
- }
190
-
191
- /**
192
- * Registers the <mathml> element in the schema
193
- */
194
- _addSchema() {
195
-
196
- const schema = this.editor.model.schema;
197
-
198
- schema.register( 'mathml', {
199
- allowWhere: '$text',
200
- isObject: true,
201
- isInline: true,
202
- allowAttributes: [ 'formula' ]
203
- } );
204
-
205
- }
206
-
207
- /**
208
- * Add the downcast and upcast converters
209
- */
210
- _addConverters() {
211
- const editor = this.editor;
212
-
213
- // Editing view -> Model
214
- editor.conversion.for( 'upcast' ).elementToElement( {
215
- view: {
216
- name: 'span',
217
- classes: 'ck-math-widget',
218
- },
219
- model: ( viewElement, { writer: modelWriter } ) => {
220
- const formula = MathML.safeXmlDecode( viewElement.getChild( 0 ).getAttribute( 'data-mathml' ) );
221
- return modelWriter.createElement( 'mathml', {
222
- formula: formula,
223
- } );
224
- }
225
- } );
226
-
227
- // Data view -> Model
228
- editor.data.upcastDispatcher.on( 'element:math', ( evt, data, conversionApi ) => {
229
- const { consumable, writer } = conversionApi;
230
- const viewItem = data.viewItem;
231
-
232
- // When element was already consumed then skip it.
233
- if ( !consumable.test( viewItem, { name: true } ) ) {
234
- return;
235
- }
236
-
237
- // If we encounter any <math> with a LaTeX annotation inside,
238
- // convert it into a "$$...$$" string.
239
- let isLatex = mathIsLatex( viewItem );
240
-
241
- // Get the formula of the <math> (which is all its children).
242
- const processor = new XmlDataProcessor( editor.editing.view.document );
243
-
244
- // Only god knows why the following line makes viewItem lose all of its children,
245
- // so we obtain isLatex before doing this because we need viewItem's children for that.
246
- const upcastWriter = new UpcastWriter( editor.editing.view.document );
247
- const viewDocumentFragment = upcastWriter.createDocumentFragment( viewItem.getChildren() );
248
-
249
- // and obtain the attributes of <math> too!
250
- const mathAttributes = [ ...viewItem.getAttributes() ]
251
- .map( ( [ key, value ] ) => ` ${ key }="${ value }"` )
252
- .join( '' );
253
-
254
- // We process the document fragment
255
- let formula = processor.toData( viewDocumentFragment ) || '';
256
-
257
- // And obtain the complete formula
258
- formula = `<math${ mathAttributes }>${ formula }</math>`;
259
-
260
- /* Model node that contains what's going to actually be inserted. This can be either:
261
- - A <mathml> element with a formula attribute set to the given formula, or
262
- - If the original <math> had a LaTeX annotation, then the annotation surrounded by "$$...$$" */
263
- const modelNode = isLatex
264
- ? writer.createText( Parser.initParse( formula, editor.config.get( 'language' ) ) )
265
- : writer.createElement( 'mathml', { formula } );
266
- modelNode.data = formula;
267
-
268
- // Find allowed parent for element that we are going to insert.
269
- // If current parent does not allow to insert element but one of the ancestors does
270
- // then split nodes to allowed parent.
271
- const splitResult = conversionApi.splitToAllowedParent( modelNode, data.modelCursor );
272
-
273
- // When there is no split result it means that we can't insert element to model tree, so let's skip it.
274
- if ( !splitResult ) {
275
- return;
276
- }
277
-
278
- // Insert element on allowed position.
279
- conversionApi.writer.insert( modelNode, splitResult.position );
280
-
281
- // Consume appropriate value from consumable values list.
282
- consumable.consume( viewItem, { name: true } );
283
-
284
- const parts = conversionApi.getSplitParts( modelNode );
285
-
286
- // Set conversion result range.
287
- data.modelRange = writer.createRange(
288
- conversionApi.writer.createPositionBefore( modelNode ),
289
- conversionApi.writer.createPositionAfter( parts[ parts.length - 1 ] )
290
- );
291
-
292
- // Now we need to check where the `modelCursor` should be.
293
- if ( splitResult.cursorParent ) {
294
- // If we split parent to insert our element then we want to continue conversion in the new part of the split parent.
295
- //
296
- // before: <allowed><notAllowed>foo[]</notAllowed></allowed>
297
- // after: <allowed><notAllowed>foo</notAllowed><converted></converted><notAllowed>[]</notAllowed></allowed>
298
-
299
- data.modelCursor = conversionApi.writer.createPositionAt( splitResult.cursorParent, 0 );
300
- } else {
301
- // Otherwise just continue after inserted element.
302
- data.modelCursor = data.modelRange.end;
303
- }
304
- } );
305
-
306
- /**
307
- * Whether the given view <math> element has a LaTeX annotation element.
308
- * @param {*} math
309
- * @returns {bool}
310
- */
311
- function mathIsLatex( math ) {
312
- const semantics = math.getChild( 0 );
313
- if ( !semantics || semantics.name !== 'semantics' ) return false;
314
- for ( const child of semantics.getChildren() ) {
315
- if ( child.name === 'annotation' && child.getAttribute( 'encoding' ) === 'LaTeX' ) {
316
- return true;
317
- }
318
- }
319
- return false;
320
- }
321
-
322
- // Model -> Editing view
323
- editor.conversion.for( 'editingDowncast' ).elementToElement( {
324
- model: 'mathml',
325
- view: createViewWidget
326
- } );
327
-
328
- // Model -> Data view
329
- editor.conversion.for( 'dataDowncast' ).elementToElement( {
330
- model: 'mathml',
331
- view: createDataString
332
- } );
333
-
334
- /**
335
- * Makes a copy of the given view node.
336
- * @param {module:engine/view/node~Node} sourceNode Node to copy.
337
- * @returns {module:engine/view/node~Node} Copy of the node.
338
- */
339
- function clone( viewWriter, sourceNode ) {
340
- if ( sourceNode.is( 'text' ) ) {
341
- return viewWriter.createText( sourceNode.data );
342
- } else if ( sourceNode.is( 'element' ) ) {
343
-
344
- if ( sourceNode.is( 'emptyElement' ) ) {
345
- return viewWriter.createEmptyElement( sourceNode.name, sourceNode.getAttributes() );
346
- } else {
347
- const element = viewWriter.createContainerElement( sourceNode.name, sourceNode.getAttributes() );
348
- for ( const child of sourceNode.getChildren() ) {
349
- viewWriter.insert( viewWriter.createPositionAt( element, 'end' ) , clone( viewWriter, child ) );
350
- }
351
- return element;
352
- }
353
-
354
- }
355
-
356
- throw new Exception('Given node has unsupported type.');
357
-
358
- }
359
-
360
- function createDataString( modelItem, { writer: viewWriter } ) {
361
-
362
- const htmlDataProcessor = new HtmlDataProcessor( viewWriter.document );
363
-
364
- let mathString = Parser.endParseSaveMode( modelItem.getAttribute( 'formula' ) );
365
-
366
- // Remove the traces from Hand. This code should be removed once PLUGINS-1307 is solved.
367
- if ( !Configuration.get( 'saveHandTraces' ) ) {
368
- mathString = MathML.removeAnnotation( mathString, 'application/json' );
369
- }
370
-
371
- const sourceMathElement = htmlDataProcessor.toView( mathString ).getChild( 0 );
372
-
373
- return clone( viewWriter, sourceMathElement );
374
-
375
- }
376
-
377
- function createViewWidget( modelItem, { writer: viewWriter } ) {
378
- const widgetElement = viewWriter.createContainerElement( 'span', {
379
- class: 'ck-math-widget'
380
- } );
381
-
382
- const mathUIElement = createViewImage( modelItem, { writer: viewWriter } );
383
-
384
- viewWriter.insert( viewWriter.createPositionAt( widgetElement, 0 ), mathUIElement );
385
-
386
- return toWidget( widgetElement, viewWriter );
387
- }
388
-
389
- function createViewImage( modelItem, { writer: viewWriter } ) {
390
-
391
- const htmlDataProcessor = new HtmlDataProcessor( viewWriter.document );
392
-
393
- const mathString = modelItem.getAttribute( 'formula' );
394
- const imgHtml = Parser.initParse( mathString, editor.config.get( 'language' ) );
395
- const imgElement = htmlDataProcessor.toView( imgHtml ).getChild( 0 );
396
-
397
- /* Although we use the HtmlDataProcessor to obtain the attributes,
398
- we must create a new EmptyElement which is independent of the
399
- DataProcessor being used by this editor instance */
400
- return viewWriter.createEmptyElement( 'img', imgElement.getAttributes() );
401
-
402
- }
403
-
404
- // This stops the view selection getting into the <span>s and messing up caret movement
405
- editor.editing.mapper.on(
406
- 'viewToModelPosition',
407
- viewToModelPositionOutsideModelElement( editor.model, viewElement => viewElement.hasClass( 'ck-math-widget' ) )
408
- );
409
-
410
- // Keep a reference to the original get function
411
- const get = editor.data.get;
412
-
413
- /**
414
- * Hack to transform $$latex$$ into <math> in editor.getData()'s output.
415
- */
416
- editor.data.get = ( options ) => {
417
- const output = get.bind( editor.data )( options );
418
- return Parser.endParse( output );
419
- };
420
-
421
- }
422
-
423
- /**
424
- * Expose the WirisPlugin variable to the window
425
- */
426
- _exposeWiris() {
427
-
428
- window.WirisPlugin = {
429
- Core: Core,
430
- Parser: Parser,
431
- Image: Image,
432
- MathML: MathML,
433
- Util: Util,
434
- Configuration: Configuration,
435
- Listeners: Listeners,
436
- IntegrationModel: IntegrationModel,
437
- currentInstance: currentInstance,
438
- Latex: Latex
439
- }
440
-
441
- }
442
-
443
- }
1
+ // CKEditor imports
2
+ import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
3
+ import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
4
+ import ClickObserver from '@ckeditor/ckeditor5-engine/src/view/observer/clickobserver';
5
+ import HtmlDataProcessor from '@ckeditor/ckeditor5-engine/src/dataprocessor/htmldataprocessor';
6
+ import XmlDataProcessor from '@ckeditor/ckeditor5-engine/src/dataprocessor/xmldataprocessor';
7
+ import UpcastWriter from '@ckeditor/ckeditor5-engine/src/view/upcastwriter';
8
+ import { toWidget, viewToModelPositionOutsideModelElement } from '@ckeditor/ckeditor5-widget/src/utils';
9
+ import Widget from '@ckeditor/ckeditor5-widget/src/widget';
10
+
11
+ // MathType API imports
12
+ import IntegrationModel from '@wiris/mathtype-html-integration-devkit/src/integrationmodel';
13
+ import Core from '@wiris/mathtype-html-integration-devkit/src/core.src';
14
+ import Parser from '@wiris/mathtype-html-integration-devkit/src/parser';
15
+ import Util from '@wiris/mathtype-html-integration-devkit/src/util';
16
+ import Image from '@wiris/mathtype-html-integration-devkit/src/image';
17
+ import Configuration from '@wiris/mathtype-html-integration-devkit/src/configuration';
18
+ import Listeners from '@wiris/mathtype-html-integration-devkit/src/listeners';
19
+ import MathML from '@wiris/mathtype-html-integration-devkit/src/mathml';
20
+ import Latex from '@wiris/mathtype-html-integration-devkit/src/latex';
21
+
22
+ // Local imports
23
+ import { MathTypeCommand, ChemTypeCommand } from './commands';
24
+ import CKEditor5Integration from './integration';
25
+
26
+ import mathIcon from '../theme/icons/formula.svg';
27
+ import chemIcon from '../theme/icons/chem.svg';
28
+
29
+ export let currentInstance = null; // eslint-disable-line import/no-mutable-exports
30
+
31
+ export default class MathType extends Plugin {
32
+ static get requires() {
33
+ return [Widget];
34
+ }
35
+
36
+ static get pluginName() {
37
+ return 'MathType';
38
+ }
39
+
40
+ init() {
41
+ // Create the MathType API Integration object
42
+ const integration = this._addIntegration();
43
+ currentInstance = integration;
44
+
45
+ // Add the MathType and ChemType commands to the editor
46
+ this._addCommands();
47
+
48
+ // Add the buttons for MathType and ChemType
49
+ this._addViews(integration);
50
+
51
+ // Registers the <mathml> element in the schema
52
+ this._addSchema();
53
+
54
+ // Add the downcast and upcast converters
55
+ this._addConverters();
56
+
57
+ // Expose the WirisPlugin variable to the window
58
+ this._exposeWiris();
59
+ }
60
+
61
+ /**
62
+ * Create the MathType API Integration object
63
+ * @returns {CKEditor5Integration} the integration object
64
+ */
65
+ _addIntegration() {
66
+ const { editor } = this;
67
+
68
+ /**
69
+ * Integration model constructor attributes.
70
+ * @type {integrationModelProperties}
71
+ */
72
+ const integrationProperties = {};
73
+ integrationProperties.environment = {};
74
+ integrationProperties.environment.editor = 'CKEditor5';
75
+ integrationProperties.environment.editorVersion = '5.x';
76
+ integrationProperties.editorObject = editor;
77
+ integrationProperties.serviceProviderProperties = {};
78
+ integrationProperties.serviceProviderProperties.URI = 'https://www.wiris.net/demo/plugins/app';
79
+ integrationProperties.serviceProviderProperties.server = 'java';
80
+ integrationProperties.target = editor.sourceElement;
81
+ integrationProperties.scriptName = 'bundle.js';
82
+ integrationProperties.managesLanguage = true;
83
+ // etc
84
+
85
+ // There are platforms like Drupal that initialize CKEditor but they hide or remove the container element.
86
+ // To avoid a wrong behaviour, this integration only starts if the workspace container exists.
87
+ let integration;
88
+ if (integrationProperties.target) {
89
+ // Instance of the integration associated to this editor instance
90
+ integration = new CKEditor5Integration(integrationProperties);
91
+ integration.init();
92
+ integration.listeners.fire('onTargetReady', {});
93
+
94
+ integration.checkElement();
95
+
96
+ this.listenTo(editor.editing.view.document, 'click', (evt, data) => {
97
+ // Is double click
98
+ if (data.domEvent.detail === 2) {
99
+ integration.doubleClickHandler(data.domTarget, data.domEvent);
100
+ evt.stop();
101
+ }
102
+ }, { priority: 'highest' });
103
+ }
104
+
105
+ return integration;
106
+ }
107
+
108
+ /**
109
+ * Add the MathType and ChemType commands to the editor
110
+ */
111
+ _addCommands() {
112
+ const { editor } = this;
113
+
114
+ // Add command to open the formula editor
115
+ editor.commands.add('MathType', new MathTypeCommand(editor));
116
+
117
+ // Add command to open the chemistry formula editor
118
+ editor.commands.add('ChemType', new ChemTypeCommand(editor));
119
+ }
120
+
121
+ /**
122
+ * Add the buttons for MathType and ChemType
123
+ * @param {CKEditor5Integration} integration the integration object
124
+ */
125
+ _addViews(integration) {
126
+ const { editor } = this;
127
+
128
+ // Add button for the formula editor
129
+ editor.ui.componentFactory.add('MathType', (locale) => {
130
+ const view = new ButtonView(locale);
131
+
132
+ // View is enabled iff command is enabled
133
+ view.bind('isEnabled').to(editor.commands.get('MathType'), 'isEnabled');
134
+
135
+ view.set({
136
+ label: 'Insert a math equation - MathType',
137
+ icon: mathIcon,
138
+ tooltip: true,
139
+ });
140
+
141
+ // Callback executed once the image is clicked.
142
+ view.on('execute', () => {
143
+ editor.execute('MathType', {
144
+ integration, // Pass integration as parameter
145
+ });
146
+ });
147
+
148
+ return view;
149
+ });
150
+
151
+ // Add button for the chemistry formula editor
152
+ editor.ui.componentFactory.add('ChemType', (locale) => {
153
+ const view = new ButtonView(locale);
154
+
155
+ // View is enabled iff command is enabled
156
+ view.bind('isEnabled').to(editor.commands.get('ChemType'), 'isEnabled');
157
+
158
+ view.set({
159
+ label: 'Insert a chemistry formula - ChemType',
160
+ icon: chemIcon,
161
+ tooltip: true,
162
+ });
163
+
164
+ // Callback executed once the image is clicked.
165
+ view.on('execute', () => {
166
+ editor.execute('ChemType', {
167
+ integration, // Pass integration as parameter
168
+ });
169
+ });
170
+
171
+ return view;
172
+ });
173
+
174
+ // Observer for the double click event
175
+ editor.editing.view.addObserver(ClickObserver);
176
+ }
177
+
178
+ /**
179
+ * Registers the <mathml> element in the schema
180
+ */
181
+ _addSchema() {
182
+ const { schema } = this.editor.model;
183
+
184
+ schema.register('mathml', {
185
+ allowWhere: '$text',
186
+ isObject: true,
187
+ isInline: true,
188
+ allowAttributes: ['formula'],
189
+ });
190
+ }
191
+
192
+ /**
193
+ * Add the downcast and upcast converters
194
+ */
195
+ _addConverters() {
196
+ const { editor } = this;
197
+
198
+ // Editing view -> Model
199
+ editor.conversion.for('upcast').elementToElement({
200
+ view: {
201
+ name: 'span',
202
+ classes: 'ck-math-widget',
203
+ },
204
+ model: (viewElement, { writer: modelWriter }) => {
205
+ const formula = MathML.safeXmlDecode(viewElement.getChild(0).getAttribute('data-mathml'));
206
+ return modelWriter.createElement('mathml', {
207
+ formula,
208
+ });
209
+ },
210
+ });
211
+
212
+ // Data view -> Model
213
+ editor.data.upcastDispatcher.on('element:math', (evt, data, conversionApi) => {
214
+ const { consumable, writer } = conversionApi;
215
+ const { viewItem } = data;
216
+
217
+ // When element was already consumed then skip it.
218
+ if (!consumable.test(viewItem, { name: true })) {
219
+ return;
220
+ }
221
+
222
+ // If we encounter any <math> with a LaTeX annotation inside,
223
+ // convert it into a "$$...$$" string.
224
+ const isLatex = mathIsLatex(viewItem); // eslint-disable-line no-use-before-define
225
+
226
+ // Get the formula of the <math> (which is all its children).
227
+ const processor = new XmlDataProcessor(editor.editing.view.document);
228
+
229
+ // Only god knows why the following line makes viewItem lose all of its children,
230
+ // so we obtain isLatex before doing this because we need viewItem's children for that.
231
+ const upcastWriter = new UpcastWriter(editor.editing.view.document);
232
+ const viewDocumentFragment = upcastWriter.createDocumentFragment(viewItem.getChildren());
233
+
234
+ // and obtain the attributes of <math> too!
235
+ const mathAttributes = [...viewItem.getAttributes()]
236
+ .map(([key, value]) => ` ${key}="${value}"`)
237
+ .join('');
238
+
239
+ // We process the document fragment
240
+ let formula = processor.toData(viewDocumentFragment) || '';
241
+
242
+ // And obtain the complete formula
243
+ formula = `<math${mathAttributes}>${formula}</math>`;
244
+
245
+ /* Model node that contains what's going to actually be inserted. This can be either:
246
+ - A <mathml> element with a formula attribute set to the given formula, or
247
+ - If the original <math> had a LaTeX annotation, then the annotation surrounded by "$$...$$" */
248
+ const modelNode = isLatex
249
+ ? writer.createText(Parser.initParse(formula, editor.config.get('language')))
250
+ : writer.createElement('mathml', { formula });
251
+ modelNode.data = formula;
252
+
253
+ // Find allowed parent for element that we are going to insert.
254
+ // If current parent does not allow to insert element but one of the ancestors does
255
+ // then split nodes to allowed parent.
256
+ const splitResult = conversionApi.splitToAllowedParent(modelNode, data.modelCursor);
257
+
258
+ // When there is no split result it means that we can't insert element to model tree, so let's skip it.
259
+ if (!splitResult) {
260
+ return;
261
+ }
262
+
263
+ // Insert element on allowed position.
264
+ conversionApi.writer.insert(modelNode, splitResult.position);
265
+
266
+ // Consume appropriate value from consumable values list.
267
+ consumable.consume(viewItem, { name: true });
268
+
269
+ const parts = conversionApi.getSplitParts(modelNode);
270
+
271
+ // Set conversion result range.
272
+ data.modelRange = writer.createRange(
273
+ conversionApi.writer.createPositionBefore(modelNode),
274
+ conversionApi.writer.createPositionAfter(parts[parts.length - 1]),
275
+ );
276
+
277
+ // Now we need to check where the `modelCursor` should be.
278
+ if (splitResult.cursorParent) {
279
+ // If we split parent to insert our element then we want to continue conversion in the new part of the split parent.
280
+ //
281
+ // before: <allowed><notAllowed>foo[]</notAllowed></allowed>
282
+ // after: <allowed><notAllowed>foo</notAllowed><converted></converted><notAllowed>[]</notAllowed></allowed>
283
+
284
+ data.modelCursor = conversionApi.writer.createPositionAt(splitResult.cursorParent, 0);
285
+ } else {
286
+ // Otherwise just continue after inserted element.
287
+ data.modelCursor = data.modelRange.end;
288
+ }
289
+ });
290
+
291
+ /**
292
+ * Whether the given view <math> element has a LaTeX annotation element.
293
+ * @param {*} math
294
+ * @returns {bool}
295
+ */
296
+ function mathIsLatex(math) {
297
+ const semantics = math.getChild(0);
298
+ if (!semantics || semantics.name !== 'semantics') return false;
299
+ for (const child of semantics.getChildren()) {
300
+ if (child.name === 'annotation' && child.getAttribute('encoding') === 'LaTeX') {
301
+ return true;
302
+ }
303
+ }
304
+ return false;
305
+ }
306
+
307
+ function createViewWidget(modelItem, { writer: viewWriter }) {
308
+ const widgetElement = viewWriter.createContainerElement('span', {
309
+ class: 'ck-math-widget',
310
+ });
311
+
312
+ const mathUIElement = createViewImage(modelItem, { writer: viewWriter }); // eslint-disable-line no-use-before-define
313
+
314
+ viewWriter.insert(viewWriter.createPositionAt(widgetElement, 0), mathUIElement);
315
+
316
+ return toWidget(widgetElement, viewWriter);
317
+ }
318
+
319
+ function createViewImage(modelItem, { writer: viewWriter }) {
320
+ const htmlDataProcessor = new HtmlDataProcessor(viewWriter.document);
321
+
322
+ const mathString = modelItem.getAttribute('formula');
323
+ const imgHtml = Parser.initParse(mathString, editor.config.get('language'));
324
+ const imgElement = htmlDataProcessor.toView(imgHtml).getChild(0);
325
+
326
+ /* Although we use the HtmlDataProcessor to obtain the attributes,
327
+ we must create a new EmptyElement which is independent of the
328
+ DataProcessor being used by this editor instance */
329
+ return viewWriter.createEmptyElement('img', imgElement.getAttributes(), {
330
+ renderUnsafeAttributes: [ 'src' ]
331
+ } );
332
+ }
333
+
334
+ // Model -> Editing view
335
+ editor.conversion.for('editingDowncast').elementToElement({
336
+ model: 'mathml',
337
+ view: createViewWidget,
338
+ });
339
+
340
+ // Model -> Data view
341
+ editor.conversion.for('dataDowncast').elementToElement({
342
+ model: 'mathml',
343
+ view: createDataString, // eslint-disable-line no-use-before-define
344
+ });
345
+
346
+ /**
347
+ * Makes a copy of the given view node.
348
+ * @param {module:engine/view/node~Node} sourceNode Node to copy.
349
+ * @returns {module:engine/view/node~Node} Copy of the node.
350
+ */
351
+ function clone(viewWriter, sourceNode) {
352
+ if (sourceNode.is('text')) {
353
+ return viewWriter.createText(sourceNode.data);
354
+ } if (sourceNode.is('element')) {
355
+ if (sourceNode.is('emptyElement')) {
356
+ return viewWriter.createEmptyElement(sourceNode.name, sourceNode.getAttributes());
357
+ }
358
+ const element = viewWriter.createContainerElement(sourceNode.name, sourceNode.getAttributes());
359
+ for (const child of sourceNode.getChildren()) {
360
+ viewWriter.insert(viewWriter.createPositionAt(element, 'end'), clone(viewWriter, child));
361
+ }
362
+ return element;
363
+ }
364
+
365
+ throw new Exception('Given node has unsupported type.'); // eslint-disable-line no-undef
366
+ }
367
+
368
+ function createDataString(modelItem, { writer: viewWriter }) {
369
+ const htmlDataProcessor = new HtmlDataProcessor(viewWriter.document);
370
+
371
+ let mathString = Parser.endParseSaveMode(modelItem.getAttribute('formula'));
372
+
373
+ // Remove the traces from Hand. This code should be removed once PLUGINS-1307 is solved.
374
+ if (!Configuration.get('saveHandTraces')) {
375
+ mathString = MathML.removeAnnotation(mathString, 'application/json');
376
+ }
377
+
378
+ const sourceMathElement = htmlDataProcessor.toView(mathString).getChild(0);
379
+
380
+ return clone(viewWriter, sourceMathElement);
381
+ }
382
+
383
+ // This stops the view selection getting into the <span>s and messing up caret movement
384
+ editor.editing.mapper.on(
385
+ 'viewToModelPosition',
386
+ viewToModelPositionOutsideModelElement(editor.model, (viewElement) => viewElement.hasClass('ck-math-widget')),
387
+ );
388
+
389
+ // Keep a reference to the original get function
390
+ const { get } = editor.data;
391
+
392
+ /**
393
+ * Hack to transform $$latex$$ into <math> in editor.getData()'s output.
394
+ */
395
+ editor.data.get = (options) => {
396
+ const output = get.bind(editor.data)(options);
397
+ return Parser.endParse(output);
398
+ };
399
+ }
400
+
401
+ /**
402
+ * Expose the WirisPlugin variable to the window
403
+ */
404
+ // eslint-disable-next-line class-methods-use-this
405
+ _exposeWiris() {
406
+ window.WirisPlugin = {
407
+ Core,
408
+ Parser,
409
+ Image,
410
+ MathML,
411
+ Util,
412
+ Configuration,
413
+ Listeners,
414
+ IntegrationModel,
415
+ currentInstance,
416
+ Latex,
417
+ };
418
+ }
419
+ }