@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.
@@ -1,294 +1,268 @@
1
- import Position from '@ckeditor/ckeditor5-engine/src/model/position';
2
-
3
- import IntegrationModel, { integrationModelProperties } from '@wiris/mathtype-html-integration-devkit/src/integrationmodel';
4
- import Util from '@wiris/mathtype-html-integration-devkit/src/util';
5
- import Configuration from '@wiris/mathtype-html-integration-devkit/src/configuration';
6
- import Latex from '@wiris/mathtype-html-integration-devkit/src/latex';
7
-
8
- /**
9
- * This class represents the MathType integration for CKEditor5.
10
- * @extends {IntegrationModel}
11
- */
12
- export default class CKEditor5Integration extends IntegrationModel {
13
-
14
- constructor( ckeditorIntegrationModelProperties ) {
15
- const editor = ckeditorIntegrationModelProperties.editorObject;
16
-
17
- if ( typeof editor.config != 'undefined' && typeof editor.config.get('mathTypeParameters') != 'undefined') {
18
- ckeditorIntegrationModelProperties.integrationParameters = editor.config.get('mathTypeParameters');
19
- }
20
- /**
21
- * CKEditor5 Integration.
22
- *
23
- * @param {integrationModelProperties} integrationModelAttributes
24
- */
25
- super( ckeditorIntegrationModelProperties );
26
-
27
- /**
28
- * Folder name used for the integration inside CKEditor plugins folder.
29
- */
30
- this.integrationFolderName = 'ckeditor_wiris';
31
- }
32
-
33
-
34
- /**
35
- * @inheritdoc
36
- * @returns {string} - The CKEditor instance language.
37
- * @override
38
- */
39
- getLanguage() {
40
- // Returns the CKEDitor instance language taking into account that the language can be an object.
41
- try {
42
- return this.editorParameters.language;
43
- } catch (e) {}
44
- const languageObject = this.editorObject.config.get( 'language' );
45
- if (languageObject != null) {
46
- if (typeof (languageObject) === 'object') {
47
- // eslint-disable-next-line no-prototype-builtins
48
- if (languageObject.hasOwnProperty('ui')) {
49
- return languageObject.ui;
50
- } else return super.getLanguage();
51
- }
52
- return languageObject;
53
- } else {
54
- return super.getLanguage();
55
- }
56
- }
57
-
58
- /**
59
- * Adds callbacks to the following CKEditor listeners:
60
- * - 'focus' - updates the current instance.
61
- * - 'contentDom' - adds 'doubleclick' callback.
62
- * - 'doubleclick' - sets to null data.dialog property to avoid modifications for MathType formulas.
63
- * - 'setData' - parses the data converting MathML into images.
64
- * - 'afterSetData' - adds an observer to MathType formulas to avoid modifications.
65
- * - 'getData' - parses the data converting images into selected save mode (MathML by default).
66
- * - 'mode' - recalculates the active element.
67
- */
68
- addEditorListeners() {
69
- const editor = this.editorObject;
70
-
71
- if ( typeof editor.config.wirislistenersdisabled == 'undefined' ||
72
- !editor.config.wirislistenersdisabled ) {
73
-
74
- this.checkElement();
75
-
76
- }
77
- }
78
-
79
- /**
80
- * Checks the current container and assign events in case that it doesn't have them.
81
- * CKEditor replaces several times the element element during its execution,
82
- * so we must assign the events again to editor element.
83
- */
84
- checkElement() {
85
-
86
- const editor = this.editorObject;
87
- const newElement = editor.sourceElement;
88
-
89
- // If the element wasn't treated, add the events.
90
- if ( !newElement.wirisActive ) {
91
- this.setTarget(newElement);
92
- this.addEvents();
93
- // Set the element as treated
94
- newElement.wirisActive = true;
95
- }
96
- }
97
-
98
- /**
99
- * @inheritdoc
100
- * @param {HTMLElement} element - HTMLElement target.
101
- * @param {MouseEvent} event - event which trigger the handler.
102
- */
103
- doubleClickHandler(element, event) {
104
- if (element.nodeName.toLowerCase() == 'img') {
105
- if (Util.containsClass(element, Configuration.get('imageClassName'))) {
106
- // Some plugins (image2, image) open a dialog on double click. On formulas
107
- // doubleclick event ends here.
108
- if (typeof event.stopPropagation != 'undefined') { // old I.E compatibility.
109
- event.stopPropagation();
110
- } else {
111
- event.returnValue = false;
112
- }
113
- this.core.getCustomEditors().disable();
114
- const customEditorAttr = element.getAttribute(Configuration.get('imageCustomEditorName'));
115
- if (customEditorAttr) {
116
- this.core.getCustomEditors().enable(customEditorAttr);
117
- }
118
- this.core.editionProperties.temporalImage = element;
119
- this.openExistingFormulaEditor();
120
- }
121
- }
122
- }
123
-
124
-
125
- /** @inheritdoc */
126
- getCorePath() {
127
- return null; // TODO
128
- }
129
-
130
- /** @inheritdoc */
131
- callbackFunction() {
132
- super.callbackFunction();
133
- this.addEditorListeners();
134
- }
135
-
136
- openNewFormulaEditor() {
137
-
138
- // Store the editor selection as it will be lost upon opening the modal
139
- this.core.editionProperties.selection = this.editorObject.editing.view.document.selection;
140
-
141
- return super.openNewFormulaEditor();
142
-
143
- }
144
-
145
- /**
146
- * Replaces old formula with new MathML or inserts it in caret position if new
147
- * @param {String} mathml MathML to update old one or insert
148
- * @returns {module:engine/model/element~Element} The model element corresponding to the inserted image
149
- */
150
- insertMathml( mathml ) {
151
- // This returns the value returned by the callback function (writer => {...})
152
- return this.editorObject.model.change( writer => {
153
-
154
- const core = this.getCore();
155
-
156
- const modelElementNew = writer.createElement( 'mathml', { formula: mathml } );
157
- modelElementNew.data = mathml;
158
-
159
- // Obtain the DOM <span><img ... /></span> object corresponding to the formula
160
- if ( core.editionProperties.isNewElement ) {
161
-
162
- // Don't bother inserting anything at all if the MathML is empty.
163
- if ( !mathml ) return;
164
-
165
- let viewSelection = this.core.editionProperties.selection || this.editorObject.editing.view.document.selection;
166
- let modelPosition = this.editorObject.editing.mapper.toModelPosition( viewSelection.getLastPosition() );
167
-
168
- writer.insert( modelElementNew, modelPosition );
169
-
170
- // Remove selection
171
- if ( !viewSelection.isCollapsed ) {
172
-
173
- for ( const range of viewSelection.getRanges() ) {
174
- writer.remove( this.editorObject.editing.mapper.toModelRange( range ) );
175
- }
176
-
177
- }
178
-
179
- } else {
180
-
181
- const img = core.editionProperties.temporalImage;
182
- const viewElement = this.editorObject.editing.view.domConverter.domToView( img ).parent;
183
- const modelElementOld = this.editorObject.editing.mapper.toModelElement( viewElement );
184
-
185
- // Insert the new <mathml> and remove the old one
186
- const position = this.editorObject.model.createPositionBefore( modelElementOld );
187
-
188
- // If the given MathML is empty, don't insert a new formula.
189
- if ( mathml ) {
190
- writer.insert( modelElementNew, position );
191
- }
192
- writer.remove( modelElementOld );
193
-
194
- }
195
-
196
- return modelElementNew;
197
-
198
- } );
199
- }
200
-
201
- /**
202
- * Finds the text node corresponding to given DOM text element.
203
- * @param {element} viewElement Element to find corresponding text node of.
204
- * @returns {module:engine/model/text~Text|undefined} Text node corresponding to the given element or undefined if it doesn't exist.
205
- */
206
- findText( viewElement ) {
207
-
208
- // mapper always converts text nodes to *new* model elements so we need to convert the text's parents and then come back down
209
- let pivot = viewElement;
210
- let element;
211
- while ( !element ) {
212
- element =
213
- this.editorObject.editing.mapper.toModelElement(
214
- this.editorObject.editing.view.domConverter.domToView(
215
- pivot
216
- )
217
- );
218
- pivot = pivot.parentElement;
219
- }
220
-
221
- // Navigate through all the subtree under `pivot` in order to find the correct text node
222
- const range = this.editorObject.model.createRangeIn( element );
223
- const descendants = Array.from( range.getItems() );
224
- for ( const node of descendants ) {
225
- if ( node.is( 'textProxy' ) && node.data == viewElement.data.replace( String.fromCharCode( 160 ), " " ) ) {
226
- return node.textNode;
227
- }
228
- }
229
-
230
- }
231
-
232
- /** @inheritdoc */
233
- insertFormula( focusElement, windowTarget, mathml, wirisProperties ) {
234
-
235
- let returnObject = {};
236
-
237
- if ( !mathml ) {
238
- this.insertMathml( '' );
239
- } else if ( this.core.editMode == 'latex' ) {
240
-
241
- returnObject.latex = Latex.getLatexFromMathML( mathml );
242
- returnObject.node = windowTarget.document.createTextNode( '$$' + returnObject.latex + '$$' );
243
-
244
- this.editorObject.model.change( writer => {
245
-
246
- const latexRange = this.core.editionProperties.latexRange;
247
-
248
- const startNode = this.findText( latexRange.startContainer );
249
- const endNode = this.findText( latexRange.endContainer );
250
-
251
- const startPosition = writer.createPositionAt( startNode.parent, startNode.startOffset + latexRange.startOffset );
252
- const endPosition = writer.createPositionAt( endNode.parent, endNode.startOffset + latexRange.endOffset );
253
-
254
- const range = writer.createRange(
255
- startPosition,
256
- endPosition,
257
- );
258
-
259
- writer.remove( range );
260
- writer.insertText( '$$' + returnObject.latex + '$$', startNode.getAttributes(), startPosition );
261
-
262
- } );
263
-
264
- } else {
265
- try {
266
- returnObject.node = this.editorObject.editing.view.domConverter.viewToDom(
267
- this.editorObject.editing.mapper.toViewElement(
268
- this.insertMathml( mathml )
269
- ), windowTarget.document
270
- );
271
- } catch ( e ) {
272
- const x = e.toString();
273
- if( ( x ).includes( "CKEditorError: Cannot read property 'parent' of undefined" ) ) {
274
- this.core.modalDialog.cancelAction();
275
- }
276
- }
277
- }
278
-
279
- /* Due to PLUGINS-1329, we add the onChange event to the CK4 insertFormula.
280
- We probably should add it here as well, but we should look further into how */
281
- // this.editorObject.fire('change');
282
-
283
- return returnObject;
284
-
285
- }
286
-
287
- /**
288
- * Function called when the content submits an action.
289
- */
290
- notifyWindowClosed() {
291
- this.editorObject.editing.view.focus();
292
- }
293
-
294
- }
1
+ import IntegrationModel from '@wiris/mathtype-html-integration-devkit/src/integrationmodel';
2
+ import Util from '@wiris/mathtype-html-integration-devkit/src/util';
3
+ import Configuration from '@wiris/mathtype-html-integration-devkit/src/configuration';
4
+ import Latex from '@wiris/mathtype-html-integration-devkit/src/latex';
5
+
6
+ /**
7
+ * This class represents the MathType integration for CKEditor5.
8
+ * @extends {IntegrationModel}
9
+ */
10
+ export default class CKEditor5Integration extends IntegrationModel {
11
+ constructor(ckeditorIntegrationModelProperties) {
12
+ const editor = ckeditorIntegrationModelProperties.editorObject;
13
+
14
+ if (typeof editor.config !== 'undefined' && typeof editor.config.get('mathTypeParameters') !== 'undefined') {
15
+ ckeditorIntegrationModelProperties.integrationParameters = editor.config.get('mathTypeParameters');
16
+ }
17
+ /**
18
+ * CKEditor5 Integration.
19
+ *
20
+ * @param {integrationModelProperties} integrationModelAttributes
21
+ */
22
+ super(ckeditorIntegrationModelProperties);
23
+
24
+ /**
25
+ * Folder name used for the integration inside CKEditor plugins folder.
26
+ */
27
+ this.integrationFolderName = 'ckeditor_wiris';
28
+ }
29
+
30
+ /**
31
+ * @inheritdoc
32
+ * @returns {string} - The CKEditor instance language.
33
+ * @override
34
+ */
35
+ getLanguage() {
36
+ // Returns the CKEDitor instance language taking into account that the language can be an object.
37
+ try {
38
+ return this.editorParameters.language;
39
+ } catch (e) { console.error(); }
40
+ const languageObject = this.editorObject.config.get('language');
41
+ if (languageObject != null) {
42
+ if (typeof (languageObject) === 'object') {
43
+ // eslint-disable-next-line no-prototype-builtins
44
+ if (languageObject.hasOwnProperty('ui')) {
45
+ return languageObject.ui;
46
+ } return super.getLanguage();
47
+ }
48
+ return languageObject;
49
+ }
50
+ return super.getLanguage();
51
+ }
52
+
53
+ /**
54
+ * Adds callbacks to the following CKEditor listeners:
55
+ * - 'focus' - updates the current instance.
56
+ * - 'contentDom' - adds 'doubleclick' callback.
57
+ * - 'doubleclick' - sets to null data.dialog property to avoid modifications for MathType formulas.
58
+ * - 'setData' - parses the data converting MathML into images.
59
+ * - 'afterSetData' - adds an observer to MathType formulas to avoid modifications.
60
+ * - 'getData' - parses the data converting images into selected save mode (MathML by default).
61
+ * - 'mode' - recalculates the active element.
62
+ */
63
+ addEditorListeners() {
64
+ const editor = this.editorObject;
65
+
66
+ if (typeof editor.config.wirislistenersdisabled === 'undefined'
67
+ || !editor.config.wirislistenersdisabled) {
68
+ this.checkElement();
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Checks the current container and assign events in case that it doesn't have them.
74
+ * CKEditor replaces several times the element element during its execution,
75
+ * so we must assign the events again to editor element.
76
+ */
77
+ checkElement() {
78
+ const editor = this.editorObject;
79
+ const newElement = editor.sourceElement;
80
+
81
+ // If the element wasn't treated, add the events.
82
+ if (!newElement.wirisActive) {
83
+ this.setTarget(newElement);
84
+ this.addEvents();
85
+ // Set the element as treated
86
+ newElement.wirisActive = true;
87
+ }
88
+ }
89
+
90
+ /**
91
+ * @inheritdoc
92
+ * @param {HTMLElement} element - HTMLElement target.
93
+ * @param {MouseEvent} event - event which trigger the handler.
94
+ */
95
+ doubleClickHandler(element, event) {
96
+ if (this.editorObject.isReadOnly === false) {
97
+ if (element.nodeName.toLowerCase() === 'img') {
98
+ if (Util.containsClass(element, Configuration.get('imageClassName'))) {
99
+ // Some plugins (image2, image) open a dialog on double click. On formulas
100
+ // doubleclick event ends here.
101
+ if (typeof event.stopPropagation !== 'undefined') { // old I.E compatibility.
102
+ event.stopPropagation();
103
+ } else {
104
+ event.returnValue = false;
105
+ }
106
+ this.core.getCustomEditors().disable();
107
+ const customEditorAttr = element.getAttribute(Configuration.get('imageCustomEditorName'));
108
+ if (customEditorAttr) {
109
+ this.core.getCustomEditors().enable(customEditorAttr);
110
+ }
111
+ this.core.editionProperties.temporalImage = element;
112
+ this.openExistingFormulaEditor();
113
+ }
114
+ }
115
+ }
116
+ }
117
+
118
+ /** @inheritdoc */
119
+ static getCorePath() {
120
+ return null; // TODO
121
+ }
122
+
123
+ /** @inheritdoc */
124
+ callbackFunction() {
125
+ super.callbackFunction();
126
+ this.addEditorListeners();
127
+ }
128
+
129
+ openNewFormulaEditor() {
130
+ // Store the editor selection as it will be lost upon opening the modal
131
+ this.core.editionProperties.selection = this.editorObject.editing.view.document.selection;
132
+
133
+ return super.openNewFormulaEditor();
134
+ }
135
+
136
+ /**
137
+ * Replaces old formula with new MathML or inserts it in caret position if new
138
+ * @param {String} mathml MathML to update old one or insert
139
+ * @returns {module:engine/model/element~Element} The model element corresponding to the inserted image
140
+ */
141
+ insertMathml(mathml) {
142
+ // This returns the value returned by the callback function (writer => {...})
143
+ return this.editorObject.model.change((writer) => {
144
+ const core = this.getCore();
145
+
146
+ const modelElementNew = writer.createElement('mathml', { formula: mathml });
147
+ modelElementNew.data = mathml;
148
+
149
+ // Obtain the DOM <span><img ... /></span> object corresponding to the formula
150
+ if (core.editionProperties.isNewElement) {
151
+ // Don't bother inserting anything at all if the MathML is empty.
152
+ if (!mathml) return;
153
+
154
+ const viewSelection = this.core.editionProperties.selection || this.editorObject.editing.view.document.selection;
155
+ const modelPosition = this.editorObject.editing.mapper.toModelPosition(viewSelection.getLastPosition());
156
+
157
+ writer.insert(modelElementNew, modelPosition);
158
+
159
+ // Remove selection
160
+ if (!viewSelection.isCollapsed) {
161
+ for (const range of viewSelection.getRanges()) {
162
+ writer.remove(this.editorObject.editing.mapper.toModelRange(range));
163
+ }
164
+ }
165
+ } else {
166
+ const img = core.editionProperties.temporalImage;
167
+ const viewElement = this.editorObject.editing.view.domConverter.domToView(img).parent;
168
+ const modelElementOld = this.editorObject.editing.mapper.toModelElement(viewElement);
169
+
170
+ // Insert the new <mathml> and remove the old one
171
+ const position = this.editorObject.model.createPositionBefore(modelElementOld);
172
+
173
+ // If the given MathML is empty, don't insert a new formula.
174
+ if (mathml) {
175
+ writer.insert(modelElementNew, position);
176
+ }
177
+ writer.remove(modelElementOld);
178
+ }
179
+
180
+ // eslint-disable-next-line consistent-return
181
+ return modelElementNew;
182
+ });
183
+ }
184
+
185
+ /**
186
+ * Finds the text node corresponding to given DOM text element.
187
+ * @param {element} viewElement Element to find corresponding text node of.
188
+ * @returns {module:engine/model/text~Text|undefined} Text node corresponding to the given element or undefined if it doesn't exist.
189
+ */
190
+ findText(viewElement) { // eslint-disable-line consistent-return
191
+ // mapper always converts text nodes to *new* model elements so we need to convert the text's parents and then come back down
192
+ let pivot = viewElement;
193
+ let element;
194
+ while (!element) {
195
+ element = this.editorObject.editing.mapper.toModelElement(
196
+ this.editorObject.editing.view.domConverter.domToView(
197
+ pivot,
198
+ ),
199
+ );
200
+ pivot = pivot.parentElement;
201
+ }
202
+
203
+ // Navigate through all the subtree under `pivot` in order to find the correct text node
204
+ const range = this.editorObject.model.createRangeIn(element);
205
+ const descendants = Array.from(range.getItems());
206
+ for (const node of descendants) {
207
+ if (node.is('textProxy') && node.data === viewElement.data.replace(String.fromCharCode(160), ' ')) {
208
+ return node.textNode;
209
+ }
210
+ }
211
+ }
212
+
213
+ /** @inheritdoc */
214
+ insertFormula(focusElement, windowTarget, mathml, wirisProperties) { // eslint-disable-line no-unused-vars
215
+ const returnObject = {};
216
+
217
+ if (!mathml) {
218
+ this.insertMathml('');
219
+ } else if (this.core.editMode === 'latex') {
220
+ returnObject.latex = Latex.getLatexFromMathML(mathml);
221
+ returnObject.node = windowTarget.document.createTextNode(`$$${returnObject.latex}$$`);
222
+
223
+ this.editorObject.model.change((writer) => {
224
+ const { latexRange } = this.core.editionProperties;
225
+
226
+ const startNode = this.findText(latexRange.startContainer);
227
+ const endNode = this.findText(latexRange.endContainer);
228
+
229
+ const startPosition = writer.createPositionAt(startNode.parent, startNode.startOffset + latexRange.startOffset);
230
+ const endPosition = writer.createPositionAt(endNode.parent, endNode.startOffset + latexRange.endOffset);
231
+
232
+ const range = writer.createRange(
233
+ startPosition,
234
+ endPosition,
235
+ );
236
+
237
+ writer.remove(range);
238
+ writer.insertText(`$$${returnObject.latex}$$`, startNode.getAttributes(), startPosition);
239
+ });
240
+ } else {
241
+ try {
242
+ returnObject.node = this.editorObject.editing.view.domConverter.viewToDom(
243
+ this.editorObject.editing.mapper.toViewElement(
244
+ this.insertMathml(mathml),
245
+ ), windowTarget.document,
246
+ );
247
+ } catch (e) {
248
+ const x = e.toString();
249
+ if ((x).includes("CKEditorError: Cannot read property 'parent' of undefined")) {
250
+ this.core.modalDialog.cancelAction();
251
+ }
252
+ }
253
+ }
254
+
255
+ /* Due to PLUGINS-1329, we add the onChange event to the CK4 insertFormula.
256
+ We probably should add it here as well, but we should look further into how */
257
+ // this.editorObject.fire('change');
258
+
259
+ return returnObject;
260
+ }
261
+
262
+ /**
263
+ * Function called when the content submits an action.
264
+ */
265
+ notifyWindowClosed() {
266
+ this.editorObject.editing.view.focus();
267
+ }
268
+ }