File:  [LON-CAPA] / loncom / html / adm / ckeditor / plugins / lcm / plugin.js
Revision 1.2: download - view: text, annotated - select for diffs
Wed Apr 1 16:14:44 2015 UTC (9 years, 5 months ago) by damieng
Branches: MAIN
CVS tags: version_2_12_X, version_2_11_X, version_2_11_5_msu, version_2_11_5, version_2_11_4_uiuc, version_2_11_4_msu, version_2_11_4, version_2_11_3_uiuc, version_2_11_3_msu, version_2_11_3, version_2_11_2_uiuc, version_2_11_2_msu, version_2_11_2_educog, version_2_11_2, HEAD
update for ckeditor, with a new plugin for <chem>

/**
 * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
 * For licensing, see LICENSE.md or http://ckeditor.com/license
 */

/**
 * @fileOverview [Mathematical Formulas] LON-CAPA ckeditor plugin for the m element, derived from ckeditor mathjax plugin.
 */

'use strict';

( function() {

    var cdn = 'http:\/\/cdn.mathjax.org\/mathjax\/2.2-latest\/MathJax.js?config=TeX-AMS_HTML';

    CKEDITOR.plugins.add( 'lcm', {
        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%
        requires: 'widget,dialog',
        icons: 'lcm',
        hidpi: true, // %REMOVE_LINE_CORE%

        init: function( editor ) {
            var cls = editor.config.mathJaxClass || 'math-tex';

            editor.widgets.add( 'lcm', {
                inline: true,
                dialog: 'lcm',
                button: editor.lang.lcm.button,
                mask: true,
                allowedContent: 'span(!' + cls + ')',
                // Allow style classes only on spans having lcm class.
                styleToAllowedContentRules: function( style ) {
                    var classes = style.getClassesArray();
                    if ( !classes )
                        return null;
                    classes.push( '!' + cls );

                    return 'span(' + classes.join( ',' ) + ')';
                },
                pathName: editor.lang.lcm.pathName,

                template: '<span class="' + cls + '" style="display:inline-block" data-cke-survive=1></span>',

                parts: {
                    span: 'span'
                },

                defaults: {
                    math: '$ x $' // the default helps to show the $...$ syntax
                },

                init: function() {
                    var iframe = this.parts.span.getChild( 0 );

                    // Check if span contains iframe and create it otherwise.
                    if ( !iframe || iframe.type != CKEDITOR.NODE_ELEMENT || !iframe.is( 'iframe' ) ) {
                        iframe = new CKEDITOR.dom.element( 'iframe' );
                        iframe.setAttributes( {
                            style: 'border:0;width:0;height:0',
                            scrolling: 'no',
                            frameborder: 0,
                            allowTransparency: true,
                            src: CKEDITOR.plugins.lcm.fixSrc
                        } );
                        this.parts.span.append( iframe );
                    }

                    // Wait for ready because on some browsers iFrame will not
                    // have document element until it is put into document.
                    // This is a problem when you crate widget using dialog.
                    this.once( 'ready', function() {
                        // Src attribute must be recreated to fix custom domain error after undo
                        // (see iFrame.removeAttribute( 'src' ) in frameWrapper.load).
                        if ( CKEDITOR.env.ie )
                            iframe.setAttribute( 'src', CKEDITOR.plugins.lcm.fixSrc );

                        this.frameWrapper = new CKEDITOR.plugins.lcm.frameWrapper( iframe, editor );
                        this.frameWrapper.setValue( this.data.math );
                    } );
                },

                data: function() {
                    if ( this.frameWrapper )
                        this.frameWrapper.setValue( this.data.math );
                },

                upcast: function( el, data ) {
                    if ( !( el.name == 'm' ) )
                        return;

                    if ( el.children.length > 1 || el.children[ 0 ].type != CKEDITOR.NODE_TEXT )
                        return;

                    data.math = CKEDITOR.tools.htmlDecode( el.children[ 0 ].value );
                    
                    // NOTE: we do not preserve the display attribute, because only MathJax is used for preview
                    var span = new CKEDITOR.htmlParser.element('span');
                    span.addClass(cls);
                    
                    // Add style display:inline-block to have proper height of widget wrapper and mask.
                    var attrs = span.attributes;

                    if ( attrs.style )
                        attrs.style += ';display:inline-block';
                    else
                        attrs.style = 'display:inline-block';

                    // Add attribute to prevent deleting empty span in data processing.
                    attrs[ 'data-cke-survive' ] = 1;
                    if (el.attributes.eval)
                        attrs['data-eval'] = el.attributes.eval;
                    // NOTE: the display attribute is not preserved
                    el.replaceWith(span);
                    return span;
                },

                downcast: function( el ) {
                    var m = new CKEDITOR.htmlParser.element('m');
                    if (el.attributes['data-eval'])
                        m.attributes.eval = el.attributes['data-eval'];
                    m.add(new CKEDITOR.htmlParser.text(CKEDITOR.tools.htmlEncode(this.data.math)));
                    return m;
                }
            } );

            // Add dialog.
            CKEDITOR.dialog.add( 'lcm', this.path + 'dialogs/lcm.js' );

            // Add MathJax script to page preview.
            editor.on( 'contentPreview', function( evt ) {
                evt.data.dataValue = evt.data.dataValue.replace( /<\/head>/,
                    '<script src="' + ( editor.config.mathJaxLib ? CKEDITOR.getUrl( editor.config.mathJaxLib ) : cdn ) + '"><\/script><\/head>' );
            } );

            editor.on( 'paste', function( evt ) {
                // Firefox does remove iFrame elements from pasted content so this event do the same on other browsers.
                // Also iFrame in paste content is reason of "Unspecified error" in IE9 (#10857).
                var regex = new RegExp( '<span[^>]*?' + cls + '.*?<\/span>', 'ig' );
                evt.data.dataValue = evt.data.dataValue.replace( regex, function( match ) {
                    return match.replace( /(<iframe.*?\/iframe>)/i, '' );
                } );
            } );
        }
    } );

    /**
     * @private
     * @singleton
     * @class CKEDITOR.plugins.lcm
     */
    CKEDITOR.plugins.lcm = {};

    /**
     * A variable to fix problems with `iframe`. This variable is global
     * because it is used in both the widget and the dialog window.
     *
     * @private
     * @property {String} fixSrc
     */
    CKEDITOR.plugins.lcm.fixSrc =
        // In Firefox src must exist and be different than about:blank to emit load event.
        CKEDITOR.env.gecko ? 'javascript:true' : // jshint ignore:line
        // Support for custom document.domain in IE.
        CKEDITOR.env.ie ? 'javascript:' + // jshint ignore:line
                        'void((function(){' + encodeURIComponent(
                            'document.open();' +
                            '(' + CKEDITOR.tools.fixDomain + ')();' +
                            'document.close();'
                        ) + '})())' :
        // In Chrome src must be undefined to emit load event.
                        'javascript:void(0)'; // jshint ignore:line

    /**
     * Loading indicator image generated by http://preloaders.net.
     *
     * @private
     * @property {String} loadingIcon
     */
    CKEDITOR.plugins.lcm.loadingIcon = CKEDITOR.plugins.get( 'lcm' ).path + 'images/loader.gif';

    /**
     * Computes predefined styles and copies them to another element.
     *
     * @private
     * @param {CKEDITOR.dom.element} from Copy source.
     * @param {CKEDITOR.dom.element} to Copy target.
     */
    CKEDITOR.plugins.lcm.copyStyles = function( from, to ) {
        var stylesToCopy = [ 'color', 'font-family', 'font-style', 'font-weight', 'font-variant', 'font-size' ];

        for ( var i = 0; i < stylesToCopy.length; i++ ) {
            var key = stylesToCopy[ i ],
                val = from.getComputedStyle( key );
            if ( val )
                to.setStyle( key, val );
        }
    };

    /**
     * Trims MathJax value from '\(1+1=2\)' to '1+1=2'.
     *
     * @private
     * @param {String} value String to trim.
     * @returns {String} Trimed string.
     */
    CKEDITOR.plugins.lcm.trim = function( value ) {
        var begin = value.indexOf( '\\(' ) + 2,
            end = value.lastIndexOf( '\\)' );

        return value.substring( begin, end );
    };

    /**
     * FrameWrapper is responsible for communication between the MathJax library
     * and the `iframe` element that is used for rendering mathematical formulas
     * inside the editor.
     * It lets you create visual mathematics by using the
     * {@link CKEDITOR.plugins.lcm.frameWrapper#setValue setValue} method.
     *
     * @private
     * @class CKEDITOR.plugins.lcm.frameWrapper
     * @constructor Creates a class instance.
     * @param {CKEDITOR.dom.element} iFrame The `iframe` element to be wrapped.
     * @param {CKEDITOR.editor} editor The editor instance.
     */
    if ( !( CKEDITOR.env.ie && CKEDITOR.env.version == 8 ) ) {
        CKEDITOR.plugins.lcm.frameWrapper = function( iFrame, editor ) {

            var buffer, preview, value, newValue,
                doc = iFrame.getFrameDocument(),

                // Is MathJax loaded and ready to work.
                isInit = false,

                // Is MathJax parsing Tex.
                isRunning = false,

                // Function called when MathJax is loaded.
                loadedHandler = CKEDITOR.tools.addFunction( function() {
                    preview = doc.getById( 'preview' );
                    buffer = doc.getById( 'buffer' );
                    isInit = true;

                    if ( newValue )
                        update();

                    // Private! For test usage only.
                    CKEDITOR.fire( 'mathJaxLoaded', iFrame );
                } ),

                // Function called when MathJax finish his job.
                updateDoneHandler = CKEDITOR.tools.addFunction( function() {
                    CKEDITOR.plugins.lcm.copyStyles( iFrame, preview );

                    preview.setHtml( buffer.getHtml() );

                    editor.fire( 'lockSnapshot' );

                    iFrame.setStyles( {
                        height: 0,
                        width: 0
                    } );

                    // Set iFrame dimensions.
                    var height = Math.max( doc.$.body.offsetHeight, doc.$.documentElement.offsetHeight ),
                        width = Math.max( preview.$.offsetWidth, doc.$.body.scrollWidth );

                    iFrame.setStyles( {
                        height: height + 'px',
                        width: width + 'px'
                    } );

                    editor.fire( 'unlockSnapshot' );

                    // Private! For test usage only.
                    CKEDITOR.fire( 'mathJaxUpdateDone', iFrame );

                    // If value changed in the meantime update it again.
                    if ( value != newValue )
                        update();
                    else
                        isRunning = false;
                } );

            iFrame.on( 'load', load );

            load();

            function load() {
                doc = iFrame.getFrameDocument();

                if ( doc.getById( 'preview' ) )
                    return;

                // Because of IE9 bug in a src attribute can not be javascript
                // when you undo (#10930). If you have iFrame with javascript in src
                // and call insertBefore on such element then IE9 will see crash.
                if ( CKEDITOR.env.ie )
                    iFrame.removeAttribute( 'src' );

                doc.write( '<!DOCTYPE html>' +
                            '<html>' +
                            '<head>' +
                                '<meta charset="utf-8">' +
                                '<script type="text/x-mathjax-config">' +

                                    // MathJax configuration, disable messages.
                                    'MathJax.Hub.Config( {' +
                                        'showMathMenu: false,' +
                                        'messageStyle: "none"' +
                                    '} );' +

                                    // Get main CKEDITOR form parent.
                                    'function getCKE() {' +
                                        'if ( typeof window.parent.CKEDITOR == \'object\' ) {' +
                                            'return window.parent.CKEDITOR;' +
                                        '} else {' +
                                            'return window.parent.parent.CKEDITOR;' +
                                        '}' +
                                    '}' +

                                    // Run MathJax.Hub with its actual parser and call callback function after that.
                                    // Because MathJax.Hub is asynchronous create MathJax.Hub.Queue to wait with callback.
                                    'function update() {' +
                                        'MathJax.Hub.Queue(' +
                                            '[ \'Typeset\', MathJax.Hub, this.buffer ],' +
                                            'function() {' +
                                                'getCKE().tools.callFunction( ' + updateDoneHandler + ' );' +
                                            '}' +
                                        ');' +
                                    '}' +

                                    // Run MathJax for the first time, when the script is loaded.
                                    // Callback function will be called then it's done.
                                    'MathJax.Hub.Queue( function() {' +
                                        'getCKE().tools.callFunction(' + loadedHandler + ');' +
                                    '} );' +
                                '</script>' +

                                // Load MathJax lib.
                                '<script src="' + ( editor.config.mathJaxLib || cdn ) + '"></script>' +
                            '</head>' +
                            '<body style="padding:0;margin:0;background:transparent;overflow:hidden">' +
                                '<span id="preview"></span>' +

                                // Render everything here and after that copy it to the preview.
                                '<span id="buffer" style="display:none"></span>' +
                            '</body>' +
                            '</html>' );
            }

            // Run MathJax parsing Tex.
            function update() {
                isRunning = true;

                value = newValue;

                editor.fire( 'lockSnapshot' );

                buffer.setHtml( value );

                // Set loading indicator.
                preview.setHtml( '<img src=' + CKEDITOR.plugins.lcm.loadingIcon + ' alt=' + editor.lang.lcm.loading + '>' );

                iFrame.setStyles( {
                    height: '16px',
                    width: '16px',
                    display: 'inline',
                    'vertical-align': 'middle'
                } );

                editor.fire( 'unlockSnapshot' );

                // Run MathJax.
                doc.getWindow().$.update( value );
            }

            return {
                /**
                 * Sets the TeX value to be displayed in the `iframe` element inside
                 * the editor. This function will activate the MathJax
                 * library which interprets TeX expressions and converts them into
                 * their representation that is displayed in the editor.
                 *
                 * @param {String} value TeX string.
                 */
                setValue: function( value ) {
                    if (/^\s*\$\$.*\$\$\s*$/.test(value)) {
                        // replace $$...$$ by \[...\]
                        value = value.replace(/^\s*\$\$(.*)\$\$\s*$/, '\\[$1\\]');
                    } else if (/^\s*\$.*\$\s*$/.test(value)) {
                        // replace $...$ by \(...\)
                        value = value.replace(/^\s*\$(.*)\$\s*$/, '\\($1\\)');
                    } else if (/^\s*\\\(.*\\\)\s*$/.test(value)) {
                        // leave \( \) as is
                    } else if (/^\s*\\\[.*\\\]\s*$/.test(value)) {
                        // leave \[ \] as is
                    } else {
                        // add \( \)
                        // value = '\\(' + value + '\\)';
                        // actually, we do not want to display things too well in this case
                        // (if this is math, it would not work well with other math renderings like tth)
                    }
                    newValue = CKEDITOR.tools.htmlEncode( value );

                    if ( isInit && !isRunning )
                        update();
                }
            };
        };
    } else {
        // In IE8 MathJax does not work stable so instead of using standard
        // frame wrapper it is replaced by placeholder to show pure TeX in iframe.
        CKEDITOR.plugins.lcm.frameWrapper = function( iFrame, editor ) {
            iFrame.getFrameDocument().write( '<!DOCTYPE html>' +
                '<html>' +
                '<head>' +
                    '<meta charset="utf-8">' +
                '</head>' +
                '<body style="padding:0;margin:0;background:transparent;overflow:hidden">' +
                    '<span style="white-space:nowrap;" id="tex"></span>' +
                '</body>' +
                '</html>' );

            return {
                setValue: function( value ) {
                    var doc = iFrame.getFrameDocument(),
                        tex = doc.getById( 'tex' );

                    tex.setHtml( CKEDITOR.plugins.lcm.trim( CKEDITOR.tools.htmlEncode( value ) ) );

                    CKEDITOR.plugins.lcm.copyStyles( iFrame, tex );

                    editor.fire( 'lockSnapshot' );

                    iFrame.setStyles( {
                        width: Math.min( 250, tex.$.offsetWidth ) + 'px',
                        height: doc.$.body.offsetHeight + 'px',
                        display: 'inline',
                        'vertical-align': 'middle'
                    } );

                    editor.fire( 'unlockSnapshot' );
                }
            };
        };
    }
} )();

/**
 * Sets the path to the MathJax library. It can be both a local
 * resource and a location different than the default CDN.
 *
 * Please note that this must be a full or absolute path.
 *
 *        config.mathJaxLib = 'http:\/\/example.com\/libs\/MathJax.js';
 *
 * @cfg {String} [mathJaxLib='http:\/\/cdn.mathjax.org\/mathjax\/2.2-latest\/MathJax.js?config=TeX-AMS_HTML']
 * @member CKEDITOR.config
 */


FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>