Annotation of loncom/html/adm/ckeditor/plugins/lcm/plugin.js, revision 1.1
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 \( \)
! 400: value = '\\(' + value + '\\)';
! 401: }
! 402: newValue = CKEDITOR.tools.htmlEncode( value );
! 403:
! 404: if ( isInit && !isRunning )
! 405: update();
! 406: }
! 407: };
! 408: };
! 409: } else {
! 410: // In IE8 MathJax does not work stable so instead of using standard
! 411: // frame wrapper it is replaced by placeholder to show pure TeX in iframe.
! 412: CKEDITOR.plugins.lcm.frameWrapper = function( iFrame, editor ) {
! 413: iFrame.getFrameDocument().write( '<!DOCTYPE html>' +
! 414: '<html>' +
! 415: '<head>' +
! 416: '<meta charset="utf-8">' +
! 417: '</head>' +
! 418: '<body style="padding:0;margin:0;background:transparent;overflow:hidden">' +
! 419: '<span style="white-space:nowrap;" id="tex"></span>' +
! 420: '</body>' +
! 421: '</html>' );
! 422:
! 423: return {
! 424: setValue: function( value ) {
! 425: var doc = iFrame.getFrameDocument(),
! 426: tex = doc.getById( 'tex' );
! 427:
! 428: tex.setHtml( CKEDITOR.plugins.lcm.trim( CKEDITOR.tools.htmlEncode( value ) ) );
! 429:
! 430: CKEDITOR.plugins.lcm.copyStyles( iFrame, tex );
! 431:
! 432: editor.fire( 'lockSnapshot' );
! 433:
! 434: iFrame.setStyles( {
! 435: width: Math.min( 250, tex.$.offsetWidth ) + 'px',
! 436: height: doc.$.body.offsetHeight + 'px',
! 437: display: 'inline',
! 438: 'vertical-align': 'middle'
! 439: } );
! 440:
! 441: editor.fire( 'unlockSnapshot' );
! 442: }
! 443: };
! 444: };
! 445: }
! 446: } )();
! 447:
! 448: /**
! 449: * Sets the path to the MathJax library. It can be both a local
! 450: * resource and a location different than the default CDN.
! 451: *
! 452: * Please note that this must be a full or absolute path.
! 453: *
! 454: * config.mathJaxLib = 'http:\/\/example.com\/libs\/MathJax.js';
! 455: *
! 456: * @cfg {String} [mathJaxLib='http:\/\/cdn.mathjax.org\/mathjax\/2.2-latest\/MathJax.js?config=TeX-AMS_HTML']
! 457: * @member CKEDITOR.config
! 458: */
! 459:
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>