Annotation of loncom/html/adm/ckeditor/plugins/lcm/plugin.js, revision 1.2
1.1 damieng 1: /**
2: * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
3: * For licensing, see LICENSE.md or http://ckeditor.com/license
4: */
5:
6: /**
7: * @fileOverview [Mathematical Formulas] LON-CAPA ckeditor plugin for the m element, derived from ckeditor mathjax plugin.
8: */
9:
10: 'use strict';
11:
12: ( function() {
13:
14: var cdn = 'http:\/\/cdn.mathjax.org\/mathjax\/2.2-latest\/MathJax.js?config=TeX-AMS_HTML';
15:
16: CKEDITOR.plugins.add( 'lcm', {
17: lang: 'af,ar,ca,cs,cy,da,de,el,en,en-gb,eo,es,fa,fi,fr,gl,he,hr,hu,it,ja,km,ku,lt,nb,nl,no,pl,pt,pt-br,ro,ru,sk,sl,sq,sv,tr,tt,uk,vi,zh,zh-cn', // %REMOVE_LINE_CORE%
18: requires: 'widget,dialog',
19: icons: 'lcm',
20: hidpi: true, // %REMOVE_LINE_CORE%
21:
22: init: function( editor ) {
23: var cls = editor.config.mathJaxClass || 'math-tex';
24:
25: editor.widgets.add( 'lcm', {
26: inline: true,
27: dialog: 'lcm',
28: button: editor.lang.lcm.button,
29: mask: true,
30: allowedContent: 'span(!' + cls + ')',
31: // Allow style classes only on spans having lcm class.
32: styleToAllowedContentRules: function( style ) {
33: var classes = style.getClassesArray();
34: if ( !classes )
35: return null;
36: classes.push( '!' + cls );
37:
38: return 'span(' + classes.join( ',' ) + ')';
39: },
40: pathName: editor.lang.lcm.pathName,
41:
42: template: '<span class="' + cls + '" style="display:inline-block" data-cke-survive=1></span>',
43:
44: parts: {
45: span: 'span'
46: },
47:
48: defaults: {
49: math: '$ x $' // the default helps to show the $...$ syntax
50: },
51:
52: init: function() {
53: var iframe = this.parts.span.getChild( 0 );
54:
55: // Check if span contains iframe and create it otherwise.
56: if ( !iframe || iframe.type != CKEDITOR.NODE_ELEMENT || !iframe.is( 'iframe' ) ) {
57: iframe = new CKEDITOR.dom.element( 'iframe' );
58: iframe.setAttributes( {
59: style: 'border:0;width:0;height:0',
60: scrolling: 'no',
61: frameborder: 0,
62: allowTransparency: true,
63: src: CKEDITOR.plugins.lcm.fixSrc
64: } );
65: this.parts.span.append( iframe );
66: }
67:
68: // Wait for ready because on some browsers iFrame will not
69: // have document element until it is put into document.
70: // This is a problem when you crate widget using dialog.
71: this.once( 'ready', function() {
72: // Src attribute must be recreated to fix custom domain error after undo
73: // (see iFrame.removeAttribute( 'src' ) in frameWrapper.load).
74: if ( CKEDITOR.env.ie )
75: iframe.setAttribute( 'src', CKEDITOR.plugins.lcm.fixSrc );
76:
77: this.frameWrapper = new CKEDITOR.plugins.lcm.frameWrapper( iframe, editor );
78: this.frameWrapper.setValue( this.data.math );
79: } );
80: },
81:
82: data: function() {
83: if ( this.frameWrapper )
84: this.frameWrapper.setValue( this.data.math );
85: },
86:
87: upcast: function( el, data ) {
88: if ( !( el.name == 'm' ) )
89: return;
90:
91: if ( el.children.length > 1 || el.children[ 0 ].type != CKEDITOR.NODE_TEXT )
92: return;
93:
94: data.math = CKEDITOR.tools.htmlDecode( el.children[ 0 ].value );
95:
96: // NOTE: we do not preserve the display attribute, because only MathJax is used for preview
97: var span = new CKEDITOR.htmlParser.element('span');
98: span.addClass(cls);
99:
100: // Add style display:inline-block to have proper height of widget wrapper and mask.
101: var attrs = span.attributes;
102:
103: if ( attrs.style )
104: attrs.style += ';display:inline-block';
105: else
106: attrs.style = 'display:inline-block';
107:
108: // Add attribute to prevent deleting empty span in data processing.
109: attrs[ 'data-cke-survive' ] = 1;
110: if (el.attributes.eval)
111: attrs['data-eval'] = el.attributes.eval;
112: // NOTE: the display attribute is not preserved
113: el.replaceWith(span);
114: return span;
115: },
116:
117: downcast: function( el ) {
118: var m = new CKEDITOR.htmlParser.element('m');
119: if (el.attributes['data-eval'])
120: m.attributes.eval = el.attributes['data-eval'];
121: m.add(new CKEDITOR.htmlParser.text(CKEDITOR.tools.htmlEncode(this.data.math)));
122: return m;
123: }
124: } );
125:
126: // Add dialog.
127: CKEDITOR.dialog.add( 'lcm', this.path + 'dialogs/lcm.js' );
128:
129: // Add MathJax script to page preview.
130: editor.on( 'contentPreview', function( evt ) {
131: evt.data.dataValue = evt.data.dataValue.replace( /<\/head>/,
132: '<script src="' + ( editor.config.mathJaxLib ? CKEDITOR.getUrl( editor.config.mathJaxLib ) : cdn ) + '"><\/script><\/head>' );
133: } );
134:
135: editor.on( 'paste', function( evt ) {
136: // Firefox does remove iFrame elements from pasted content so this event do the same on other browsers.
137: // Also iFrame in paste content is reason of "Unspecified error" in IE9 (#10857).
138: var regex = new RegExp( '<span[^>]*?' + cls + '.*?<\/span>', 'ig' );
139: evt.data.dataValue = evt.data.dataValue.replace( regex, function( match ) {
140: return match.replace( /(<iframe.*?\/iframe>)/i, '' );
141: } );
142: } );
143: }
144: } );
145:
146: /**
147: * @private
148: * @singleton
149: * @class CKEDITOR.plugins.lcm
150: */
151: CKEDITOR.plugins.lcm = {};
152:
153: /**
154: * A variable to fix problems with `iframe`. This variable is global
155: * because it is used in both the widget and the dialog window.
156: *
157: * @private
158: * @property {String} fixSrc
159: */
160: CKEDITOR.plugins.lcm.fixSrc =
161: // In Firefox src must exist and be different than about:blank to emit load event.
162: CKEDITOR.env.gecko ? 'javascript:true' : // jshint ignore:line
163: // Support for custom document.domain in IE.
164: CKEDITOR.env.ie ? 'javascript:' + // jshint ignore:line
165: 'void((function(){' + encodeURIComponent(
166: 'document.open();' +
167: '(' + CKEDITOR.tools.fixDomain + ')();' +
168: 'document.close();'
169: ) + '})())' :
170: // In Chrome src must be undefined to emit load event.
171: 'javascript:void(0)'; // jshint ignore:line
172:
173: /**
174: * Loading indicator image generated by http://preloaders.net.
175: *
176: * @private
177: * @property {String} loadingIcon
178: */
179: CKEDITOR.plugins.lcm.loadingIcon = CKEDITOR.plugins.get( 'lcm' ).path + 'images/loader.gif';
180:
181: /**
182: * Computes predefined styles and copies them to another element.
183: *
184: * @private
185: * @param {CKEDITOR.dom.element} from Copy source.
186: * @param {CKEDITOR.dom.element} to Copy target.
187: */
188: CKEDITOR.plugins.lcm.copyStyles = function( from, to ) {
189: var stylesToCopy = [ 'color', 'font-family', 'font-style', 'font-weight', 'font-variant', 'font-size' ];
190:
191: for ( var i = 0; i < stylesToCopy.length; i++ ) {
192: var key = stylesToCopy[ i ],
193: val = from.getComputedStyle( key );
194: if ( val )
195: to.setStyle( key, val );
196: }
197: };
198:
199: /**
200: * Trims MathJax value from '\(1+1=2\)' to '1+1=2'.
201: *
202: * @private
203: * @param {String} value String to trim.
204: * @returns {String} Trimed string.
205: */
206: CKEDITOR.plugins.lcm.trim = function( value ) {
207: var begin = value.indexOf( '\\(' ) + 2,
208: end = value.lastIndexOf( '\\)' );
209:
210: return value.substring( begin, end );
211: };
212:
213: /**
214: * FrameWrapper is responsible for communication between the MathJax library
215: * and the `iframe` element that is used for rendering mathematical formulas
216: * inside the editor.
217: * It lets you create visual mathematics by using the
218: * {@link CKEDITOR.plugins.lcm.frameWrapper#setValue setValue} method.
219: *
220: * @private
221: * @class CKEDITOR.plugins.lcm.frameWrapper
222: * @constructor Creates a class instance.
223: * @param {CKEDITOR.dom.element} iFrame The `iframe` element to be wrapped.
224: * @param {CKEDITOR.editor} editor The editor instance.
225: */
226: if ( !( CKEDITOR.env.ie && CKEDITOR.env.version == 8 ) ) {
227: CKEDITOR.plugins.lcm.frameWrapper = function( iFrame, editor ) {
228:
229: var buffer, preview, value, newValue,
230: doc = iFrame.getFrameDocument(),
231:
232: // Is MathJax loaded and ready to work.
233: isInit = false,
234:
235: // Is MathJax parsing Tex.
236: isRunning = false,
237:
238: // Function called when MathJax is loaded.
239: loadedHandler = CKEDITOR.tools.addFunction( function() {
240: preview = doc.getById( 'preview' );
241: buffer = doc.getById( 'buffer' );
242: isInit = true;
243:
244: if ( newValue )
245: update();
246:
247: // Private! For test usage only.
248: CKEDITOR.fire( 'mathJaxLoaded', iFrame );
249: } ),
250:
251: // Function called when MathJax finish his job.
252: updateDoneHandler = CKEDITOR.tools.addFunction( function() {
253: CKEDITOR.plugins.lcm.copyStyles( iFrame, preview );
254:
255: preview.setHtml( buffer.getHtml() );
256:
257: editor.fire( 'lockSnapshot' );
258:
259: iFrame.setStyles( {
260: height: 0,
261: width: 0
262: } );
263:
264: // Set iFrame dimensions.
265: var height = Math.max( doc.$.body.offsetHeight, doc.$.documentElement.offsetHeight ),
266: width = Math.max( preview.$.offsetWidth, doc.$.body.scrollWidth );
267:
268: iFrame.setStyles( {
269: height: height + 'px',
270: width: width + 'px'
271: } );
272:
273: editor.fire( 'unlockSnapshot' );
274:
275: // Private! For test usage only.
276: CKEDITOR.fire( 'mathJaxUpdateDone', iFrame );
277:
278: // If value changed in the meantime update it again.
279: if ( value != newValue )
280: update();
281: else
282: isRunning = false;
283: } );
284:
285: iFrame.on( 'load', load );
286:
287: load();
288:
289: function load() {
290: doc = iFrame.getFrameDocument();
291:
292: if ( doc.getById( 'preview' ) )
293: return;
294:
295: // Because of IE9 bug in a src attribute can not be javascript
296: // when you undo (#10930). If you have iFrame with javascript in src
297: // and call insertBefore on such element then IE9 will see crash.
298: if ( CKEDITOR.env.ie )
299: iFrame.removeAttribute( 'src' );
300:
301: doc.write( '<!DOCTYPE html>' +
302: '<html>' +
303: '<head>' +
304: '<meta charset="utf-8">' +
305: '<script type="text/x-mathjax-config">' +
306:
307: // MathJax configuration, disable messages.
308: 'MathJax.Hub.Config( {' +
309: 'showMathMenu: false,' +
310: 'messageStyle: "none"' +
311: '} );' +
312:
313: // Get main CKEDITOR form parent.
314: 'function getCKE() {' +
315: 'if ( typeof window.parent.CKEDITOR == \'object\' ) {' +
316: 'return window.parent.CKEDITOR;' +
317: '} else {' +
318: 'return window.parent.parent.CKEDITOR;' +
319: '}' +
320: '}' +
321:
322: // Run MathJax.Hub with its actual parser and call callback function after that.
323: // Because MathJax.Hub is asynchronous create MathJax.Hub.Queue to wait with callback.
324: 'function update() {' +
325: 'MathJax.Hub.Queue(' +
326: '[ \'Typeset\', MathJax.Hub, this.buffer ],' +
327: 'function() {' +
328: 'getCKE().tools.callFunction( ' + updateDoneHandler + ' );' +
329: '}' +
330: ');' +
331: '}' +
332:
333: // Run MathJax for the first time, when the script is loaded.
334: // Callback function will be called then it's done.
335: 'MathJax.Hub.Queue( function() {' +
336: 'getCKE().tools.callFunction(' + loadedHandler + ');' +
337: '} );' +
338: '</script>' +
339:
340: // Load MathJax lib.
341: '<script src="' + ( editor.config.mathJaxLib || cdn ) + '"></script>' +
342: '</head>' +
343: '<body style="padding:0;margin:0;background:transparent;overflow:hidden">' +
344: '<span id="preview"></span>' +
345:
346: // Render everything here and after that copy it to the preview.
347: '<span id="buffer" style="display:none"></span>' +
348: '</body>' +
349: '</html>' );
350: }
351:
352: // Run MathJax parsing Tex.
353: function update() {
354: isRunning = true;
355:
356: value = newValue;
357:
358: editor.fire( 'lockSnapshot' );
359:
360: buffer.setHtml( value );
361:
362: // Set loading indicator.
363: preview.setHtml( '<img src=' + CKEDITOR.plugins.lcm.loadingIcon + ' alt=' + editor.lang.lcm.loading + '>' );
364:
365: iFrame.setStyles( {
366: height: '16px',
367: width: '16px',
368: display: 'inline',
369: 'vertical-align': 'middle'
370: } );
371:
372: editor.fire( 'unlockSnapshot' );
373:
374: // Run MathJax.
375: doc.getWindow().$.update( value );
376: }
377:
378: return {
379: /**
380: * Sets the TeX value to be displayed in the `iframe` element inside
381: * the editor. This function will activate the MathJax
382: * library which interprets TeX expressions and converts them into
383: * their representation that is displayed in the editor.
384: *
385: * @param {String} value TeX string.
386: */
387: setValue: function( value ) {
388: if (/^\s*\$\$.*\$\$\s*$/.test(value)) {
389: // replace $$...$$ by \[...\]
390: value = value.replace(/^\s*\$\$(.*)\$\$\s*$/, '\\[$1\\]');
391: } else if (/^\s*\$.*\$\s*$/.test(value)) {
392: // replace $...$ by \(...\)
393: value = value.replace(/^\s*\$(.*)\$\s*$/, '\\($1\\)');
394: } else if (/^\s*\\\(.*\\\)\s*$/.test(value)) {
395: // leave \( \) as is
396: } else if (/^\s*\\\[.*\\\]\s*$/.test(value)) {
397: // leave \[ \] as is
398: } else {
399: // add \( \)
1.2 ! damieng 400: // value = '\\(' + value + '\\)';
! 401: // actually, we do not want to display things too well in this case
! 402: // (if this is math, it would not work well with other math renderings like tth)
1.1 damieng 403: }
404: newValue = CKEDITOR.tools.htmlEncode( value );
405:
406: if ( isInit && !isRunning )
407: update();
408: }
409: };
410: };
411: } else {
412: // In IE8 MathJax does not work stable so instead of using standard
413: // frame wrapper it is replaced by placeholder to show pure TeX in iframe.
414: CKEDITOR.plugins.lcm.frameWrapper = function( iFrame, editor ) {
415: iFrame.getFrameDocument().write( '<!DOCTYPE html>' +
416: '<html>' +
417: '<head>' +
418: '<meta charset="utf-8">' +
419: '</head>' +
420: '<body style="padding:0;margin:0;background:transparent;overflow:hidden">' +
421: '<span style="white-space:nowrap;" id="tex"></span>' +
422: '</body>' +
423: '</html>' );
424:
425: return {
426: setValue: function( value ) {
427: var doc = iFrame.getFrameDocument(),
428: tex = doc.getById( 'tex' );
429:
430: tex.setHtml( CKEDITOR.plugins.lcm.trim( CKEDITOR.tools.htmlEncode( value ) ) );
431:
432: CKEDITOR.plugins.lcm.copyStyles( iFrame, tex );
433:
434: editor.fire( 'lockSnapshot' );
435:
436: iFrame.setStyles( {
437: width: Math.min( 250, tex.$.offsetWidth ) + 'px',
438: height: doc.$.body.offsetHeight + 'px',
439: display: 'inline',
440: 'vertical-align': 'middle'
441: } );
442:
443: editor.fire( 'unlockSnapshot' );
444: }
445: };
446: };
447: }
448: } )();
449:
450: /**
451: * Sets the path to the MathJax library. It can be both a local
452: * resource and a location different than the default CDN.
453: *
454: * Please note that this must be a full or absolute path.
455: *
456: * config.mathJaxLib = 'http:\/\/example.com\/libs\/MathJax.js';
457: *
458: * @cfg {String} [mathJaxLib='http:\/\/cdn.mathjax.org\/mathjax\/2.2-latest\/MathJax.js?config=TeX-AMS_HTML']
459: * @member CKEDITOR.config
460: */
461:
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>