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>