Index: lams_central/web/ckeditor/plugins/equation/images/equation.gif =================================================================== diff -u -re215b142c7d6806421645b25dbd5a45a23c129bc -r7cb2670e5527930c6c50678be8770e6946ee7916 Binary files differ Fisheye: Tag 7cb2670e5527930c6c50678be8770e6946ee7916 refers to a dead (removed) revision in file `lams_central/web/ckeditor/plugins/equation/lang/en.js'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 7cb2670e5527930c6c50678be8770e6946ee7916 refers to a dead (removed) revision in file `lams_central/web/ckeditor/plugins/equation/plugin.js'. Fisheye: No comparison available. Pass `N' to diff? Index: lams_central/web/ckeditor/plugins/lineutils/plugin.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/lineutils/plugin.js (revision 0) +++ lams_central/web/ckeditor/plugins/lineutils/plugin.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,1013 @@ +/** + * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ + + /** + * @fileOverview A set of utilities to find and create horizontal spaces in edited content. + */ + +'use strict'; + +( function() { + + CKEDITOR.plugins.add( 'lineutils' ); + + /** + * Determines a position relative to an element in DOM (before). + * + * @readonly + * @property {Number} [=0] + * @member CKEDITOR + */ + CKEDITOR.LINEUTILS_BEFORE = 1; + + /** + * Determines a position relative to an element in DOM (after). + * + * @readonly + * @property {Number} [=2] + * @member CKEDITOR + */ + CKEDITOR.LINEUTILS_AFTER = 2; + + /** + * Determines a position relative to an element in DOM (inside). + * + * @readonly + * @property {Number} [=4] + * @member CKEDITOR + */ + CKEDITOR.LINEUTILS_INSIDE = 4; + + /** + * A utility that traverses the DOM tree and discovers elements + * (relations) matching user-defined lookups. + * + * @private + * @class CKEDITOR.plugins.lineutils.finder + * @constructor Creates a Finder class instance. + * @param {CKEDITOR.editor} editor Editor instance that the Finder belongs to. + * @param {Object} def Finder's definition. + * @since 4.3 + */ + function Finder( editor, def ) { + CKEDITOR.tools.extend( this, { + editor: editor, + editable: editor.editable(), + doc: editor.document, + win: editor.window + }, def, true ); + + this.inline = this.editable.isInline(); + + if ( !this.inline ) { + this.frame = this.win.getFrame(); + } + + this.target = this[ this.inline ? 'editable' : 'doc' ]; + } + + Finder.prototype = { + /** + * Initializes searching for elements with every mousemove event fired. + * To stop searching use {@link #stop}. + * + * @param {Function} [callback] Function executed on every iteration. + */ + start: function( callback ) { + var that = this, + editor = this.editor, + doc = this.doc, + el, elfp, x, y; + + var moveBuffer = CKEDITOR.tools.eventsBuffer( 50, function() { + if ( editor.readOnly || editor.mode != 'wysiwyg' ) + return; + + that.relations = {}; + + // Sometimes it happens that elementFromPoint returns null (especially on IE). + // Any further traversal makes no sense if there's no start point. Abort. + // Note: In IE8 elementFromPoint may return zombie nodes of undefined nodeType, + // so rejecting those as well. + if ( !( elfp = doc.$.elementFromPoint( x, y ) ) || !elfp.nodeType ) { + return; + } + + el = new CKEDITOR.dom.element( elfp ); + + that.traverseSearch( el ); + + if ( !isNaN( x + y ) ) { + that.pixelSearch( el, x, y ); + } + + callback && callback( that.relations, x, y ); + } ); + + // Searching starting from element from point on mousemove. + this.listener = this.editable.attachListener( this.target, 'mousemove', function( evt ) { + x = evt.data.$.clientX; + y = evt.data.$.clientY; + + moveBuffer.input(); + } ); + + this.editable.attachListener( this.inline ? this.editable : this.frame, 'mouseout', function() { + moveBuffer.reset(); + } ); + }, + + /** + * Stops observing mouse events attached by {@link #start}. + */ + stop: function() { + if ( this.listener ) { + this.listener.removeListener(); + } + }, + + /** + * Returns a range representing the relation, according to its element + * and type. + * + * @param {Object} location Location containing a unique identifier and type. + * @returns {CKEDITOR.dom.range} Range representing the relation. + */ + getRange: ( function() { + var where = {}; + + where[ CKEDITOR.LINEUTILS_BEFORE ] = CKEDITOR.POSITION_BEFORE_START; + where[ CKEDITOR.LINEUTILS_AFTER ] = CKEDITOR.POSITION_AFTER_END; + where[ CKEDITOR.LINEUTILS_INSIDE ] = CKEDITOR.POSITION_AFTER_START; + + return function( location ) { + var range = this.editor.createRange(); + + range.moveToPosition( this.relations[ location.uid ].element, where[ location.type ] ); + + return range; + }; + } )(), + + /** + * Stores given relation in a {@link #relations} object. Processes the relation + * to normalize and avoid duplicates. + * + * @param {CKEDITOR.dom.element} el Element of the relation. + * @param {Number} type Relation, one of `CKEDITOR.LINEUTILS_AFTER`, `CKEDITOR.LINEUTILS_BEFORE`, `CKEDITOR.LINEUTILS_INSIDE`. + */ + store: ( function() { + function merge( el, type, relations ) { + var uid = el.getUniqueId(); + + if ( uid in relations ) { + relations[ uid ].type |= type; + } else { + relations[ uid ] = { element: el, type: type }; + } + } + + return function( el, type ) { + var alt; + + // Normalization to avoid duplicates: + // CKEDITOR.LINEUTILS_AFTER becomes CKEDITOR.LINEUTILS_BEFORE of el.getNext(). + if ( is( type, CKEDITOR.LINEUTILS_AFTER ) && isStatic( alt = el.getNext() ) && alt.isVisible() ) { + merge( alt, CKEDITOR.LINEUTILS_BEFORE, this.relations ); + type ^= CKEDITOR.LINEUTILS_AFTER; + } + + // Normalization to avoid duplicates: + // CKEDITOR.LINEUTILS_INSIDE becomes CKEDITOR.LINEUTILS_BEFORE of el.getFirst(). + if ( is( type, CKEDITOR.LINEUTILS_INSIDE ) && isStatic( alt = el.getFirst() ) && alt.isVisible() ) { + merge( alt, CKEDITOR.LINEUTILS_BEFORE, this.relations ); + type ^= CKEDITOR.LINEUTILS_INSIDE; + } + + merge( el, type, this.relations ); + }; + } )(), + + /** + * Traverses the DOM tree towards root, checking all ancestors + * with lookup rules, avoiding duplicates. Stores positive relations + * in the {@link #relations} object. + * + * @param {CKEDITOR.dom.element} el Element which is the starting point. + */ + traverseSearch: function( el ) { + var l, type, uid; + + // Go down DOM towards root (or limit). + do { + uid = el.$[ 'data-cke-expando' ]; + + // This element was already visited and checked. + if ( uid && uid in this.relations ) { + continue; + } + + if ( el.equals( this.editable ) ) { + return; + } + + if ( isStatic( el ) ) { + // Collect all addresses yielded by lookups for that element. + for ( l in this.lookups ) { + + if ( ( type = this.lookups[ l ]( el ) ) ) { + this.store( el, type ); + } + } + } + } while ( !isLimit( el ) && ( el = el.getParent() ) ); + }, + + /** + * Iterates vertically pixel-by-pixel within a given element starting + * from given coordinates, searching for elements in the neighborhood. + * Once an element is found it is processed by {@link #traverseSearch}. + * + * @param {CKEDITOR.dom.element} el Element which is the starting point. + * @param {Number} [x] Horizontal mouse coordinate relative to the viewport. + * @param {Number} [y] Vertical mouse coordinate relative to the viewport. + */ + pixelSearch: ( function() { + var contains = CKEDITOR.env.ie || CKEDITOR.env.webkit ? + function( el, found ) { + return el.contains( found ); + } : function( el, found ) { + return !!( el.compareDocumentPosition( found ) & 16 ); + }; + + // Iterates pixel-by-pixel from starting coordinates, moving by defined + // step and getting elementFromPoint in every iteration. Iteration stops when: + // * A valid element is found. + // * Condition function returns `false` (i.e. reached boundaries of viewport). + // * No element is found (i.e. coordinates out of viewport). + // * Element found is ascendant of starting element. + // + // @param {Object} doc Native DOM document. + // @param {Object} el Native DOM element. + // @param {Number} xStart Horizontal starting coordinate to use. + // @param {Number} yStart Vertical starting coordinate to use. + // @param {Number} step Step of the algorithm. + // @param {Function} condition A condition relative to current vertical coordinate. + function iterate( el, xStart, yStart, step, condition ) { + var y = yStart, + tryouts = 0, + found; + + while ( condition( y ) ) { + y += step; + + // If we try and we try, and still nothing's found, let's end + // that party. + if ( ++tryouts == 25 ) { + return; + } + + found = this.doc.$.elementFromPoint( xStart, y ); + + // Nothing found. This is crazy... but... + // It might be that a line, which is in different document, + // covers that pixel (elementFromPoint is doc-sensitive). + // Better let's have another try. + if ( !found ) { + continue; + } + + // Still in the same element. + else if ( found == el ) { + tryouts = 0; + continue; + } + + // Reached the edge of an element and found an ancestor or... + // A line, that covers that pixel. Better let's have another try. + else if ( !contains( el, found ) ) { + continue; + } + + tryouts = 0; + + // Found a valid element. Stop iterating. + if ( isStatic( ( found = new CKEDITOR.dom.element( found ) ) ) ) { + return found; + } + } + } + + return function( el, x, y ) { + var paneHeight = this.win.getViewPaneSize().height, + + // Try to find an element iterating *up* from the starting point. + neg = iterate.call( this, el.$, x, y, -1, function( y ) { + return y > 0; + } ), + + // Try to find an element iterating *down* from the starting point. + pos = iterate.call( this, el.$, x, y, 1, function( y ) { + return y < paneHeight; + } ); + + if ( neg ) { + this.traverseSearch( neg ); + + // Iterate towards DOM root until neg is a direct child of el. + while ( !neg.getParent().equals( el ) ) { + neg = neg.getParent(); + } + } + + if ( pos ) { + this.traverseSearch( pos ); + + // Iterate towards DOM root until pos is a direct child of el. + while ( !pos.getParent().equals( el ) ) { + pos = pos.getParent(); + } + } + + // Iterate forwards starting from neg and backwards from + // pos to harvest all children of el between those elements. + // Stop when neg and pos meet each other or there's none of them. + // TODO (?) reduce number of hops forwards/backwards. + while ( neg || pos ) { + if ( neg ) { + neg = neg.getNext( isStatic ); + } + + if ( !neg || neg.equals( pos ) ) { + break; + } + + this.traverseSearch( neg ); + + if ( pos ) { + pos = pos.getPrevious( isStatic ); + } + + if ( !pos || pos.equals( neg ) ) { + break; + } + + this.traverseSearch( pos ); + } + }; + } )(), + + /** + * Unlike {@link #traverseSearch}, it collects **all** elements from editable's DOM tree + * and runs lookups for every one of them, collecting relations. + * + * @returns {Object} {@link #relations}. + */ + greedySearch: function() { + this.relations = {}; + + var all = this.editable.getElementsByTag( '*' ), + i = 0, + el, type, l; + + while ( ( el = all.getItem( i++ ) ) ) { + // Don't consider editable, as it might be inline, + // and i.e. checking it's siblings is pointless. + if ( el.equals( this.editable ) ) { + continue; + } + + // Don't visit non-editable internals, for example widget's + // guts (above wrapper, below nested). Still check editable limits, + // as they are siblings with editable contents. + if ( !el.hasAttribute( 'contenteditable' ) && el.isReadOnly() ) { + continue; + } + + if ( isStatic( el ) && el.isVisible() ) { + // Collect all addresses yielded by lookups for that element. + for ( l in this.lookups ) { + if ( ( type = this.lookups[ l ]( el ) ) ) { + this.store( el, type ); + } + } + } + } + + return this.relations; + } + + /** + * Relations express elements in DOM that match user-defined {@link #lookups}. + * Every relation has its own `type` that determines whether + * it refers to the space before, after or inside the `element`. + * This object stores relations found by {@link #traverseSearch} or {@link #greedySearch}, structured + * in the following way: + * + * relations: { + * // Unique identifier of the element. + * Number: { + * // Element of this relation. + * element: {@link CKEDITOR.dom.element} + * // Conjunction of CKEDITOR.LINEUTILS_BEFORE, CKEDITOR.LINEUTILS_AFTER and CKEDITOR.LINEUTILS_INSIDE. + * type: Number + * }, + * ... + * } + * + * @property {Object} relations + * @readonly + */ + + /** + * A set of user-defined functions used by Finder to check if an element + * is a valid relation, belonging to {@link #relations}. + * When the criterion is met, lookup returns a logical conjunction of `CKEDITOR.LINEUTILS_BEFORE`, + * `CKEDITOR.LINEUTILS_AFTER` or `CKEDITOR.LINEUTILS_INSIDE`. + * + * Lookups are passed along with Finder's definition. + * + * lookups: { + * 'some lookup': function( el ) { + * if ( someCondition ) + * return CKEDITOR.LINEUTILS_BEFORE; + * }, + * ... + * } + * + * @property {Object} lookups + */ + }; + + + /** + * A utility that analyses relations found by + * CKEDITOR.plugins.lineutils.finder and locates them + * in the viewport as horizontal lines of specific coordinates. + * + * @private + * @class CKEDITOR.plugins.lineutils.locator + * @constructor Creates a Locator class instance. + * @param {CKEDITOR.editor} editor Editor instance that Locator belongs to. + * @since 4.3 + */ + function Locator( editor, def ) { + CKEDITOR.tools.extend( this, def, { + editor: editor + }, true ); + } + + Locator.prototype = { + /** + * Locates the Y coordinate for all types of every single relation and stores + * them in an object. + * + * @param {Object} relations {@link CKEDITOR.plugins.lineutils.finder#relations}. + * @returns {Object} {@link #locations}. + */ + locate: ( function() { + function locateSibling( rel, type ) { + var sib = rel.element[ type === CKEDITOR.LINEUTILS_BEFORE ? 'getPrevious' : 'getNext' ](); + + // Return the middle point between siblings. + if ( sib && isStatic( sib ) ) { + rel.siblingRect = sib.getClientRect(); + + if ( type == CKEDITOR.LINEUTILS_BEFORE ) { + return ( rel.siblingRect.bottom + rel.elementRect.top ) / 2; + } else { + return ( rel.elementRect.bottom + rel.siblingRect.top ) / 2; + } + } + + // If there's no sibling, use the edge of an element. + else { + if ( type == CKEDITOR.LINEUTILS_BEFORE ) { + return rel.elementRect.top; + } else { + return rel.elementRect.bottom; + } + } + } + + return function( relations ) { + var rel; + + this.locations = {}; + + for ( var uid in relations ) { + rel = relations[ uid ]; + rel.elementRect = rel.element.getClientRect(); + + if ( is( rel.type, CKEDITOR.LINEUTILS_BEFORE ) ) { + this.store( uid, CKEDITOR.LINEUTILS_BEFORE, locateSibling( rel, CKEDITOR.LINEUTILS_BEFORE ) ); + } + + if ( is( rel.type, CKEDITOR.LINEUTILS_AFTER ) ) { + this.store( uid, CKEDITOR.LINEUTILS_AFTER, locateSibling( rel, CKEDITOR.LINEUTILS_AFTER ) ); + } + + // The middle point of the element. + if ( is( rel.type, CKEDITOR.LINEUTILS_INSIDE ) ) { + this.store( uid, CKEDITOR.LINEUTILS_INSIDE, ( rel.elementRect.top + rel.elementRect.bottom ) / 2 ); + } + } + + return this.locations; + }; + } )(), + + /** + * Calculates distances from every location to given vertical coordinate + * and sorts locations according to that distance. + * + * @param {Number} y The vertical coordinate used for sorting, used as a reference. + * @param {Number} [howMany] Determines the number of "closest locations" to be returned. + * @returns {Array} Sorted, array representation of {@link #locations}. + */ + sort: ( function() { + var locations, sorted, + dist, i; + + function distance( y, uid, type ) { + return Math.abs( y - locations[ uid ][ type ] ); + } + + return function( y, howMany ) { + locations = this.locations; + sorted = []; + + for ( var uid in locations ) { + for ( var type in locations[ uid ] ) { + dist = distance( y, uid, type ); + + // An array is empty. + if ( !sorted.length ) { + sorted.push( { uid: +uid, type: type, dist: dist } ); + } else { + // Sort the array on fly when it's populated. + for ( i = 0; i < sorted.length; i++ ) { + if ( dist < sorted[ i ].dist ) { + sorted.splice( i, 0, { uid: +uid, type: type, dist: dist } ); + break; + } + } + + // Nothing was inserted, so the distance is bigger than + // any of already calculated: push to the end. + if ( i == sorted.length ) { + sorted.push( { uid: +uid, type: type, dist: dist } ); + } + } + } + } + + if ( typeof howMany != 'undefined' ) { + return sorted.slice( 0, howMany ); + } else { + return sorted; + } + }; + } )(), + + /** + * Stores the location in a collection. + * + * @param {Number} uid Unique identifier of the relation. + * @param {Number} type One of `CKEDITOR.LINEUTILS_BEFORE`, `CKEDITOR.LINEUTILS_AFTER` and `CKEDITOR.LINEUTILS_INSIDE`. + * @param {Number} y Vertical position of the relation. + */ + store: function( uid, type, y ) { + if ( !this.locations[ uid ] ) { + this.locations[ uid ] = {}; + } + + this.locations[ uid ][ type ] = y; + } + + /** + * @readonly + * @property {Object} locations + */ + }; + + var tipCss = { + display: 'block', + width: '0px', + height: '0px', + 'border-color': 'transparent', + 'border-style': 'solid', + position: 'absolute', + top: '-6px' + }, + + lineStyle = { + height: '0px', + 'border-top': '1px dashed red', + position: 'absolute', + 'z-index': 9999 + }, + + lineTpl = + '
' + + ' ' + + ' ' + + '
'; + + /** + * A utility that draws horizontal lines in DOM according to locations + * returned by CKEDITOR.plugins.lineutils.locator. + * + * @private + * @class CKEDITOR.plugins.lineutils.liner + * @constructor Creates a Liner class instance. + * @param {CKEDITOR.editor} editor Editor instance that Liner belongs to. + * @param {Object} def Liner's definition. + * @since 4.3 + */ + function Liner( editor, def ) { + var editable = editor.editable(); + + CKEDITOR.tools.extend( this, { + editor: editor, + editable: editable, + inline: editable.isInline(), + doc: editor.document, + win: editor.window, + container: CKEDITOR.document.getBody(), + winTop: CKEDITOR.document.getWindow() + }, def, true ); + + this.hidden = {}; + this.visible = {}; + + if ( !this.inline ) { + this.frame = this.win.getFrame(); + } + + this.queryViewport(); + + // Callbacks must be wrapped. Otherwise they're not attached + // to global DOM objects (i.e. topmost window) for every editor + // because they're treated as duplicates. They belong to the + // same prototype shared among Liner instances. + var queryViewport = CKEDITOR.tools.bind( this.queryViewport, this ), + hideVisible = CKEDITOR.tools.bind( this.hideVisible, this ), + removeAll = CKEDITOR.tools.bind( this.removeAll, this ); + + editable.attachListener( this.winTop, 'resize', queryViewport ); + editable.attachListener( this.winTop, 'scroll', queryViewport ); + + editable.attachListener( this.winTop, 'resize', hideVisible ); + editable.attachListener( this.win, 'scroll', hideVisible ); + + editable.attachListener( this.inline ? editable : this.frame, 'mouseout', function( evt ) { + var x = evt.data.$.clientX, + y = evt.data.$.clientY; + + this.queryViewport(); + + // Check if mouse is out of the element (iframe/editable). + if ( x <= this.rect.left || x >= this.rect.right || y <= this.rect.top || y >= this.rect.bottom ) { + this.hideVisible(); + } + + // Check if mouse is out of the top-window vieport. + if ( x <= 0 || x >= this.winTopPane.width || y <= 0 || y >= this.winTopPane.height ) { + this.hideVisible(); + } + }, this ); + + editable.attachListener( editor, 'resize', queryViewport ); + editable.attachListener( editor, 'mode', removeAll ); + editor.on( 'destroy', removeAll ); + + this.lineTpl = new CKEDITOR.template( lineTpl ).output( { + lineStyle: CKEDITOR.tools.writeCssText( + CKEDITOR.tools.extend( {}, lineStyle, this.lineStyle, true ) + ), + tipLeftStyle: CKEDITOR.tools.writeCssText( + CKEDITOR.tools.extend( {}, tipCss, { + left: '0px', + 'border-left-color': 'red', + 'border-width': '6px 0 6px 6px' + }, this.tipCss, this.tipLeftStyle, true ) + ), + tipRightStyle: CKEDITOR.tools.writeCssText( + CKEDITOR.tools.extend( {}, tipCss, { + right: '0px', + 'border-right-color': 'red', + 'border-width': '6px 6px 6px 0' + }, this.tipCss, this.tipRightStyle, true ) + ) + } ); + } + + Liner.prototype = { + /** + * Permanently removes all lines (both hidden and visible) from DOM. + */ + removeAll: function() { + var l; + + for ( l in this.hidden ) { + this.hidden[ l ].remove(); + delete this.hidden[ l ]; + } + + for ( l in this.visible ) { + this.visible[ l ].remove(); + delete this.visible[ l ]; + } + }, + + /** + * Hides a given line. + * + * @param {CKEDITOR.dom.element} line The line to be hidden. + */ + hideLine: function( line ) { + var uid = line.getUniqueId(); + + line.hide(); + + this.hidden[ uid ] = line; + delete this.visible[ uid ]; + }, + + /** + * Shows a given line. + * + * @param {CKEDITOR.dom.element} line The line to be shown. + */ + showLine: function( line ) { + var uid = line.getUniqueId(); + + line.show(); + + this.visible[ uid ] = line; + delete this.hidden[ uid ]; + }, + + /** + * Hides all visible lines. + */ + hideVisible: function() { + for ( var l in this.visible ) { + this.hideLine( this.visible[ l ] ); + } + }, + + /** + * Shows a line at given location. + * + * @param {Object} location Location object containing the unique identifier of the relation + * and its type. Usually returned by {@link CKEDITOR.plugins.lineutils.locator#sort}. + * @param {Function} [callback] A callback to be called once the line is shown. + */ + placeLine: function( location, callback ) { + var styles, line, l; + + // No style means that line would be out of viewport. + if ( !( styles = this.getStyle( location.uid, location.type ) ) ) { + return; + } + + // Search for any visible line of a different hash first. + // It's faster to re-position visible line than to show it. + for ( l in this.visible ) { + if ( this.visible[ l ].getCustomData( 'hash' ) !== this.hash ) { + line = this.visible[ l ]; + break; + } + } + + // Search for any hidden line of a different hash. + if ( !line ) { + for ( l in this.hidden ) { + if ( this.hidden[ l ].getCustomData( 'hash' ) !== this.hash ) { + this.showLine( ( line = this.hidden[ l ] ) ); + break; + } + } + } + + // If no line available, add the new one. + if ( !line ) { + this.showLine( ( line = this.addLine() ) ); + } + + // Mark the line with current hash. + line.setCustomData( 'hash', this.hash ); + + // Mark the line as visible. + this.visible[ line.getUniqueId() ] = line; + + line.setStyles( styles ); + + callback && callback( line ); + }, + + /** + * Creates a style set to be used by the line, representing a particular + * relation (location). + * + * @param {Number} uid Unique identifier of the relation. + * @param {Number} type Type of the relation. + * @returns {Object} An object containing styles. + */ + getStyle: function( uid, type ) { + var rel = this.relations[ uid ], + loc = this.locations[ uid ][ type ], + styles = {}, + hdiff; + + // Line should be between two elements. + if ( rel.siblingRect ) { + styles.width = Math.max( rel.siblingRect.width, rel.elementRect.width ); + } + // Line is relative to a single element. + else { + styles.width = rel.elementRect.width; + } + + // Let's calculate the vertical position of the line. + if ( this.inline ) { + // (#13155) + styles.top = loc + this.winTopScroll.y - this.rect.relativeY; + } else { + styles.top = this.rect.top + this.winTopScroll.y + loc; + } + + // Check if line would be vertically out of the viewport. + if ( styles.top - this.winTopScroll.y < this.rect.top || styles.top - this.winTopScroll.y > this.rect.bottom ) { + return false; + } + + // Now let's calculate the horizontal alignment (left and width). + if ( this.inline ) { + // (#13155) + styles.left = rel.elementRect.left - this.rect.relativeX; + } else { + if ( rel.elementRect.left > 0 ) + styles.left = this.rect.left + rel.elementRect.left; + + // H-scroll case. Left edge of element may be out of viewport. + else { + styles.width += rel.elementRect.left; + styles.left = this.rect.left; + } + + // H-scroll case. Right edge of element may be out of viewport. + if ( ( hdiff = styles.left + styles.width - ( this.rect.left + this.winPane.width ) ) > 0 ) { + styles.width -= hdiff; + } + } + + // Finally include horizontal scroll of the global window. + styles.left += this.winTopScroll.x; + + // Append 'px' to style values. + for ( var style in styles ) { + styles[ style ] = CKEDITOR.tools.cssLength( styles[ style ] ); + } + + return styles; + }, + + /** + * Adds a new line to DOM. + * + * @returns {CKEDITOR.dom.element} A brand-new line. + */ + addLine: function() { + var line = CKEDITOR.dom.element.createFromHtml( this.lineTpl ); + + line.appendTo( this.container ); + + return line; + }, + + /** + * Assigns a unique hash to the instance that is later used + * to tell unwanted lines from new ones. This method **must** be called + * before a new set of relations is to be visualized so {@link #cleanup} + * eventually hides obsolete lines. This is because lines + * are re-used between {@link #placeLine} calls and the number of + * necessary ones may vary depending on the number of relations. + * + * @param {Object} relations {@link CKEDITOR.plugins.lineutils.finder#relations}. + * @param {Object} locations {@link CKEDITOR.plugins.lineutils.locator#locations}. + */ + prepare: function( relations, locations ) { + this.relations = relations; + this.locations = locations; + this.hash = Math.random(); + }, + + /** + * Hides all visible lines that do not belong to current hash + * and no longer represent relations (locations). + * + * See also: {@link #prepare}. + */ + cleanup: function() { + var line; + + for ( var l in this.visible ) { + line = this.visible[ l ]; + + if ( line.getCustomData( 'hash' ) !== this.hash ) { + this.hideLine( line ); + } + } + }, + + /** + * Queries dimensions of the viewport, editable, frame etc. + * that are used for correct positioning of the line. + */ + queryViewport: function() { + this.winPane = this.win.getViewPaneSize(); + this.winTopScroll = this.winTop.getScrollPosition(); + this.winTopPane = this.winTop.getViewPaneSize(); + + // (#13155) + this.rect = this.getClientRect( this.inline ? this.editable : this.frame ); + }, + + /** + * Returns `boundingClientRect` of an element, shifted by the position + * of `container` when the container is not `static` (#13155). + * + * See also: {@link CKEDITOR.dom.element#getClientRect}. + * + * @param {CKEDITOR.dom.element} el A DOM element. + * @returns {Object} A shifted rect, extended by `relativeY` and `relativeX` properties. + */ + getClientRect: function( el ) { + var rect = el.getClientRect(), + relativeContainerDocPosition = this.container.getDocumentPosition(), + relativeContainerComputedPosition = this.container.getComputedStyle( 'position' ); + + // Static or not, those values are used to offset the position of the line so they cannot be undefined. + rect.relativeX = rect.relativeY = 0; + + if ( relativeContainerComputedPosition != 'static' ) { + // Remember the offset used to shift the clientRect. + rect.relativeY = relativeContainerDocPosition.y; + rect.relativeX = relativeContainerDocPosition.x; + + rect.top -= rect.relativeY; + rect.bottom -= rect.relativeY; + rect.left -= rect.relativeX; + rect.right -= rect.relativeX; + } + + return rect; + } + }; + + function is( type, flag ) { + return type & flag; + } + + var floats = { left: 1, right: 1, center: 1 }, + positions = { absolute: 1, fixed: 1 }; + + function isElement( node ) { + return node && node.type == CKEDITOR.NODE_ELEMENT; + } + + function isFloated( el ) { + return !!( floats[ el.getComputedStyle( 'float' ) ] || floats[ el.getAttribute( 'align' ) ] ); + } + + function isPositioned( el ) { + return !!positions[ el.getComputedStyle( 'position' ) ]; + } + + function isLimit( node ) { + return isElement( node ) && node.getAttribute( 'contenteditable' ) == 'true'; + } + + function isStatic( node ) { + return isElement( node ) && !isFloated( node ) && !isPositioned( node ); + } + + /** + * Global namespace storing definitions and global helpers for the Line Utilities plugin. + * + * @private + * @class + * @singleton + * @since 4.3 + */ + CKEDITOR.plugins.lineutils = { + finder: Finder, + locator: Locator, + liner: Liner + }; +} )(); Index: lams_central/web/ckeditor/plugins/mathjax/dialogs/mathjax.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/mathjax/dialogs/mathjax.js (revision 0) +++ lams_central/web/ckeditor/plugins/mathjax/dialogs/mathjax.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,78 @@ +/** + * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ + +'use strict'; + +CKEDITOR.dialog.add( 'mathjax', function( editor ) { + + var preview, + lang = editor.lang.mathjax; + + return { + title: lang.title, + minWidth: 350, + minHeight: 100, + contents: [ + { + id: 'info', + elements: [ + { + id: 'equation', + type: 'textarea', + label: lang.dialogInput, + + onLoad: function() { + var that = this; + + if ( !( CKEDITOR.env.ie && CKEDITOR.env.version == 8 ) ) { + this.getInputElement().on( 'keyup', function() { + // Add \( and \) for preview. + preview.setValue( '\\(' + that.getInputElement().getValue() + '\\)' ); + } ); + } + }, + + setup: function( widget ) { + // Remove \( and \). + this.setValue( CKEDITOR.plugins.mathjax.trim( widget.data.math ) ); + }, + + commit: function( widget ) { + // Add \( and \) to make TeX be parsed by MathJax by default. + widget.setData( 'math', '\\(' + this.getValue() + '\\)' ); + } + }, + { + id: 'documentation', + type: 'html', + html: + '
' + + '' + + lang.docLabel + + '' + + '
' + }, + ( !( CKEDITOR.env.ie && CKEDITOR.env.version == 8 ) ) && { + id: 'preview', + type: 'html', + html: + '
' + + '' + + '
', + + onLoad: function() { + var iFrame = CKEDITOR.document.getById( this.domId ).getChild( 0 ); + preview = new CKEDITOR.plugins.mathjax.frameWrapper( iFrame, editor ); + }, + + setup: function( widget ) { + preview.setValue( widget.data.math ); + } + } + ] + } + ] + }; +} ); Index: lams_central/web/ckeditor/plugins/mathjax/icons/hidpi/mathjax.png =================================================================== diff -u Binary files differ Index: lams_central/web/ckeditor/plugins/mathjax/icons/mathjax.png =================================================================== diff -u Binary files differ Index: lams_central/web/ckeditor/plugins/mathjax/images/loader.gif =================================================================== diff -u Binary files differ Index: lams_central/web/ckeditor/plugins/mathjax/lang/af.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/mathjax/lang/af.js (revision 0) +++ lams_central/web/ckeditor/plugins/mathjax/lang/af.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,13 @@ +/* +Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang( 'mathjax', 'af', { + title: 'Wiskunde in TeX', + button: 'Wiskunde', + dialogInput: 'Skryf you Tex hier', + docUrl: 'http://en.wikibooks.org/wiki/LaTeX/Mathematics', + docLabel: 'TeX dokument', + loading: 'laai...', + pathName: 'wiskunde' +} ); Index: lams_central/web/ckeditor/plugins/mathjax/lang/ar.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/mathjax/lang/ar.js (revision 0) +++ lams_central/web/ckeditor/plugins/mathjax/lang/ar.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,13 @@ +/* +Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang( 'mathjax', 'ar', { + title: 'Mathematics in TeX', // MISSING + button: 'Math', // MISSING + dialogInput: 'Write your TeX here', // MISSING + docUrl: 'http://en.wikibooks.org/wiki/LaTeX/Mathematics', + docLabel: 'TeX documentation', // MISSING + loading: 'تحميل', + pathName: 'math' // MISSING +} ); Index: lams_central/web/ckeditor/plugins/mathjax/lang/ca.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/mathjax/lang/ca.js (revision 0) +++ lams_central/web/ckeditor/plugins/mathjax/lang/ca.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,13 @@ +/* +Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang( 'mathjax', 'ca', { + title: 'Matemàtiques a TeX', + button: 'Matemàtiques', + dialogInput: 'Escriu el TeX aquí', + docUrl: 'http://en.wikibooks.org/wiki/LaTeX/Mathematics', + docLabel: 'Documentació TeX', + loading: 'carregant...', + pathName: 'matemàtiques' +} ); Index: lams_central/web/ckeditor/plugins/mathjax/lang/cs.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/mathjax/lang/cs.js (revision 0) +++ lams_central/web/ckeditor/plugins/mathjax/lang/cs.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,13 @@ +/* +Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang( 'mathjax', 'cs', { + title: 'Matematika v TeXu', + button: 'Matematika', + dialogInput: 'Zde napište TeXový kód', + docUrl: 'http://en.wikibooks.org/wiki/LaTeX/Mathematics', + docLabel: 'Dokumentace k TeXu', + loading: 'Nahrává se...', + pathName: 'Matematika' +} ); Index: lams_central/web/ckeditor/plugins/mathjax/lang/cy.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/mathjax/lang/cy.js (revision 0) +++ lams_central/web/ckeditor/plugins/mathjax/lang/cy.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,13 @@ +/* +Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang( 'mathjax', 'cy', { + title: 'Mathemateg mewn TeX', + button: 'Math', + dialogInput: 'Ysgrifennwch eich TeX yma', + docUrl: 'http://en.wikibooks.org/wiki/LaTeX/Mathematics', + docLabel: 'Dogfennaeth TeX', + loading: 'llwytho...', + pathName: 'math' +} ); Index: lams_central/web/ckeditor/plugins/mathjax/lang/da.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/mathjax/lang/da.js (revision 0) +++ lams_central/web/ckeditor/plugins/mathjax/lang/da.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,13 @@ +/* +Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang( 'mathjax', 'da', { + title: 'Matematik i TeX', + button: 'Matematik', + dialogInput: 'Write your TeX here', // MISSING + docUrl: 'http://en.wikibooks.org/wiki/LaTeX/Mathematics', + docLabel: 'TeX dokumentation', + loading: 'loading...', // MISSING + pathName: 'matematik' +} ); Index: lams_central/web/ckeditor/plugins/mathjax/lang/de.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/mathjax/lang/de.js (revision 0) +++ lams_central/web/ckeditor/plugins/mathjax/lang/de.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,13 @@ +/* +Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang( 'mathjax', 'de', { + title: 'Mathematik in Tex', + button: 'Rechnung', + dialogInput: 'Schreiben Sie hier in Tex', + docUrl: 'http://en.wikibooks.org/wiki/LaTeX/Mathematics', + docLabel: 'Tex Dokumentation', + loading: 'lädt...', + pathName: 'rechnen' +} ); Index: lams_central/web/ckeditor/plugins/mathjax/lang/el.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/mathjax/lang/el.js (revision 0) +++ lams_central/web/ckeditor/plugins/mathjax/lang/el.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,13 @@ +/* +Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang( 'mathjax', 'el', { + title: 'Μαθηματικά με τη γλώσσα TeX', + button: 'Μαθηματικά', + dialogInput: 'Γράψτε κώδικα TeX εδώ', + docUrl: 'http://en.wikibooks.org/wiki/LaTeX/Mathematics', + docLabel: 'Τεκμηρίωση TeX', + loading: 'γίνεται φόρτωση...', + pathName: 'μαθηματικά' +} ); Index: lams_central/web/ckeditor/plugins/mathjax/lang/en-gb.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/mathjax/lang/en-gb.js (revision 0) +++ lams_central/web/ckeditor/plugins/mathjax/lang/en-gb.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,13 @@ +/* +Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang( 'mathjax', 'en-gb', { + title: 'Mathematics in TeX', + button: 'Math', + dialogInput: 'Write you TeX here', + docUrl: 'http://en.wikibooks.org/wiki/LaTeX/Mathematics', + docLabel: 'TeX documentation', + loading: 'loading...', + pathName: 'math' +} ); Index: lams_central/web/ckeditor/plugins/mathjax/lang/en.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/mathjax/lang/en.js (revision 0) +++ lams_central/web/ckeditor/plugins/mathjax/lang/en.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,13 @@ +/* +Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang( 'mathjax', 'en', { + title: 'Mathematics in TeX', + button: 'Math', + dialogInput: 'Write your TeX here', + docUrl: 'http://en.wikibooks.org/wiki/LaTeX/Mathematics', + docLabel: 'TeX documentation', + loading: 'loading...', + pathName: 'math' +} ); Index: lams_central/web/ckeditor/plugins/mathjax/lang/eo.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/mathjax/lang/eo.js (revision 0) +++ lams_central/web/ckeditor/plugins/mathjax/lang/eo.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,13 @@ +/* +Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang( 'mathjax', 'eo', { + title: 'Matematiko en TeX', + button: 'Matematiko', + dialogInput: 'Skribu vian TeX tien', + docUrl: 'http://en.wikibooks.org/wiki/LaTeX/Mathematics', + docLabel: 'TeX dokumentado', + loading: 'estas ŝarganta', + pathName: 'matematiko' +} ); Index: lams_central/web/ckeditor/plugins/mathjax/lang/es.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/mathjax/lang/es.js (revision 0) +++ lams_central/web/ckeditor/plugins/mathjax/lang/es.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,13 @@ +/* +Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang( 'mathjax', 'es', { + title: 'Matemáticas en TeX', + button: 'Matemáticas', + dialogInput: 'Escribe tu TeX aquí', + docUrl: 'http://es.wikipedia.org/wiki/TeX', + docLabel: 'Documentación de TeX', + loading: 'cargando...', + pathName: 'matemáticas' +} ); Index: lams_central/web/ckeditor/plugins/mathjax/lang/fa.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/mathjax/lang/fa.js (revision 0) +++ lams_central/web/ckeditor/plugins/mathjax/lang/fa.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,13 @@ +/* +Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang( 'mathjax', 'fa', { + title: 'ریاضیات در تک', + button: 'ریاضی', + dialogInput: 'فرمول خود را اینجا بنویسید', + docUrl: 'http://en.wikibooks.org/wiki/LaTeX/Mathematics', + docLabel: 'مستندسازی فرمول نویسی', + loading: 'بارگیری', + pathName: 'ریاضی' +} ); Index: lams_central/web/ckeditor/plugins/mathjax/lang/fi.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/mathjax/lang/fi.js (revision 0) +++ lams_central/web/ckeditor/plugins/mathjax/lang/fi.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,13 @@ +/* +Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang( 'mathjax', 'fi', { + title: 'Matematiikkaa TeX:llä', + button: 'Matematiikka', + dialogInput: 'Kirjoita TeX:iä tähän', + docUrl: 'http://en.wikibooks.org/wiki/LaTeX/Mathematics', + docLabel: 'TeX dokumentaatio', + loading: 'lataa...', + pathName: 'matematiikka' +} ); Index: lams_central/web/ckeditor/plugins/mathjax/lang/fr.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/mathjax/lang/fr.js (revision 0) +++ lams_central/web/ckeditor/plugins/mathjax/lang/fr.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,13 @@ +/* +Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang( 'mathjax', 'fr', { + title: 'Mathématiques au format TeX', + button: 'Math', + dialogInput: 'Saisir la formule TeX ici', + docUrl: 'http://fr.wikibooks.org/wiki/LaTeX/Math%C3%A9matiques', + docLabel: 'Documentation du format TeX', + loading: 'chargement...', + pathName: 'math' +} ); Index: lams_central/web/ckeditor/plugins/mathjax/lang/gl.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/mathjax/lang/gl.js (revision 0) +++ lams_central/web/ckeditor/plugins/mathjax/lang/gl.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,13 @@ +/* +Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang( 'mathjax', 'gl', { + title: 'Matemáticas en TeX', + button: 'Matemáticas', + dialogInput: 'Escriba o seu TeX aquí', + docUrl: 'http://en.wikibooks.org/wiki/LaTeX/Mathematics', + docLabel: 'Documentación de TeX', + loading: 'cargando...', + pathName: 'matemáticas' +} ); Index: lams_central/web/ckeditor/plugins/mathjax/lang/he.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/mathjax/lang/he.js (revision 0) +++ lams_central/web/ckeditor/plugins/mathjax/lang/he.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,13 @@ +/* +Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang( 'mathjax', 'he', { + title: 'מתמטיקה בTeX', + button: 'מתמטיקה', + dialogInput: 'כתוב את הTeX שלך כאן', + docUrl: 'http://en.wikibooks.org/wiki/LaTeX/Mathematics', + docLabel: 'תיעוד TeX', + loading: 'טוען...', + pathName: 'מתמטיקה' +} ); Index: lams_central/web/ckeditor/plugins/mathjax/lang/hr.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/mathjax/lang/hr.js (revision 0) +++ lams_central/web/ckeditor/plugins/mathjax/lang/hr.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,13 @@ +/* +Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang( 'mathjax', 'hr', { + title: 'Matematika u TeXu', + button: 'Matematika', + dialogInput: 'Napiši svoj TeX ovdje', + docUrl: 'http://en.wikibooks.org/wiki/LaTeX/Mathematics', + docLabel: 'TeX dokumentacija', + loading: 'učitavanje...', + pathName: 'matematika' +} ); Index: lams_central/web/ckeditor/plugins/mathjax/lang/hu.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/mathjax/lang/hu.js (revision 0) +++ lams_central/web/ckeditor/plugins/mathjax/lang/hu.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,13 @@ +/* +Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang( 'mathjax', 'hu', { + title: 'Matematika a TeX-ben', + button: 'Matek', + dialogInput: 'Írd a TeX-ed ide', + docUrl: 'http://en.wikibooks.org/wiki/LaTeX/Mathematics', + docLabel: 'TeX dokumentáció', + loading: 'töltés...', + pathName: 'matek' +} ); Index: lams_central/web/ckeditor/plugins/mathjax/lang/it.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/mathjax/lang/it.js (revision 0) +++ lams_central/web/ckeditor/plugins/mathjax/lang/it.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,13 @@ +/* +Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang( 'mathjax', 'it', { + title: 'Formule in TeX', + button: 'Formule', + dialogInput: 'Scrivere qui il proprio TeX', + docUrl: 'http://en.wikibooks.org/wiki/LaTeX/Mathematics', + docLabel: 'Documentazione TeX', + loading: 'caricamento…', + pathName: 'formula' +} ); Index: lams_central/web/ckeditor/plugins/mathjax/lang/ja.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/mathjax/lang/ja.js (revision 0) +++ lams_central/web/ckeditor/plugins/mathjax/lang/ja.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,13 @@ +/* +Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang( 'mathjax', 'ja', { + title: 'TeX形式の数式', + button: '数式', + dialogInput: 'TeX形式の数式を入力してください', + docUrl: 'http://en.wikibooks.org/wiki/LaTeX/Mathematics', + docLabel: 'TeXの解説', + loading: '読み込み中…', + pathName: 'math' +} ); Index: lams_central/web/ckeditor/plugins/mathjax/lang/km.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/mathjax/lang/km.js (revision 0) +++ lams_central/web/ckeditor/plugins/mathjax/lang/km.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,13 @@ +/* +Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang( 'mathjax', 'km', { + title: 'គណិត​វិទ្យា​ក្នុង TeX', + button: 'គណិត', + dialogInput: 'សរសេរ TeX របស់​អ្នក​នៅ​ទីនេះ', + docUrl: 'http://en.wikibooks.org/wiki/LaTeX/Mathematics', + docLabel: 'ឯកសារ​អត្ថបទ​ពី ​TeX', + loading: 'កំពុង​ផ្ទុក..', + pathName: 'គណិត' +} ); Index: lams_central/web/ckeditor/plugins/mathjax/lang/ku.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/mathjax/lang/ku.js (revision 0) +++ lams_central/web/ckeditor/plugins/mathjax/lang/ku.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,13 @@ +/* +Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang( 'mathjax', 'ku', { + title: 'بیرکاری لە TeX', + button: 'بیرکاری', + dialogInput: 'TeXەکەت لێرە بنووسە', + docUrl: 'http://en.wikibooks.org/wiki/LaTeX/Mathematics', + docLabel: 'بەڵگەنامەکردنی TeX', + loading: 'بارکردن...', + pathName: 'بیرکاری' +} ); Index: lams_central/web/ckeditor/plugins/mathjax/lang/lt.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/mathjax/lang/lt.js (revision 0) +++ lams_central/web/ckeditor/plugins/mathjax/lang/lt.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,13 @@ +/* +Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang( 'mathjax', 'lt', { + title: 'Matematika per TeX', + button: 'Matematika', + dialogInput: 'Parašyk savo TeX čia', + docUrl: 'http://en.wikibooks.org/wiki/LaTeX/Mathematics', + docLabel: 'TeX žinynas', + loading: 'kraunasi...', + pathName: 'matematika' +} ); Index: lams_central/web/ckeditor/plugins/mathjax/lang/nb.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/mathjax/lang/nb.js (revision 0) +++ lams_central/web/ckeditor/plugins/mathjax/lang/nb.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,13 @@ +/* +Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang( 'mathjax', 'nb', { + title: 'Matematikk i TeX', + button: 'Matte', + dialogInput: 'Skriv TeX-koden her', + docUrl: 'http://en.wikibooks.org/wiki/LaTeX/Mathematics', + docLabel: 'TeX-dokumentasjon', + loading: 'laster...', + pathName: 'matte' +} ); Index: lams_central/web/ckeditor/plugins/mathjax/lang/nl.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/mathjax/lang/nl.js (revision 0) +++ lams_central/web/ckeditor/plugins/mathjax/lang/nl.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,13 @@ +/* +Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang( 'mathjax', 'nl', { + title: 'Wiskunde in TeX', + button: 'Wiskunde', + dialogInput: 'Typ hier uw TeX', + docUrl: 'http://en.wikibooks.org/wiki/LaTeX/Mathematics', + docLabel: 'TeX documentatie', + loading: 'laden...', + pathName: 'wiskunde' +} ); Index: lams_central/web/ckeditor/plugins/mathjax/lang/no.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/mathjax/lang/no.js (revision 0) +++ lams_central/web/ckeditor/plugins/mathjax/lang/no.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,13 @@ +/* +Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang( 'mathjax', 'no', { + title: 'Matematikk i TeX', + button: 'Matte', + dialogInput: 'Skriv TeX-koden her', + docUrl: 'http://en.wikibooks.org/wiki/LaTeX/Mathematics', + docLabel: 'TeX-dokumentasjon', + loading: 'laster...', + pathName: 'matte' +} ); Index: lams_central/web/ckeditor/plugins/mathjax/lang/pl.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/mathjax/lang/pl.js (revision 0) +++ lams_central/web/ckeditor/plugins/mathjax/lang/pl.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,13 @@ +/* +Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang( 'mathjax', 'pl', { + title: 'Wzory matematyczne w TeX', + button: 'Wzory matematyczne', + dialogInput: 'Wpisz wyrażenie w TeX', + docUrl: 'http://en.wikibooks.org/wiki/LaTeX/Mathematics', + docLabel: 'Dokumentacja TeX', + loading: 'ładowanie...', + pathName: 'matematyka' +} ); Index: lams_central/web/ckeditor/plugins/mathjax/lang/pt-br.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/mathjax/lang/pt-br.js (revision 0) +++ lams_central/web/ckeditor/plugins/mathjax/lang/pt-br.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,13 @@ +/* +Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang( 'mathjax', 'pt-br', { + title: 'Matemática em TeX', + button: 'Matemática', + dialogInput: 'Escreva seu TeX aqui', + docUrl: 'http://en.wikibooks.org/wiki/LaTeX/Mathematics', + docLabel: 'Documentação TeX', + loading: 'carregando...', + pathName: 'Matemática' +} ); Index: lams_central/web/ckeditor/plugins/mathjax/lang/pt.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/mathjax/lang/pt.js (revision 0) +++ lams_central/web/ckeditor/plugins/mathjax/lang/pt.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,13 @@ +/* +Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang( 'mathjax', 'pt', { + title: 'Matemática em TeX', + button: 'Matemática', + dialogInput: 'Escreva aqui o seu Tex', + docUrl: 'http://en.wikibooks.org/wiki/LaTeX/Mathematics', + docLabel: 'Documentação TeX', + loading: 'a carregar ...', + pathName: 'matemática' +} ); Index: lams_central/web/ckeditor/plugins/mathjax/lang/ro.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/mathjax/lang/ro.js (revision 0) +++ lams_central/web/ckeditor/plugins/mathjax/lang/ro.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,13 @@ +/* +Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang( 'mathjax', 'ro', { + title: 'Matematici in TeX', + button: 'Matematici', + dialogInput: 'Scrie TeX-ul aici', + docUrl: 'http://ro.wikibooks.org/wiki/LaTeX/Mathematics', + docLabel: 'Documentatie TeX', + loading: 'încarcă...', + pathName: 'matematici' +} ); Index: lams_central/web/ckeditor/plugins/mathjax/lang/ru.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/mathjax/lang/ru.js (revision 0) +++ lams_central/web/ckeditor/plugins/mathjax/lang/ru.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,13 @@ +/* +Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang( 'mathjax', 'ru', { + title: 'Математика в TeX-системе', + button: 'Математика', + dialogInput: 'Введите здесь TeX', + docUrl: 'http://en.wikibooks.org/wiki/LaTeX/Mathematics', + docLabel: 'TeX документация', + loading: 'загрузка...', + pathName: 'мат.' +} ); Index: lams_central/web/ckeditor/plugins/mathjax/lang/sk.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/mathjax/lang/sk.js (revision 0) +++ lams_central/web/ckeditor/plugins/mathjax/lang/sk.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,13 @@ +/* +Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang( 'mathjax', 'sk', { + title: 'Matematika v TeX', + button: 'Matika', + dialogInput: 'Napíšte svoj TeX sem', + docUrl: 'http://en.wikibooks.org/wiki/LaTeX/Mathematics', + docLabel: 'Dokumentácia TeX', + loading: 'načítavanie...', + pathName: 'matika' +} ); Index: lams_central/web/ckeditor/plugins/mathjax/lang/sl.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/mathjax/lang/sl.js (revision 0) +++ lams_central/web/ckeditor/plugins/mathjax/lang/sl.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,13 @@ +/* +Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang( 'mathjax', 'sl', { + title: 'Matematika v TeX', + button: 'Matematika', + dialogInput: 'Napišite svoj TeX tukaj', + docUrl: 'http://en.wikibooks.org/wiki/LaTeX/Mathematics', + docLabel: 'TeX dokumentacija', + loading: 'nalaganje...', + pathName: 'matematika' +} ); Index: lams_central/web/ckeditor/plugins/mathjax/lang/sq.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/mathjax/lang/sq.js (revision 0) +++ lams_central/web/ckeditor/plugins/mathjax/lang/sq.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,13 @@ +/* +Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang( 'mathjax', 'sq', { + title: 'Matematikë në TeX', + button: 'Matematikë', + dialogInput: 'Shkruani TeX-in tuaj këtu', + docUrl: 'http://en.wikibooks.org/wiki/LaTeX/Mathematics', + docLabel: 'Tex dokumentimi', + loading: 'duke u hapur...', + pathName: 'matematikë' +} ); Index: lams_central/web/ckeditor/plugins/mathjax/lang/sv.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/mathjax/lang/sv.js (revision 0) +++ lams_central/web/ckeditor/plugins/mathjax/lang/sv.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,13 @@ +/* +Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang( 'mathjax', 'sv', { + title: 'Mattematik i TeX', + button: 'Matte', + dialogInput: 'Skriv din TeX här', + docUrl: 'http://en.wikibooks.org/wiki/LaTeX/Mathematics', + docLabel: 'TeX dokumentation', + loading: 'laddar', + pathName: 'matte' +} ); Index: lams_central/web/ckeditor/plugins/mathjax/lang/tr.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/mathjax/lang/tr.js (revision 0) +++ lams_central/web/ckeditor/plugins/mathjax/lang/tr.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,13 @@ +/* +Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang( 'mathjax', 'tr', { + title: 'TeX ile Matematik', + button: 'Matematik', + dialogInput: 'TeX kodunuzu buraya yazın', + docUrl: 'http://en.wikibooks.org/wiki/LaTeX/Mathematics', + docLabel: 'TeX yardım dökümanı', + loading: 'yükleniyor...', + pathName: 'matematik' +} ); Index: lams_central/web/ckeditor/plugins/mathjax/lang/tt.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/mathjax/lang/tt.js (revision 0) +++ lams_central/web/ckeditor/plugins/mathjax/lang/tt.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,13 @@ +/* +Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang( 'mathjax', 'tt', { + title: 'TeX\'та математика', + button: 'Математика', + dialogInput: 'Биредә TeX форматында аңлатмагызны языгыз', + docUrl: 'http://en.wikibooks.org/wiki/LaTeX/Mathematics', + docLabel: 'TeX турыдна документлар', + loading: 'йөкләнә...', + pathName: 'математика' +} ); Index: lams_central/web/ckeditor/plugins/mathjax/lang/uk.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/mathjax/lang/uk.js (revision 0) +++ lams_central/web/ckeditor/plugins/mathjax/lang/uk.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,13 @@ +/* +Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang( 'mathjax', 'uk', { + title: 'Математика у TeX', + button: 'Математика', + dialogInput: 'Наберіть тут на TeX\'у', + docUrl: 'http://en.wikibooks.org/wiki/LaTeX/Mathematics', + docLabel: 'Документація про TeX', + loading: 'завантажується…', + pathName: 'математика' +} ); Index: lams_central/web/ckeditor/plugins/mathjax/lang/vi.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/mathjax/lang/vi.js (revision 0) +++ lams_central/web/ckeditor/plugins/mathjax/lang/vi.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,13 @@ +/* +Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang( 'mathjax', 'vi', { + title: 'Toán học bằng TeX', + button: 'Toán', + dialogInput: 'Nhập mã TeX ở đây', + docUrl: 'http://en.wikibooks.org/wiki/LaTeX/Mathematics', + docLabel: 'Tài liệu TeX', + loading: 'đang nạp...', + pathName: 'toán' +} ); Index: lams_central/web/ckeditor/plugins/mathjax/lang/zh-cn.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/mathjax/lang/zh-cn.js (revision 0) +++ lams_central/web/ckeditor/plugins/mathjax/lang/zh-cn.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,13 @@ +/* +Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang( 'mathjax', 'zh-cn', { + title: 'TeX 语法的数学公式编辑器', + button: '数学公式', + dialogInput: '在此编写您的 TeX 指令', + docUrl: 'http://zh.wikipedia.org/wiki/TeX', + docLabel: 'TeX 语法(可以参考维基百科自身关于数学公式显示方式的帮助)', + loading: '正在加载...', + pathName: '数字公式' +} ); Index: lams_central/web/ckeditor/plugins/mathjax/lang/zh.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/mathjax/lang/zh.js (revision 0) +++ lams_central/web/ckeditor/plugins/mathjax/lang/zh.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,13 @@ +/* +Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +CKEDITOR.plugins.setLang( 'mathjax', 'zh', { + title: '以 TeX 表示數學', + button: '數學', + dialogInput: '請輸入 TeX', + docUrl: 'http://en.wikibooks.org/wiki/LaTeX/Mathematics', + docLabel: 'TeX 說明文件', + loading: '載入中…', + pathName: '數學' +} ); Index: lams_central/web/ckeditor/plugins/mathjax/plugin.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/mathjax/plugin.js (revision 0) +++ lams_central/web/ckeditor/plugins/mathjax/plugin.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,460 @@ +/** + * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ + +/** + * @fileOverview [Mathematical Formulas](http://ckeditor.com/addon/mathjax) plugin. + */ + +'use strict'; + +( function() { + + var cdn = 'http:\/\/cdn.mathjax.org\/mathjax\/2.2-latest\/MathJax.js?config=TeX-AMS_HTML'; + + CKEDITOR.plugins.add( 'mathjax', { + 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: 'mathjax', + hidpi: true, // %REMOVE_LINE_CORE% + + init: function( editor ) { + var cls = editor.config.mathJaxClass || 'math-tex'; + + editor.widgets.add( 'mathjax', { + inline: true, + dialog: 'mathjax', + button: editor.lang.mathjax.button, + mask: true, + allowedContent: 'span(!' + cls + ')', + // Allow style classes only on spans having mathjax class. + styleToAllowedContentRules: function( style ) { + var classes = style.getClassesArray(); + if ( !classes ) + return null; + classes.push( '!' + cls ); + + return 'span(' + classes.join( ',' ) + ')'; + }, + pathName: editor.lang.mathjax.pathName, + + template: '', + + parts: { + span: 'span' + }, + + defaults: { + math: '\\(x = {-b \\pm \\sqrt{b^2-4ac} \\over 2a}\\)' + }, + + 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.mathjax.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.mathjax.fixSrc ); + + this.frameWrapper = new CKEDITOR.plugins.mathjax.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 == 'span' && el.hasClass( cls ) ) ) + return; + + if ( el.children.length > 1 || el.children[ 0 ].type != CKEDITOR.NODE_TEXT ) + return; + + data.math = CKEDITOR.tools.htmlDecode( el.children[ 0 ].value ); + + // Add style display:inline-block to have proper height of widget wrapper and mask. + var attrs = el.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; + + el.children[ 0 ].remove(); + + return el; + }, + + downcast: function( el ) { + el.children[ 0 ].replaceWith( new CKEDITOR.htmlParser.text( CKEDITOR.tools.htmlEncode( this.data.math ) ) ); + + // Remove style display:inline-block. + var attrs = el.attributes; + attrs.style = attrs.style.replace( /display:\s?inline-block;?\s?/, '' ); + if ( attrs.style === '' ) + delete attrs.style; + + return el; + } + } ); + + // Add dialog. + CKEDITOR.dialog.add( 'mathjax', this.path + 'dialogs/mathjax.js' ); + + // Add MathJax script to page preview. + editor.on( 'contentPreview', function( evt ) { + evt.data.dataValue = evt.data.dataValue.replace( /<\/head>/, + '' + + + // Load MathJax lib. + '' + + '' + + '' + + '' + + + // Render everything here and after that copy it to the preview. + '' + + '' + + '' ); + } + + // Run MathJax parsing Tex. + function update() { + isRunning = true; + + value = newValue; + + editor.fire( 'lockSnapshot' ); + + buffer.setHtml( value ); + + // Set loading indicator. + preview.setHtml( ' + editor.lang.mathjax.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 ) { + 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.mathjax.frameWrapper = function( iFrame, editor ) { + iFrame.getFrameDocument().write( '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' ); + + return { + setValue: function( value ) { + var doc = iFrame.getFrameDocument(), + tex = doc.getById( 'tex' ); + + tex.setHtml( CKEDITOR.plugins.mathjax.trim( CKEDITOR.tools.htmlEncode( value ) ) ); + + CKEDITOR.plugins.mathjax.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 + */ + +/** + * Sets the default class for `span` elements that will be + * converted into [Mathematical Formulas](http://ckeditor.com/addon/mathjax) + * widgets. + * + * If you set it to the following: + * + * config.mathJaxClass = 'my-math'; + * + * The code below will be recognized as a Mathematical Formulas widget. + * + * \( \sqrt{4} = 2 \) + * + * @cfg {String} [mathJaxClass='math-tex'] + * @member CKEDITOR.config + */ Index: lams_central/web/ckeditor/plugins/widget/images/handle.png =================================================================== diff -u Binary files differ Index: lams_central/web/ckeditor/plugins/widget/lang/af.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/widget/lang/af.js (revision 0) +++ lams_central/web/ckeditor/plugins/widget/lang/af.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,7 @@ +/** + * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ +CKEDITOR.plugins.setLang( 'widget', 'af', { + 'move': 'Klik en trek on te beweeg' +} ); Index: lams_central/web/ckeditor/plugins/widget/lang/ar.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/widget/lang/ar.js (revision 0) +++ lams_central/web/ckeditor/plugins/widget/lang/ar.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,7 @@ +/** + * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ +CKEDITOR.plugins.setLang( 'widget', 'ar', { + 'move': 'Click and drag to move' // MISSING +} ); Index: lams_central/web/ckeditor/plugins/widget/lang/ca.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/widget/lang/ca.js (revision 0) +++ lams_central/web/ckeditor/plugins/widget/lang/ca.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,7 @@ +/** + * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ +CKEDITOR.plugins.setLang( 'widget', 'ca', { + 'move': 'Clicar i arrossegar per moure' +} ); Index: lams_central/web/ckeditor/plugins/widget/lang/cs.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/widget/lang/cs.js (revision 0) +++ lams_central/web/ckeditor/plugins/widget/lang/cs.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,7 @@ +/** + * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ +CKEDITOR.plugins.setLang( 'widget', 'cs', { + 'move': 'Klepněte a táhněte pro přesunutí' +} ); Index: lams_central/web/ckeditor/plugins/widget/lang/cy.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/widget/lang/cy.js (revision 0) +++ lams_central/web/ckeditor/plugins/widget/lang/cy.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,7 @@ +/** + * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ +CKEDITOR.plugins.setLang( 'widget', 'cy', { + 'move': 'Clcio a llusgo i symud' +} ); Index: lams_central/web/ckeditor/plugins/widget/lang/da.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/widget/lang/da.js (revision 0) +++ lams_central/web/ckeditor/plugins/widget/lang/da.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,7 @@ +/** + * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ +CKEDITOR.plugins.setLang( 'widget', 'da', { + 'move': 'Klik og træk for at flytte' +} ); Index: lams_central/web/ckeditor/plugins/widget/lang/de.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/widget/lang/de.js (revision 0) +++ lams_central/web/ckeditor/plugins/widget/lang/de.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,7 @@ +/** + * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ +CKEDITOR.plugins.setLang( 'widget', 'de', { + 'move': 'Zum verschieben anwählen und ziehen' +} ); Index: lams_central/web/ckeditor/plugins/widget/lang/el.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/widget/lang/el.js (revision 0) +++ lams_central/web/ckeditor/plugins/widget/lang/el.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,7 @@ +/** + * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ +CKEDITOR.plugins.setLang( 'widget', 'el', { + 'move': 'Κάνετε κλικ και σύρετε το ποντίκι για να μετακινήστε' +} ); Index: lams_central/web/ckeditor/plugins/widget/lang/en-gb.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/widget/lang/en-gb.js (revision 0) +++ lams_central/web/ckeditor/plugins/widget/lang/en-gb.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,7 @@ +/** + * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ +CKEDITOR.plugins.setLang( 'widget', 'en-gb', { + 'move': 'Click and drag to move' +} ); Index: lams_central/web/ckeditor/plugins/widget/lang/en.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/widget/lang/en.js (revision 0) +++ lams_central/web/ckeditor/plugins/widget/lang/en.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,7 @@ +/** + * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ +CKEDITOR.plugins.setLang( 'widget', 'en', { + 'move': 'Click and drag to move' +} ); Index: lams_central/web/ckeditor/plugins/widget/lang/eo.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/widget/lang/eo.js (revision 0) +++ lams_central/web/ckeditor/plugins/widget/lang/eo.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,7 @@ +/** + * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ +CKEDITOR.plugins.setLang( 'widget', 'eo', { + 'move': 'klaki kaj treni por movi' +} ); Index: lams_central/web/ckeditor/plugins/widget/lang/es.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/widget/lang/es.js (revision 0) +++ lams_central/web/ckeditor/plugins/widget/lang/es.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,7 @@ +/** + * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ +CKEDITOR.plugins.setLang( 'widget', 'es', { + 'move': 'Dar clic y arrastrar para mover' +} ); Index: lams_central/web/ckeditor/plugins/widget/lang/fa.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/widget/lang/fa.js (revision 0) +++ lams_central/web/ckeditor/plugins/widget/lang/fa.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,7 @@ +/** + * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ +CKEDITOR.plugins.setLang( 'widget', 'fa', { + 'move': 'کلیک و کشیدن برای جابجایی' +} ); Index: lams_central/web/ckeditor/plugins/widget/lang/fi.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/widget/lang/fi.js (revision 0) +++ lams_central/web/ckeditor/plugins/widget/lang/fi.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,7 @@ +/** + * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ +CKEDITOR.plugins.setLang( 'widget', 'fi', { + 'move': 'Siirrä klikkaamalla ja raahaamalla' +} ); Index: lams_central/web/ckeditor/plugins/widget/lang/fr.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/widget/lang/fr.js (revision 0) +++ lams_central/web/ckeditor/plugins/widget/lang/fr.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,7 @@ +/** + * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ +CKEDITOR.plugins.setLang( 'widget', 'fr', { + 'move': 'Cliquer et glisser pour déplacer' +} ); Index: lams_central/web/ckeditor/plugins/widget/lang/gl.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/widget/lang/gl.js (revision 0) +++ lams_central/web/ckeditor/plugins/widget/lang/gl.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,7 @@ +/** + * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ +CKEDITOR.plugins.setLang( 'widget', 'gl', { + 'move': 'Prema e arrastre para mover' +} ); Index: lams_central/web/ckeditor/plugins/widget/lang/he.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/widget/lang/he.js (revision 0) +++ lams_central/web/ckeditor/plugins/widget/lang/he.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,7 @@ +/** + * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ +CKEDITOR.plugins.setLang( 'widget', 'he', { + 'move': 'לחץ וגרור להזזה' +} ); Index: lams_central/web/ckeditor/plugins/widget/lang/hr.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/widget/lang/hr.js (revision 0) +++ lams_central/web/ckeditor/plugins/widget/lang/hr.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,7 @@ +/** + * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ +CKEDITOR.plugins.setLang( 'widget', 'hr', { + 'move': 'Klikni i povuci da pomakneš' +} ); Index: lams_central/web/ckeditor/plugins/widget/lang/hu.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/widget/lang/hu.js (revision 0) +++ lams_central/web/ckeditor/plugins/widget/lang/hu.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,7 @@ +/** + * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ +CKEDITOR.plugins.setLang( 'widget', 'hu', { + 'move': 'Kattints és húzd a mozgatáshoz' +} ); Index: lams_central/web/ckeditor/plugins/widget/lang/it.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/widget/lang/it.js (revision 0) +++ lams_central/web/ckeditor/plugins/widget/lang/it.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,7 @@ +/** + * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ +CKEDITOR.plugins.setLang( 'widget', 'it', { + 'move': 'Fare clic e trascinare per spostare' +} ); Index: lams_central/web/ckeditor/plugins/widget/lang/ja.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/widget/lang/ja.js (revision 0) +++ lams_central/web/ckeditor/plugins/widget/lang/ja.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,7 @@ +/** + * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ +CKEDITOR.plugins.setLang( 'widget', 'ja', { + 'move': 'ドラッグして移動' +} ); Index: lams_central/web/ckeditor/plugins/widget/lang/km.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/widget/lang/km.js (revision 0) +++ lams_central/web/ckeditor/plugins/widget/lang/km.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,7 @@ +/** + * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ +CKEDITOR.plugins.setLang( 'widget', 'km', { + 'move': 'ចុច​ហើយ​ទាញ​ដើម្បី​ផ្លាស់​ទី' +} ); Index: lams_central/web/ckeditor/plugins/widget/lang/ko.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/widget/lang/ko.js (revision 0) +++ lams_central/web/ckeditor/plugins/widget/lang/ko.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,7 @@ +/** + * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ +CKEDITOR.plugins.setLang( 'widget', 'ko', { + 'move': '움직이려면 클릭 후 드래그 하세요' +} ); Index: lams_central/web/ckeditor/plugins/widget/lang/ku.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/widget/lang/ku.js (revision 0) +++ lams_central/web/ckeditor/plugins/widget/lang/ku.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,7 @@ +/** + * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ +CKEDITOR.plugins.setLang( 'widget', 'ku', { + 'move': 'کرتەبکە و ڕایبکێشە بۆ جوڵاندن' +} ); Index: lams_central/web/ckeditor/plugins/widget/lang/nb.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/widget/lang/nb.js (revision 0) +++ lams_central/web/ckeditor/plugins/widget/lang/nb.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,7 @@ +/** + * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ +CKEDITOR.plugins.setLang( 'widget', 'nb', { + 'move': 'Klikk og dra for å flytte' +} ); Index: lams_central/web/ckeditor/plugins/widget/lang/nl.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/widget/lang/nl.js (revision 0) +++ lams_central/web/ckeditor/plugins/widget/lang/nl.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,7 @@ +/** + * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ +CKEDITOR.plugins.setLang( 'widget', 'nl', { + 'move': 'Klik en sleep om te verplaatsen' +} ); Index: lams_central/web/ckeditor/plugins/widget/lang/no.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/widget/lang/no.js (revision 0) +++ lams_central/web/ckeditor/plugins/widget/lang/no.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,7 @@ +/** + * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ +CKEDITOR.plugins.setLang( 'widget', 'no', { + 'move': 'Klikk og dra for å flytte' +} ); Index: lams_central/web/ckeditor/plugins/widget/lang/pl.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/widget/lang/pl.js (revision 0) +++ lams_central/web/ckeditor/plugins/widget/lang/pl.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,7 @@ +/** + * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ +CKEDITOR.plugins.setLang( 'widget', 'pl', { + 'move': 'Kliknij i przeciągnij, by przenieść.' +} ); Index: lams_central/web/ckeditor/plugins/widget/lang/pt-br.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/widget/lang/pt-br.js (revision 0) +++ lams_central/web/ckeditor/plugins/widget/lang/pt-br.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,7 @@ +/** + * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ +CKEDITOR.plugins.setLang( 'widget', 'pt-br', { + 'move': 'Click e arraste para mover' +} ); Index: lams_central/web/ckeditor/plugins/widget/lang/pt.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/widget/lang/pt.js (revision 0) +++ lams_central/web/ckeditor/plugins/widget/lang/pt.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,7 @@ +/** + * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ +CKEDITOR.plugins.setLang( 'widget', 'pt', { + 'move': 'Clique e arraste para mover' +} ); Index: lams_central/web/ckeditor/plugins/widget/lang/ru.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/widget/lang/ru.js (revision 0) +++ lams_central/web/ckeditor/plugins/widget/lang/ru.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,7 @@ +/** + * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ +CKEDITOR.plugins.setLang( 'widget', 'ru', { + 'move': 'Нажмите и перетащите' +} ); Index: lams_central/web/ckeditor/plugins/widget/lang/sk.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/widget/lang/sk.js (revision 0) +++ lams_central/web/ckeditor/plugins/widget/lang/sk.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,7 @@ +/** + * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ +CKEDITOR.plugins.setLang( 'widget', 'sk', { + 'move': 'Kliknite a potiahnite pre presunutie' +} ); Index: lams_central/web/ckeditor/plugins/widget/lang/sl.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/widget/lang/sl.js (revision 0) +++ lams_central/web/ckeditor/plugins/widget/lang/sl.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,7 @@ +/** + * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ +CKEDITOR.plugins.setLang( 'widget', 'sl', { + 'move': 'Kliknite in povlecite, da premaknete' +} ); Index: lams_central/web/ckeditor/plugins/widget/lang/sq.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/widget/lang/sq.js (revision 0) +++ lams_central/web/ckeditor/plugins/widget/lang/sq.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,7 @@ +/** + * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ +CKEDITOR.plugins.setLang( 'widget', 'sq', { + 'move': 'Kliko dhe tërhiqe për ta lëvizur' +} ); Index: lams_central/web/ckeditor/plugins/widget/lang/sv.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/widget/lang/sv.js (revision 0) +++ lams_central/web/ckeditor/plugins/widget/lang/sv.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,7 @@ +/** + * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ +CKEDITOR.plugins.setLang( 'widget', 'sv', { + 'move': 'Klicka och drag för att flytta' +} ); Index: lams_central/web/ckeditor/plugins/widget/lang/tr.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/widget/lang/tr.js (revision 0) +++ lams_central/web/ckeditor/plugins/widget/lang/tr.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,7 @@ +/** + * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ +CKEDITOR.plugins.setLang( 'widget', 'tr', { + 'move': 'Taşımak için, tıklayın ve sürükleyin' +} ); Index: lams_central/web/ckeditor/plugins/widget/lang/tt.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/widget/lang/tt.js (revision 0) +++ lams_central/web/ckeditor/plugins/widget/lang/tt.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,7 @@ +/** + * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ +CKEDITOR.plugins.setLang( 'widget', 'tt', { + 'move': 'Күчереп куер өчен басып шудырыгыз' +} ); Index: lams_central/web/ckeditor/plugins/widget/lang/uk.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/widget/lang/uk.js (revision 0) +++ lams_central/web/ckeditor/plugins/widget/lang/uk.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,7 @@ +/** + * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ +CKEDITOR.plugins.setLang( 'widget', 'uk', { + 'move': 'Клікніть і потягніть для переміщення' +} ); Index: lams_central/web/ckeditor/plugins/widget/lang/vi.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/widget/lang/vi.js (revision 0) +++ lams_central/web/ckeditor/plugins/widget/lang/vi.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,7 @@ +/** + * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ +CKEDITOR.plugins.setLang( 'widget', 'vi', { + 'move': 'Nhấp chuột và kéo để di chuyển' +} ); Index: lams_central/web/ckeditor/plugins/widget/lang/zh-cn.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/widget/lang/zh-cn.js (revision 0) +++ lams_central/web/ckeditor/plugins/widget/lang/zh-cn.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,7 @@ +/** + * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ +CKEDITOR.plugins.setLang( 'widget', 'zh-cn', { + 'move': '点击并拖拽以移动' +} ); Index: lams_central/web/ckeditor/plugins/widget/lang/zh.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/widget/lang/zh.js (revision 0) +++ lams_central/web/ckeditor/plugins/widget/lang/zh.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,7 @@ +/** + * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ +CKEDITOR.plugins.setLang( 'widget', 'zh', { + 'move': '拖曳以移動' +} ); Index: lams_central/web/ckeditor/plugins/widget/plugin.js =================================================================== diff -u --- lams_central/web/ckeditor/plugins/widget/plugin.js (revision 0) +++ lams_central/web/ckeditor/plugins/widget/plugin.js (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -0,0 +1,3744 @@ +/** + * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ + +/** + * @fileOverview [Widget](http://ckeditor.com/addon/widget) plugin. + */ + +'use strict'; + +( function() { + + var DRAG_HANDLER_SIZE = 15; + + CKEDITOR.plugins.add( 'widget', { + // jscs:disable maximumLineLength + lang: 'af,ar,ca,cs,cy,da,de,el,en,en-gb,eo,es,fa,fi,fr,gl,he,hr,hu,it,ja,km,ko,ku,nb,nl,no,pl,pt,pt-br,ru,sk,sl,sq,sv,tr,tt,uk,vi,zh,zh-cn', // %REMOVE_LINE_CORE% + // jscs:enable maximumLineLength + requires: 'lineutils,clipboard', + onLoad: function() { + CKEDITOR.addCss( + '.cke_widget_wrapper{' + + 'position:relative;' + + 'outline:none' + + '}' + + '.cke_widget_inline{' + + 'display:inline-block' + + '}' + + '.cke_widget_wrapper:hover>.cke_widget_element{' + + 'outline:2px solid yellow;' + + 'cursor:default' + + '}' + + '.cke_widget_wrapper:hover .cke_widget_editable{' + + 'outline:2px solid yellow' + + '}' + + '.cke_widget_wrapper.cke_widget_focused>.cke_widget_element,' + + // We need higher specificity than hover style. + '.cke_widget_wrapper .cke_widget_editable.cke_widget_editable_focused{' + + 'outline:2px solid #ace' + + '}' + + '.cke_widget_editable{' + + 'cursor:text' + + '}' + + '.cke_widget_drag_handler_container{' + + 'position:absolute;' + + 'width:' + DRAG_HANDLER_SIZE + 'px;' + + 'height:0;' + + // Initially drag handler should not be visible, until its position will be + // repositioned. #11177 + 'left:-9999px;' + + 'opacity:0.75;' + + 'transition:height 0s 0.2s;' + // Delay hiding drag handler. + // Prevent drag handler from being misplaced (#11198). + 'line-height:0' + + '}' + + '.cke_widget_wrapper:hover>.cke_widget_drag_handler_container{' + + 'height:' + DRAG_HANDLER_SIZE + 'px;' + + 'transition:none' + + '}' + + '.cke_widget_drag_handler_container:hover{' + + 'opacity:1' + + '}' + + 'img.cke_widget_drag_handler{' + + 'cursor:move;' + + 'width:' + DRAG_HANDLER_SIZE + 'px;' + + 'height:' + DRAG_HANDLER_SIZE + 'px;' + + 'display:inline-block' + + '}' + + '.cke_widget_mask{' + + 'position:absolute;' + + 'top:0;' + + 'left:0;' + + 'width:100%;' + + 'height:100%;' + + 'display:block' + + '}' + + '.cke_editable.cke_widget_dragging, .cke_editable.cke_widget_dragging *{' + + 'cursor:move !important' + + '}' + ); + }, + + beforeInit: function( editor ) { + /** + * An instance of widget repository. It contains all + * {@link CKEDITOR.plugins.widget.repository#registered registered widget definitions} and + * {@link CKEDITOR.plugins.widget.repository#instances initialized instances}. + * + * editor.widgets.add( 'someName', { + * // Widget definition... + * } ); + * + * editor.widgets.registered.someName; // -> Widget definition + * + * @since 4.3 + * @readonly + * @property {CKEDITOR.plugins.widget.repository} widgets + * @member CKEDITOR.editor + */ + editor.widgets = new Repository( editor ); + }, + + afterInit: function( editor ) { + addWidgetButtons( editor ); + setupContextMenu( editor ); + } + } ); + + /** + * Widget repository. It keeps track of all {@link #registered registered widget definitions} and + * {@link #instances initialized instances}. An instance of the repository is available under + * the {@link CKEDITOR.editor#widgets} property. + * + * @class CKEDITOR.plugins.widget.repository + * @mixins CKEDITOR.event + * @constructor Creates a widget repository instance. Note that the widget plugin automatically + * creates a repository instance which is available under the {@link CKEDITOR.editor#widgets} property. + * @param {CKEDITOR.editor} editor The editor instance for which the repository will be created. + */ + function Repository( editor ) { + /** + * The editor instance for which this repository was created. + * + * @readonly + * @property {CKEDITOR.editor} editor + */ + this.editor = editor; + + /** + * A hash of registered widget definitions (definition name => {@link CKEDITOR.plugins.widget.definition}). + * + * To register a definition use the {@link #add} method. + * + * @readonly + */ + this.registered = {}; + + /** + * An object containing initialized widget instances (widget id => {@link CKEDITOR.plugins.widget}). + * + * @readonly + */ + this.instances = {}; + + /** + * An array of selected widget instances. + * + * @readonly + * @property {CKEDITOR.plugins.widget[]} selected + */ + this.selected = []; + + /** + * The focused widget instance. See also {@link CKEDITOR.plugins.widget#event-focus} + * and {@link CKEDITOR.plugins.widget#event-blur} events. + * + * editor.on( 'selectionChange', function() { + * if ( editor.widgets.focused ) { + * // Do something when a widget is focused... + * } + * } ); + * + * @readonly + * @property {CKEDITOR.plugins.widget} focused + */ + this.focused = null; + + /** + * The widget instance that contains the nested editable which is currently focused. + * + * @readonly + * @property {CKEDITOR.plugins.widget} widgetHoldingFocusedEditable + */ + this.widgetHoldingFocusedEditable = null; + + this._ = { + nextId: 0, + upcasts: [], + upcastCallbacks: [], + filters: {} + }; + + setupWidgetsLifecycle( this ); + setupSelectionObserver( this ); + setupMouseObserver( this ); + setupKeyboardObserver( this ); + setupDragAndDrop( this ); + setupNativeCutAndCopy( this ); + } + + Repository.prototype = { + /** + * Minimum interval between selection checks. + * + * @private + */ + MIN_SELECTION_CHECK_INTERVAL: 500, + + /** + * Adds a widget definition to the repository. Fires the {@link CKEDITOR.editor#widgetDefinition} event + * which allows to modify the widget definition which is going to be registered. + * + * @param {String} name The name of the widget definition. + * @param {CKEDITOR.plugins.widget.definition} widgetDef Widget definition. + * @returns {CKEDITOR.plugins.widget.definition} + */ + add: function( name, widgetDef ) { + // Create prototyped copy of original widget definition, so we won't modify it. + widgetDef = CKEDITOR.tools.prototypedCopy( widgetDef ); + widgetDef.name = name; + + widgetDef._ = widgetDef._ || {}; + + this.editor.fire( 'widgetDefinition', widgetDef ); + + if ( widgetDef.template ) + widgetDef.template = new CKEDITOR.template( widgetDef.template ); + + addWidgetCommand( this.editor, widgetDef ); + addWidgetProcessors( this, widgetDef ); + + this.registered[ name ] = widgetDef; + + return widgetDef; + }, + + /** + * Adds a callback for element upcasting. Each callback will be executed + * for every element which is later tested by upcast methods. If a callback + * returns `false`, the element will not be upcasted. + * + * // Images with the "banner" class will not be upcasted (e.g. to the image widget). + * editor.widgets.addUpcastCallback( function( element ) { + * if ( element.name == 'img' && element.hasClass( 'banner' ) ) + * return false; + * } ); + * + * @param {Function} callback + * @param {CKEDITOR.htmlParser.element} callback.element + */ + addUpcastCallback: function( callback ) { + this._.upcastCallbacks.push( callback ); + }, + + /** + * Checks the selection to update widget states (selection and focus). + * + * This method is triggered by the {@link #event-checkSelection} event. + */ + checkSelection: function() { + var sel = this.editor.getSelection(), + selectedElement = sel.getSelectedElement(), + updater = stateUpdater( this ), + widget; + + // Widget is focused so commit and finish checking. + if ( selectedElement && ( widget = this.getByElement( selectedElement, true ) ) ) + return updater.focus( widget ).select( widget ).commit(); + + var range = sel.getRanges()[ 0 ]; + + // No ranges or collapsed range mean that nothing is selected, so commit and finish checking. + if ( !range || range.collapsed ) + return updater.commit(); + + // Range is not empty, so create walker checking for wrappers. + var walker = new CKEDITOR.dom.walker( range ), + wrapper; + + walker.evaluator = isDomWidgetWrapper; + + while ( ( wrapper = walker.next() ) ) + updater.select( this.getByElement( wrapper ) ); + + updater.commit(); + }, + + /** + * Checks if all widget instances are still present in the DOM. + * Destroys those instances that are not present. + * Reinitializes widgets on widget wrappers for which widget instances + * cannot be found. + * + * This method triggers the {@link #event-checkWidgets} event whose listeners + * can cancel the method's execution or modify its options. + * + * @param [options] The options object. + * @param {Boolean} [options.initOnlyNew] Initializes widgets only on newly wrapped + * widget elements (those which still have the `cke_widget_new` class). When this option is + * set to `true`, widgets which were invalidated (e.g. by replacing with a cloned DOM structure) + * will not be reinitialized. This makes the check faster. + * @param {Boolean} [options.focusInited] If only one widget is initialized by + * the method, it will be focused. + */ + checkWidgets: function( options ) { + this.fire( 'checkWidgets', CKEDITOR.tools.copy( options || {} ) ); + }, + + /** + * Removes the widget from the editor and moves the selection to the closest + * editable position if the widget was focused before. + * + * @param {CKEDITOR.plugins.widget} widget The widget instance to be deleted. + */ + del: function( widget ) { + if ( this.focused === widget ) { + var editor = widget.editor, + range = editor.createRange(), + found; + + // If haven't found place for caret on the default side, + // try to find it on the other side. + if ( !( found = range.moveToClosestEditablePosition( widget.wrapper, true ) ) ) + found = range.moveToClosestEditablePosition( widget.wrapper, false ); + + if ( found ) + editor.getSelection().selectRanges( [ range ] ); + } + + widget.wrapper.remove(); + this.destroy( widget, true ); + }, + + /** + * Destroys the widget instance. + * + * @param {CKEDITOR.plugins.widget} widget The widget instance to be destroyed. + * @param {Boolean} [offline] Whether the widget is offline (detached from the DOM tree) — + * in this case the DOM (attributes, classes, etc.) will not be cleaned up. + */ + destroy: function( widget, offline ) { + if ( this.widgetHoldingFocusedEditable === widget ) + setFocusedEditable( this, widget, null, offline ); + + widget.destroy( offline ); + delete this.instances[ widget.id ]; + this.fire( 'instanceDestroyed', widget ); + }, + + /** + * Destroys all widget instances. + * + * @param {Boolean} [offline] Whether the widgets are offline (detached from the DOM tree) — + * in this case the DOM (attributes, classes, etc.) will not be cleaned up. + */ + destroyAll: function( offline ) { + var instances = this.instances, + widget; + + for ( var id in instances ) { + widget = instances[ id ]; + this.destroy( widget, offline ); + } + }, + + /** + * Finalizes a process of widget creation. This includes: + * + * * inserting widget element into editor, + * * marking widget instance as ready (see {@link CKEDITOR.plugins.widget#event-ready}), + * * focusing widget instance. + * + * This method is used by the default widget's command and is called + * after widget's dialog (if set) is closed. It may also be used in a + * customized process of widget creation and insertion. + * + * widget.once( 'edit', function() { + * // Finalize creation only of not ready widgets. + * if ( widget.isReady() ) + * return; + * + * // Cancel edit event to prevent automatic widget insertion. + * evt.cancel(); + * + * CustomDialog.open( widget.data, function saveCallback( savedData ) { + * // Cache the container, because widget may be destroyed while saving data, + * // if this process will require some deep transformations. + * var container = widget.wrapper.getParent(); + * + * widget.setData( savedData ); + * + * // Widget will be retrieved from container and inserted into editor. + * editor.widgets.finalizeCreation( container ); + * } ); + * } ); + * + * @param {CKEDITOR.dom.element/CKEDITOR.dom.documentFragment} container The element + * or document fragment which contains widget wrapper. The container is used, so before + * finalizing creation the widget can be freely transformed (even destroyed and reinitialized). + */ + finalizeCreation: function( container ) { + var wrapper = container.getFirst(); + if ( wrapper && isDomWidgetWrapper( wrapper ) ) { + this.editor.insertElement( wrapper ); + + var widget = this.getByElement( wrapper ); + // Fire postponed #ready event. + widget.ready = true; + widget.fire( 'ready' ); + widget.focus(); + } + }, + + /** + * Finds a widget instance which contains a given element. The element will be the {@link CKEDITOR.plugins.widget#wrapper wrapper} + * of the returned widget or a descendant of this {@link CKEDITOR.plugins.widget#wrapper wrapper}. + * + * editor.widgets.getByElement( someWidget.wrapper ); // -> someWidget + * editor.widgets.getByElement( someWidget.parts.caption ); // -> someWidget + * + * // Check wrapper only: + * editor.widgets.getByElement( someWidget.wrapper, true ); // -> someWidget + * editor.widgets.getByElement( someWidget.parts.caption, true ); // -> null + * + * @param {CKEDITOR.dom.element} element The element to be checked. + * @param {Boolean} [checkWrapperOnly] If set to `true`, the method will not check wrappers' descendants. + * @returns {CKEDITOR.plugins.widget} The widget instance or `null`. + */ + getByElement: ( function() { + var validWrapperElements = { div: 1, span: 1 }; + function getWidgetId( element ) { + return element.is( validWrapperElements ) && element.data( 'cke-widget-id' ); + } + + return function( element, checkWrapperOnly ) { + if ( !element ) + return null; + + var id = getWidgetId( element ); + + // There's no need to check element parents if element is a wrapper. + if ( !checkWrapperOnly && !id ) { + var limit = this.editor.editable(); + + // Try to find a closest ascendant which is a widget wrapper. + do { + element = element.getParent(); + } while ( element && !element.equals( limit ) && !( id = getWidgetId( element ) ) ); + } + + return this.instances[ id ] || null; + }; + } )(), + + /** + * Initializes a widget on a given element if the widget has not been initialized on it yet. + * + * @param {CKEDITOR.dom.element} element The future widget element. + * @param {String/CKEDITOR.plugins.widget.definition} [widgetDef] Name of a widget or a widget definition. + * The widget definition should be previously registered by using the + * {@link CKEDITOR.plugins.widget.repository#add} method. + * @param [startupData] Widget startup data (has precedence over default one). + * @returns {CKEDITOR.plugins.widget} The widget instance or `null` if a widget could not be initialized on + * a given element. + */ + initOn: function( element, widgetDef, startupData ) { + if ( !widgetDef ) + widgetDef = this.registered[ element.data( 'widget' ) ]; + else if ( typeof widgetDef == 'string' ) + widgetDef = this.registered[ widgetDef ]; + + if ( !widgetDef ) + return null; + + // Wrap element if still wasn't wrapped (was added during runtime by method that skips dataProcessor). + var wrapper = this.wrapElement( element, widgetDef.name ); + + if ( wrapper ) { + // Check if widget wrapper is new (widget hasn't been initialized on it yet). + // This class will be removed by widget constructor to avoid locking snapshot twice. + if ( wrapper.hasClass( 'cke_widget_new' ) ) { + var widget = new Widget( this, this._.nextId++, element, widgetDef, startupData ); + + // Widget could be destroyed when initializing it. + if ( widget.isInited() ) { + this.instances[ widget.id ] = widget; + + return widget; + } else { + return null; + } + } + + // Widget already has been initialized, so try to get widget by element. + // Note - it may happen that other instance will returned than the one created above, + // if for example widget was destroyed and reinitialized. + return this.getByElement( element ); + } + + // No wrapper means that there's no widget for this element. + return null; + }, + + /** + * Initializes widgets on all elements which were wrapped by {@link #wrapElement} and + * have not been initialized yet. + * + * @param {CKEDITOR.dom.element} [container=editor.editable()] The container which will be checked for not + * initialized widgets. Defaults to editor's {@link CKEDITOR.editor#editable editable} element. + * @returns {CKEDITOR.plugins.widget[]} Array of widget instances which have been initialized. + * Note: Only first-level widgets are returned — without nested widgets. + */ + initOnAll: function( container ) { + var newWidgets = ( container || this.editor.editable() ).find( '.cke_widget_new' ), + newInstances = [], + instance; + + for ( var i = newWidgets.count(); i--; ) { + instance = this.initOn( newWidgets.getItem( i ).getFirst( isDomWidgetElement ) ); + if ( instance ) + newInstances.push( instance ); + } + + return newInstances; + }, + + /** + * Parses element classes string and returns an object + * whose keys contain class names. Skips all `cke_*` classes. + * + * This method is used by the {@link CKEDITOR.plugins.widget#getClasses} method and + * may be used when overriding that method. + * + * @since 4.4 + * @param {String} classes String (value of `class` attribute). + * @returns {Object} Object containing classes or `null` if no classes found. + */ + parseElementClasses: function( classes ) { + if ( !classes ) + return null; + + classes = CKEDITOR.tools.trim( classes ).split( /\s+/ ); + + var cl, + obj = {}, + hasClasses = 0; + + while ( ( cl = classes.pop() ) ) { + if ( cl.indexOf( 'cke_' ) == -1 ) + obj[ cl ] = hasClasses = 1; + } + + return hasClasses ? obj : null; + }, + + /** + * Wraps an element with a widget's non-editable container. + * + * If this method is called on an {@link CKEDITOR.htmlParser.element}, then it will + * also take care of fixing the DOM after wrapping (the wrapper may not be allowed in element's parent). + * + * @param {CKEDITOR.dom.element/CKEDITOR.htmlParser.element} element The widget element to be wrapped. + * @param {String} [widgetName] The name of the widget definition. Defaults to element's `data-widget` + * attribute value. + * @returns {CKEDITOR.dom.element/CKEDITOR.htmlParser.element} The wrapper element or `null` if + * the widget definition of this name is not registered. + */ + wrapElement: function( element, widgetName ) { + var wrapper = null, + widgetDef, + isInline; + + if ( element instanceof CKEDITOR.dom.element ) { + widgetDef = this.registered[ widgetName || element.data( 'widget' ) ]; + if ( !widgetDef ) + return null; + + // Do not wrap already wrapped element. + wrapper = element.getParent(); + if ( wrapper && wrapper.type == CKEDITOR.NODE_ELEMENT && wrapper.data( 'cke-widget-wrapper' ) ) + return wrapper; + + // If attribute isn't already set (e.g. for pasted widget), set it. + if ( !element.hasAttribute( 'data-cke-widget-keep-attr' ) ) + element.data( 'cke-widget-keep-attr', element.data( 'widget' ) ? 1 : 0 ); + if ( widgetName ) + element.data( 'widget', widgetName ); + + isInline = isWidgetInline( widgetDef, element.getName() ); + + wrapper = new CKEDITOR.dom.element( isInline ? 'span' : 'div' ); + wrapper.setAttributes( getWrapperAttributes( isInline ) ); + + wrapper.data( 'cke-display-name', widgetDef.pathName ? widgetDef.pathName : element.getName() ); + + // Replace element unless it is a detached one. + if ( element.getParent( true ) ) + wrapper.replace( element ); + element.appendTo( wrapper ); + } + else if ( element instanceof CKEDITOR.htmlParser.element ) { + widgetDef = this.registered[ widgetName || element.attributes[ 'data-widget' ] ]; + if ( !widgetDef ) + return null; + + wrapper = element.parent; + if ( wrapper && wrapper.type == CKEDITOR.NODE_ELEMENT && wrapper.attributes[ 'data-cke-widget-wrapper' ] ) + return wrapper; + + // If attribute isn't already set (e.g. for pasted widget), set it. + if ( !( 'data-cke-widget-keep-attr' in element.attributes ) ) + element.attributes[ 'data-cke-widget-keep-attr' ] = element.attributes[ 'data-widget' ] ? 1 : 0; + if ( widgetName ) + element.attributes[ 'data-widget' ] = widgetName; + + isInline = isWidgetInline( widgetDef, element.name ); + + wrapper = new CKEDITOR.htmlParser.element( isInline ? 'span' : 'div', getWrapperAttributes( isInline ) ); + + wrapper.attributes[ 'data-cke-display-name' ] = widgetDef.pathName ? widgetDef.pathName : element.name; + + var parent = element.parent, + index; + + // Don't detach already detached element. + if ( parent ) { + index = element.getIndex(); + element.remove(); + } + + wrapper.add( element ); + + // Insert wrapper fixing DOM (splitting parents if wrapper is not allowed inside them). + parent && insertElement( parent, index, wrapper ); + } + + return wrapper; + }, + + // Expose for tests. + _tests_getNestedEditable: getNestedEditable, + _tests_createEditableFilter: createEditableFilter + }; + + CKEDITOR.event.implementOn( Repository.prototype ); + + /** + * An event fired when a widget instance is created, but before it is fully initialized. + * + * @event instanceCreated + * @param {CKEDITOR.plugins.widget} data The widget instance. + */ + + /** + * An event fired when a widget instance was destroyed. + * + * See also {@link CKEDITOR.plugins.widget#event-destroy}. + * + * @event instanceDestroyed + * @param {CKEDITOR.plugins.widget} data The widget instance. + */ + + /** + * An event fired to trigger the selection check. + * + * See the {@link #method-checkSelection} method. + * + * @event checkSelection + */ + + /** + * An event fired by the the {@link #method-checkWidgets} method. + * + * It can be canceled in order to stop the {@link #method-checkWidgets} + * method execution or the event listener can modify the method's options. + * + * @event checkWidgets + * @param [data] + * @param {Boolean} [data.initOnlyNew] Initialize widgets only on newly wrapped + * widget elements (those which still have the `cke_widget_new` class). When this option is + * set to `true`, widgets which were invalidated (e.g. by replacing with a cloned DOM structure) + * will not be reinitialized. This makes the check faster. + * @param {Boolean} [data.focusInited] If only one widget is initialized by + * the method, it will be focused. + */ + + + /** + * An instance of a widget. Together with {@link CKEDITOR.plugins.widget.repository} these + * two classes constitute the core of the Widget System. + * + * Note that neither the repository nor the widget instances can be created by using their constructors. + * A repository instance is automatically set up by the Widget plugin and is accessible under + * {@link CKEDITOR.editor#widgets}, while widget instances are created and destroyed by the repository. + * + * To create a widget, first you need to {@link CKEDITOR.plugins.widget.repository#add register} its + * {@link CKEDITOR.plugins.widget.definition definition}: + * + * editor.widgets.add( 'simplebox', { + * upcast: function( element ) { + * // Defines which elements will become widgets. + * if ( element.hasClass( 'simplebox' ) ) + * return true; + * }, + * init: function() { + * // ... + * } + * } ); + * + * Once the widget definition is registered, widgets will be automatically + * created when loading data: + * + * editor.setData( '
foo
', function() { + * console.log( editor.widgets.instances ); // -> An object containing one instance. + * } ); + * + * It is also possible to create instances during runtime by using a command + * (if a {@link CKEDITOR.plugins.widget.definition#template} property was defined): + * + * // You can execute an automatically defined command to + * // insert a new simplebox widget or edit the one currently focused. + * editor.execCommand( 'simplebox' ); + * + * Or in a completely custom way: + * + * var element = editor.document.createElement( 'div' ); + * editor.insertElement( element ); + * var widget = editor.widgets.initOn( element, 'simplebox' ); + * + * @since 4.3 + * @class CKEDITOR.plugins.widget + * @mixins CKEDITOR.event + * @extends CKEDITOR.plugins.widget.definition + * @constructor Creates an instance of the widget class. Do not use it directly, but instead initialize widgets + * by using the {@link CKEDITOR.plugins.widget.repository#initOn} method or by the upcasting system. + * @param {CKEDITOR.plugins.widget.repository} widgetsRepo + * @param {Number} id Unique ID of this widget instance. + * @param {CKEDITOR.dom.element} element The widget element. + * @param {CKEDITOR.plugins.widget.definition} widgetDef Widget's registered definition. + * @param [startupData] Initial widget data. This data object will overwrite the default data and + * the data loaded from the DOM. + */ + function Widget( widgetsRepo, id, element, widgetDef, startupData ) { + var editor = widgetsRepo.editor; + + // Extend this widget with widgetDef-specific methods and properties. + CKEDITOR.tools.extend( this, widgetDef, { + /** + * The editor instance. + * + * @readonly + * @property {CKEDITOR.editor} + */ + editor: editor, + + /** + * This widget's unique (per editor instance) ID. + * + * @readonly + * @property {Number} + */ + id: id, + + /** + * Whether this widget is an inline widget (based on an inline element unless + * forced otherwise by {@link CKEDITOR.plugins.widget.definition#inline}). + * + * **Note:** This option does not allow to turn a block element into an inline widget. + * However, it makes it possible to turn an inline element into a block widget or to + * force a correct type in case when automatic recognition fails. + * + * @readonly + * @property {Boolean} + */ + inline: element.getParent().getName() == 'span', + + /** + * The widget element — the element on which the widget was initialized. + * + * @readonly + * @property {CKEDITOR.dom.element} element + */ + element: element, + + /** + * Widget's data object. + * + * The data can only be set by using the {@link #setData} method. + * Changes made to the data fire the {@link #event-data} event. + * + * @readonly + */ + data: CKEDITOR.tools.extend( {}, typeof widgetDef.defaults == 'function' ? widgetDef.defaults() : widgetDef.defaults ), + + /** + * Indicates if a widget is data-ready. Set to `true` when data from all sources + * ({@link CKEDITOR.plugins.widget.definition#defaults}, set in the + * {@link #init} method, loaded from the widget's element and startup data coming from the constructor) + * are finally loaded. This is immediately followed by the first {@link #event-data}. + * + * @readonly + */ + dataReady: false, + + /** + * Whether a widget instance was initialized. This means that: + * + * * An instance was created, + * * Its properties were set, + * * The `init` method was executed. + * + * **Note**: The first {@link #event-data} event could not be fired yet which + * means that the widget's DOM has not been set up yet. Wait for the {@link #event-ready} + * event to be notified when a widget is fully initialized and ready. + * + * **Note**: Use the {@link #isInited} method to check whether a widget is initialized and + * has not been destroyed. + * + * @readonly + */ + inited: false, + + /** + * Whether a widget instance is ready. This means that the widget is {@link #inited} and + * that its DOM was finally set up. + * + * **Note:** Use the {@link #isReady} method to check whether a widget is ready and + * has not been destroyed. + * + * @readonly + */ + ready: false, + + // Revert what widgetDef could override (automatic #edit listener). + edit: Widget.prototype.edit, + + /** + * The nested editable element which is currently focused. + * + * @readonly + * @property {CKEDITOR.plugins.widget.nestedEditable} + */ + focusedEditable: null, + + /** + * The widget definition from which this instance was created. + * + * @readonly + * @property {CKEDITOR.plugins.widget.definition} definition + */ + definition: widgetDef, + + /** + * Link to the widget repository which created this instance. + * + * @readonly + * @property {CKEDITOR.plugins.widget.repository} repository + */ + repository: widgetsRepo, + + draggable: widgetDef.draggable !== false, + + // WAAARNING: Overwrite widgetDef's priv object, because otherwise violent unicorn's gonna visit you. + _: { + downcastFn: ( widgetDef.downcast && typeof widgetDef.downcast == 'string' ) ? + widgetDef.downcasts[ widgetDef.downcast ] : widgetDef.downcast + } + }, true ); + + /** + * An object of widget component elements. + * + * For every `partName => selector` pair in {@link CKEDITOR.plugins.widget.definition#parts}, + * one `partName => element` pair is added to this object during the widget initialization. + * + * @readonly + * @property {Object} parts + */ + + /** + * The template which will be used to create a new widget element (when the widget's command is executed). + * It will be populated with {@link #defaults default values}. + * + * @readonly + * @property {CKEDITOR.template} template + */ + + /** + * The widget wrapper — a non-editable `div` or `span` element (depending on {@link #inline}) + * which is a parent of the {@link #element} and widget compontents like the drag handler and the {@link #mask}. + * It is the outermost widget element. + * + * @readonly + * @property {CKEDITOR.dom.element} wrapper + */ + + widgetsRepo.fire( 'instanceCreated', this ); + + setupWidget( this, widgetDef ); + + this.init && this.init(); + + // Finally mark widget as inited. + this.inited = true; + + setupWidgetData( this, startupData ); + + // If at some point (e.g. in #data listener) widget hasn't been destroyed + // and widget is already attached to document then fire #ready. + if ( this.isInited() && editor.editable().contains( this.wrapper ) ) { + this.ready = true; + this.fire( 'ready' ); + } + } + + Widget.prototype = { + /** + * Adds a class to the widget element. This method is used by + * the {@link #applyStyle} method and should be overriden by widgets + * which should handle classes differently (e.g. add them to other elements). + * + * **Note**: This method should not be used directly. Use the {@link #setData} method to + * set the `classes` property. Read more in the {@link #setData} documentation. + * + * See also: {@link #removeClass}, {@link #hasClass}, {@link #getClasses}. + * + * @since 4.4 + * @param {String} className The class name to be added. + */ + addClass: function( className ) { + this.element.addClass( className ); + }, + + /** + * Applies the specified style to the widget. It is highly recommended to use the + * {@link CKEDITOR.editor#applyStyle} or {@link CKEDITOR.style#apply} methods instead of + * using this method directly, because unlike editor's and style's methods, this one + * does not perform any checks. + * + * By default this method handles only classes defined in the style. It clones existing + * classes which are stored in the {@link #property-data widget data}'s `classes` property, + * adds new classes, and calls the {@link #setData} method if at least one new class was added. + * Then, using the {@link #event-data} event listener widget applies modifications passing + * new classes to the {@link #addClass} method. + * + * If you need to handle classes differently than in the default way, you can override the + * {@link #addClass} and related methods. You can also handle other style properties than `classes` + * by overriding this method. + * + * See also: {@link #checkStyleActive}, {@link #removeStyle}. + * + * @since 4.4 + * @param {CKEDITOR.style} style The custom widget style to be applied. + */ + applyStyle: function( style ) { + applyRemoveStyle( this, style, 1 ); + }, + + /** + * Checks if the specified style is applied to this widget. It is highly recommended to use the + * {@link CKEDITOR.style#checkActive} method instead of using this method directly, + * because unlike style's method, this one does not perform any checks. + * + * By default this method handles only classes defined in the style and passes + * them to the {@link #hasClass} method. You can override these methods to handle classes + * differently or to handle more of the style properties. + * + * See also: {@link #applyStyle}, {@link #removeStyle}. + * + * @since 4.4 + * @param {CKEDITOR.style} style The custom widget style to be checked. + * @returns {Boolean} Whether the style is applied to this widget. + */ + checkStyleActive: function( style ) { + var classes = getStyleClasses( style ), + cl; + + if ( !classes ) + return false; + + while ( ( cl = classes.pop() ) ) { + if ( !this.hasClass( cl ) ) + return false; + } + return true; + }, + + /** + * Destroys this widget instance. + * + * Use {@link CKEDITOR.plugins.widget.repository#destroy} when possible instead of this method. + * + * This method fires the {#event-destroy} event. + * + * @param {Boolean} [offline] Whether a widget is offline (detached from the DOM tree) — + * in this case the DOM (attributes, classes, etc.) will not be cleaned up. + */ + destroy: function( offline ) { + this.fire( 'destroy' ); + + if ( this.editables ) { + for ( var name in this.editables ) + this.destroyEditable( name, offline ); + } + + if ( !offline ) { + if ( this.element.data( 'cke-widget-keep-attr' ) == '0' ) + this.element.removeAttribute( 'data-widget' ); + this.element.removeAttributes( [ 'data-cke-widget-data', 'data-cke-widget-keep-attr' ] ); + this.element.removeClass( 'cke_widget_element' ); + this.element.replace( this.wrapper ); + } + + this.wrapper = null; + }, + + /** + * Destroys a nested editable. + * + * @param {String} editableName Nested editable name. + * @param {Boolean} [offline] See {@link #method-destroy} method. + */ + destroyEditable: function( editableName, offline ) { + var editable = this.editables[ editableName ]; + + editable.removeListener( 'focus', onEditableFocus ); + editable.removeListener( 'blur', onEditableBlur ); + this.editor.focusManager.remove( editable ); + + if ( !offline ) { + editable.removeClass( 'cke_widget_editable' ); + editable.removeClass( 'cke_widget_editable_focused' ); + editable.removeAttributes( [ 'contenteditable', 'data-cke-widget-editable', 'data-cke-enter-mode' ] ); + } + + delete this.editables[ editableName ]; + }, + + /** + * Starts widget editing. + * + * This method fires the {@link CKEDITOR.plugins.widget#event-edit} event + * which may be canceled in order to prevent it from opening a dialog window. + * + * The dialog window name is obtained from the event's data `dialog` property or + * from {@link CKEDITOR.plugins.widget.definition#dialog}. + * + * @returns {Boolean} Returns `true` if a dialog window was opened. + */ + edit: function() { + var evtData = { dialog: this.dialog }, + that = this; + + // Edit event was blocked, but there's no dialog to be automatically opened. + if ( this.fire( 'edit', evtData ) === false || !evtData.dialog ) + return false; + + this.editor.openDialog( evtData.dialog, function( dialog ) { + var showListener, + okListener; + + // Allow to add a custom dialog handler. + if ( that.fire( 'dialog', dialog ) === false ) + return; + + showListener = dialog.on( 'show', function() { + dialog.setupContent( that ); + } ); + + okListener = dialog.on( 'ok', function() { + // Commit dialog's fields, but prevent from + // firing data event for every field. Fire only one, + // bulk event at the end. + var dataChanged, + dataListener = that.on( 'data', function( evt ) { + dataChanged = 1; + evt.cancel(); + }, null, null, 0 ); + + // Create snapshot preceeding snapshot with changed widget... + // TODO it should not be required, but it is and I found similar + // code in dialog#ok listener in dialog/plugin.js. + that.editor.fire( 'saveSnapshot' ); + dialog.commitContent( that ); + + dataListener.removeListener(); + if ( dataChanged ) { + that.fire( 'data', that.data ); + that.editor.fire( 'saveSnapshot' ); + } + } ); + + dialog.once( 'hide', function() { + showListener.removeListener(); + okListener.removeListener(); + } ); + } ); + + return true; + }, + + /** + * Returns widget element classes parsed to an object. This method + * is used to populate the `classes` property of widget's {@link #property-data}. + * + * This method reuses {@link CKEDITOR.plugins.widget.repository#parseElementClasses}. + * It should be overriden if a widget should handle classes differently (e.g. on other elements). + * + * See also: {@link #removeClass}, {@link #addClass}, {@link #hasClass}. + * + * @since 4.4 + * @returns {Object} + */ + getClasses: function() { + return this.repository.parseElementClasses( this.element.getAttribute( 'class' ) ); + }, + + /** + * Checks if the widget element has specified class. This method is used by + * the {@link #checkStyleActive} method and should be overriden by widgets + * which should handle classes differently (e.g. on other elements). + * + * See also: {@link #removeClass}, {@link #addClass}, {@link #getClasses}. + * + * @since 4.4 + * @param {String} className The class to be checked. + * @param {Boolean} Whether a widget has specified class. + */ + hasClass: function( className ) { + return this.element.hasClass( className ); + }, + + /** + * Initializes a nested editable. + * + * **Note**: Only elements from {@link CKEDITOR.dtd#$editable} may become editables. + * + * @param {String} editableName The nested editable name. + * @param {CKEDITOR.plugins.widget.nestedEditable.definition} definition The definition of the nested editable. + * @returns {Boolean} Whether an editable was successfully initialized. + */ + initEditable: function( editableName, definition ) { + var editable = this.wrapper.findOne( definition.selector ); + + if ( editable && editable.is( CKEDITOR.dtd.$editable ) ) { + editable = new NestedEditable( this.editor, editable, { + filter: createEditableFilter.call( this.repository, this.name, editableName, definition ) + } ); + this.editables[ editableName ] = editable; + + editable.setAttributes( { + contenteditable: 'true', + 'data-cke-widget-editable': editableName, + 'data-cke-enter-mode': editable.enterMode + } ); + + if ( editable.filter ) + editable.data( 'cke-filter', editable.filter.id ); + + editable.addClass( 'cke_widget_editable' ); + // This class may be left when d&ding widget which + // had focused editable. Clean this class here, not in + // cleanUpWidgetElement for performance and code size reasons. + editable.removeClass( 'cke_widget_editable_focused' ); + + if ( definition.pathName ) + editable.data( 'cke-display-name', definition.pathName ); + + this.editor.focusManager.add( editable ); + editable.on( 'focus', onEditableFocus, this ); + CKEDITOR.env.ie && editable.on( 'blur', onEditableBlur, this ); + + // Finally, process editable's data. This data wasn't processed when loading + // editor's data, becuase they need to be processed separately, with its own filters and settings. + editable.setData( editable.getHtml() ); + + return true; + } + + return false; + }, + + /** + * Checks if a widget has already been initialized and has not been destroyed yet. + * + * See {@link #inited} for more details. + * + * @returns {Boolean} + */ + isInited: function() { + return !!( this.wrapper && this.inited ); + }, + + /** + * Checks if a widget is ready and has not been destroyed yet. + * + * See {@link #property-ready} for more details. + * + * @returns {Boolean} + */ + isReady: function() { + return this.isInited() && this.ready; + }, + + /** + * Focuses a widget by selecting it. + */ + focus: function() { + var sel = this.editor.getSelection(); + + // Fake the selection before focusing editor, to avoid unpreventable viewports scrolling + // on Webkit/Blink/IE which is done because there's no selection or selection was somewhere else than widget. + if ( sel ) { + var isDirty = this.editor.checkDirty(); + + sel.fake( this.wrapper ); + + !isDirty && this.editor.resetDirty(); + } + + // Always focus editor (not only when focusManger.hasFocus is false) (because of #10483). + this.editor.focus(); + }, + + /** + * Removes a class from the widget element. This method is used by + * the {@link #removeStyle} method and should be overriden by widgets + * which should handle classes differently (e.g. on other elements). + * + * **Note**: This method should not be used directly. Use the {@link #setData} method to + * set the `classes` property. Read more in the {@link #setData} documentation. + * + * See also: {@link #hasClass}, {@link #addClass}. + * + * @since 4.4 + * @param {String} className The class to be removed. + */ + removeClass: function( className ) { + this.element.removeClass( className ); + }, + + /** + * Removes the specified style from the widget. It is highly recommended to use the + * {@link CKEDITOR.editor#removeStyle} or {@link CKEDITOR.style#remove} methods instead of + * using this method directly, because unlike editor's and style's methods, this one + * does not perform any checks. + * + * Read more about how applying/removing styles works in the {@link #applyStyle} method documentation. + * + * See also {@link #checkStyleActive}, {@link #applyStyle}, {@link #getClasses}. + * + * @since 4.4 + * @param {CKEDITOR.style} style The custom widget style to be removed. + */ + removeStyle: function( style ) { + applyRemoveStyle( this, style, 0 ); + }, + + /** + * Sets widget value(s) in the {@link #property-data} object. + * If the given value(s) modifies current ones, the {@link #event-data} event is fired. + * + * this.setData( 'align', 'left' ); + * this.data.align; // -> 'left' + * + * this.setData( { align: 'right', opened: false } ); + * this.data.align; // -> 'right' + * this.data.opened; // -> false + * + * Set values are stored in {@link #element}'s attribute (`data-cke-widget-data`), + * in a JSON string, therefore {@link #property-data} should contain + * only serializable data. + * + * **Note:** A special data property, `classes`, exists. It contains an object with + * classes which were returned by the {@link #getClasses} method during the widget initialization. + * This property is then used by the {@link #applyStyle} and {@link #removeStyle} methods. + * When it is changed (the reference to object must be changed!), the widget updates its classes by + * using the {@link #addClass} and {@link #removeClass} methods. + * + * // Adding a new class. + * var classes = CKEDITOR.tools.clone( widget.data.classes ); + * classes.newClass = 1; + * widget.setData( 'classes', classes ); + * + * // Removing a class. + * var classes = CKEDITOR.tools.clone( widget.data.classes ); + * delete classes.newClass; + * widget.setData( 'classes', classes ); + * + * @param {String/Object} keyOrData + * @param {Object} value + * @chainable + */ + setData: function( key, value ) { + var data = this.data, + modified = 0; + + if ( typeof key == 'string' ) { + if ( data[ key ] !== value ) { + data[ key ] = value; + modified = 1; + } + } + else { + var newData = key; + + for ( key in newData ) { + if ( data[ key ] !== newData[ key ] ) { + modified = 1; + data[ key ] = newData[ key ]; + } + } + } + + // Block firing data event and overwriting data element before setupWidgetData is executed. + if ( modified && this.dataReady ) { + writeDataToElement( this ); + this.fire( 'data', data ); + } + + return this; + }, + + /** + * Changes the widget's focus state. This method is executed automatically after + * a widget has been focused by the {@link #method-focus} method or a selection was moved + * out of the widget. + * + * @param {Boolean} selected Whether to select or deselect this widget. + * @chainable + */ + setFocused: function( focused ) { + this.wrapper[ focused ? 'addClass' : 'removeClass' ]( 'cke_widget_focused' ); + this.fire( focused ? 'focus' : 'blur' ); + return this; + }, + + /** + * Changes the widget's select state. This method is executed automatically after + * a widget has been selected by the {@link #method-focus} method or the selection + * was moved out of widget. + * + * @param {Boolean} selected Whether to select or deselect this widget. + * @chainable + */ + setSelected: function( selected ) { + this.wrapper[ selected ? 'addClass' : 'removeClass' ]( 'cke_widget_selected' ); + this.fire( selected ? 'select' : 'deselect' ); + return this; + }, + + /** + * Repositions drag handler according to the widget's element position. Should be called from events, like mouseover. + */ + updateDragHandlerPosition: function() { + var editor = this.editor, + domElement = this.element.$, + oldPos = this._.dragHandlerOffset, + newPos = { + x: domElement.offsetLeft, + y: domElement.offsetTop - DRAG_HANDLER_SIZE + }; + + if ( oldPos && newPos.x == oldPos.x && newPos.y == oldPos.y ) + return; + + // We need to make sure that dirty state is not changed (#11487). + var initialDirty = editor.checkDirty(); + + editor.fire( 'lockSnapshot' ); + this.dragHandlerContainer.setStyles( { + top: newPos.y + 'px', + left: newPos.x + 'px' + } ); + editor.fire( 'unlockSnapshot' ); + !initialDirty && editor.resetDirty(); + + this._.dragHandlerOffset = newPos; + } + }; + + CKEDITOR.event.implementOn( Widget.prototype ); + + /** + * An event fired when a widget is ready (fully initialized). This event is fired after: + * + * * {@link #init} is called, + * * The first {@link #event-data} event is fired, + * * A widget is attached to the document. + * + * Therefore, in case of widget creation with a command which opens a dialog window, this event + * will be delayed after the dialog window is closed and the widget is finally inserted into the document. + * + * **Note**: If your widget does not use automatic dialog window binding (i.e. you open the dialog window manually) + * or another situation in which the widget wrapper is not attached to document at the time when it is + * initialized occurs, you need to take care of firing {@link #event-ready} yourself. + * + * See also {@link #property-ready} and {@link #property-inited} properties, and + * {@link #isReady} and {@link #isInited} methods. + * + * @event ready + */ + + /** + * An event fired when a widget is about to be destroyed, but before it is + * fully torn down. + * + * @event destroy + */ + + /** + * An event fired when a widget is focused. + * + * Widget can be focused by executing {@link #method-focus}. + * + * @event focus + */ + + /** + * An event fired when a widget is blurred. + * + * @event blur + */ + + /** + * An event fired when a widget is selected. + * + * @event select + */ + + /** + * An event fired when a widget is deselected. + * + * @event deselect + */ + + /** + * An event fired by the {@link #method-edit} method. It can be canceled + * in order to stop the default action (opening a dialog window and/or + * {@link CKEDITOR.plugins.widget.repository#finalizeCreation finalizing widget creation}). + * + * @event edit + * @param data + * @param {String} data.dialog Defaults to {@link CKEDITOR.plugins.widget.definition#dialog} + * and can be changed or set by the listener. + */ + + /** + * An event fired when a dialog window for widget editing is opened. + * This event can be canceled in order to handle the editing dialog in a custom manner. + * + * @event dialog + * @param {CKEDITOR.dialog} data The opened dialog window instance. + */ + + /** + * An event fired when a key is pressed on a focused widget. + * This event is forwarded from the {@link CKEDITOR.editor#key} event and + * has the ability to block editor keystrokes if it is canceled. + * + * @event key + * @param data + * @param {Number} data.keyCode A number representing the key code (or combination). + */ + + /** + * An event fired when a widget is double clicked. + * + * **Note:** If a default editing action is executed on double click (i.e. a widget has a + * {@link CKEDITOR.plugins.widget.definition#dialog dialog} defined and the {@link #event-doubleclick} event was not + * canceled), this event will be automatically canceled, so a listener added with the default priority (10) + * will not be executed. Use a listener with low priority (e.g. 5) to be sure that it will be executed. + * + * widget.on( 'doubleclick', function( evt ) { + * console.log( 'widget#doubleclick' ); + * }, null, null, 5 ); + * + * If your widget handles double click in a special way (so the default editing action is not executed), + * make sure you cancel this event, because otherwise it will be propagated to {@link CKEDITOR.editor#doubleclick} + * and another feature may step in (e.g. a Link dialog window may be opened if your widget was inside a link). + * + * @event doubleclick + * @param data + * @param {CKEDITOR.dom.element} data.element The double-clicked element. + */ + + /** + * An event fired when the context menu is opened for a widget. + * + * @event contextMenu + * @param data The object containing context menu options to be added + * for this widget. See {@link CKEDITOR.plugins.contextMenu#addListener}. + */ + + /** + * An event fired when the widget data changed. See the {@link #setData} method and the {@link #property-data} property. + * + * @event data + */ + + + + /** + * The wrapper class for editable elements inside widgets. + * + * Do not use directly. Use {@link CKEDITOR.plugins.widget.definition#editables} or + * {@link CKEDITOR.plugins.widget#initEditable}. + * + * @class CKEDITOR.plugins.widget.nestedEditable + * @extends CKEDITOR.dom.element + * @constructor + * @param {CKEDITOR.editor} editor + * @param {CKEDITOR.dom.element} element + * @param config + * @param {CKEDITOR.filter} [config.filter] + */ + function NestedEditable( editor, element, config ) { + // Call the base constructor. + CKEDITOR.dom.element.call( this, element.$ ); + this.editor = editor; + var filter = this.filter = config.filter; + + // If blockless editable - always use BR mode. + if ( !CKEDITOR.dtd[ this.getName() ].p ) + this.enterMode = this.shiftEnterMode = CKEDITOR.ENTER_BR; + else { + this.enterMode = filter ? filter.getAllowedEnterMode( editor.enterMode ) : editor.enterMode; + this.shiftEnterMode = filter ? filter.getAllowedEnterMode( editor.shiftEnterMode, true ) : editor.shiftEnterMode; + } + } + + NestedEditable.prototype = CKEDITOR.tools.extend( CKEDITOR.tools.prototypedCopy( CKEDITOR.dom.element.prototype ), { + /** + * Sets the editable data. The data will be passed through the {@link CKEDITOR.editor#dataProcessor} + * and the {@link CKEDITOR.editor#filter}. This ensures that the data was filtered and prepared to be + * edited like the {@link CKEDITOR.editor#method-setData editor data}. + * + * @param {String} data + */ + setData: function( data ) { + data = this.editor.dataProcessor.toHtml( data, { + context: this.getName(), + filter: this.filter, + enterMode: this.enterMode + } ); + this.setHtml( data ); + + this.editor.widgets.initOnAll( this ); + }, + + /** + * Gets the editable data. Like {@link #setData}, this method will process and filter the data. + * + * @returns {String} + */ + getData: function() { + return this.editor.dataProcessor.toDataFormat( this.getHtml(), { + context: this.getName(), + filter: this.filter, + enterMode: this.enterMode + } ); + } + } ); + + /** + * The editor instance. + * + * @readonly + * @property {CKEDITOR.editor} editor + */ + + /** + * The filter instance if allowed content rules were defined. + * + * @readonly + * @property {CKEDITOR.filter} filter + */ + + /** + * The enter mode active in this editable. + * It is determined from editable's name (whether it is a blockless editable), + * its allowed content rules (if defined) and the default editor's mode. + * + * @readonly + * @property {Number} enterMode + */ + + /** + * The shift enter move active in this editable. + * + * @readonly + * @property {Number} shiftEnterMode + */ + + + // + // REPOSITORY helpers ----------------------------------------------------- + // + + function addWidgetButtons( editor ) { + var widgets = editor.widgets.registered, + widget, + widgetName, + widgetButton; + + for ( widgetName in widgets ) { + widget = widgets[ widgetName ]; + + // Create button if defined. + widgetButton = widget.button; + if ( widgetButton && editor.ui.addButton ) { + editor.ui.addButton( CKEDITOR.tools.capitalize( widget.name, true ), { + label: widgetButton, + command: widget.name, + toolbar: 'insert,10' + } ); + } + } + } + + // Create a command creating and editing widget. + // + // @param editor + // @param {CKEDITOR.plugins.widget.definition} widgetDef + function addWidgetCommand( editor, widgetDef ) { + editor.addCommand( widgetDef.name, { + exec: function() { + var focused = editor.widgets.focused; + // If a widget of the same type is focused, start editing. + if ( focused && focused.name == widgetDef.name ) + focused.edit(); + // Otherwise... + // ... use insert method is was defined. + else if ( widgetDef.insert ) + widgetDef.insert(); + // ... or create a brand-new widget from template. + else if ( widgetDef.template ) { + var defaults = typeof widgetDef.defaults == 'function' ? widgetDef.defaults() : widgetDef.defaults, + element = CKEDITOR.dom.element.createFromHtml( widgetDef.template.output( defaults ) ), + instance, + wrapper = editor.widgets.wrapElement( element, widgetDef.name ), + temp = new CKEDITOR.dom.documentFragment( wrapper.getDocument() ); + + // Append wrapper to a temporary document. This will unify the environment + // in which #data listeners work when creating and editing widget. + temp.append( wrapper ); + instance = editor.widgets.initOn( element, widgetDef ); + + // Instance could be destroyed during initialization. + // In this case finalize creation if some new widget + // was left in temporary document fragment. + if ( !instance ) { + finalizeCreation(); + return; + } + + // Listen on edit to finalize widget insertion. + // + // * If dialog was set, then insert widget after dialog was successfully saved or destroy this + // temporary instance. + // * If dialog wasn't set and edit wasn't canceled, insert widget. + var editListener = instance.once( 'edit', function( evt ) { + if ( evt.data.dialog ) { + instance.once( 'dialog', function( evt ) { + var dialog = evt.data, + okListener, + cancelListener; + + // Finalize creation AFTER (20) new data was set. + okListener = dialog.once( 'ok', finalizeCreation, null, null, 20 ); + + cancelListener = dialog.once( 'cancel', function() { + editor.widgets.destroy( instance, true ); + } ); + + dialog.once( 'hide', function() { + okListener.removeListener(); + cancelListener.removeListener(); + } ); + } ); + } else { + // Dialog hasn't been set, so insert widget now. + finalizeCreation(); + } + }, null, null, 999 ); + + instance.edit(); + + // Remove listener in case someone canceled it before this + // listener was executed. + editListener.removeListener(); + } + + function finalizeCreation() { + editor.widgets.finalizeCreation( temp ); + } + }, + + refresh: function( editor, path ) { + // Disable widgets' commands inside nested editables - + // check if blockLimit is a nested editable or a descendant of any. + this.setState( getNestedEditable( editor.editable(), path.blockLimit ) ? CKEDITOR.TRISTATE_DISABLED : CKEDITOR.TRISTATE_OFF ); + }, + // A hack to force command refreshing on context change. + context: 'div', + + allowedContent: widgetDef.allowedContent, + requiredContent: widgetDef.requiredContent, + contentForms: widgetDef.contentForms, + contentTransformations: widgetDef.contentTransformations + } ); + } + + function addWidgetProcessors( widgetsRepo, widgetDef ) { + var upcast = widgetDef.upcast, + upcasts; + + if ( !upcast ) + return; + + // Multiple upcasts defined in string. + if ( typeof upcast == 'string' ) { + upcasts = upcast.split( ',' ); + while ( upcasts.length ) + widgetsRepo._.upcasts.push( [ widgetDef.upcasts[ upcasts.pop() ], widgetDef.name ] ); + } else { + // Single rule which is automatically activated. + widgetsRepo._.upcasts.push( [ upcast, widgetDef.name ] ); + } + } + + function blurWidget( widgetsRepo, widget ) { + widgetsRepo.focused = null; + + if ( widget.isInited() ) { + var isDirty = widget.editor.checkDirty(); + + // Widget could be destroyed in the meantime - e.g. data could be set. + widgetsRepo.fire( 'widgetBlurred', { widget: widget } ); + widget.setFocused( false ); + + !isDirty && widget.editor.resetDirty(); + } + } + + function checkWidgets( evt ) { + var options = evt.data; + + if ( this.editor.mode != 'wysiwyg' ) + return; + + var editable = this.editor.editable(), + instances = this.instances, + newInstances, i, count, wrapper; + + if ( !editable ) + return; + + // Remove widgets which have no corresponding elements in DOM. + for ( i in instances ) { + if ( !editable.contains( instances[ i ].wrapper ) ) + this.destroy( instances[ i ], true ); + } + + // Init on all (new) if initOnlyNew option was passed. + if ( options && options.initOnlyNew ) + newInstances = this.initOnAll(); + else { + var wrappers = editable.find( '.cke_widget_wrapper' ); + newInstances = []; + + // Create widgets on existing wrappers if they do not exists. + for ( i = 0, count = wrappers.count(); i < count; i++ ) { + wrapper = wrappers.getItem( i ); + + // Check if there's no instance for this widget and that + // wrapper is not inside some temporary element like copybin (#11088). + if ( !this.getByElement( wrapper, true ) && !findParent( wrapper, isDomTemp ) ) { + // Add cke_widget_new class because otherwise + // widget will not be created on such wrapper. + wrapper.addClass( 'cke_widget_new' ); + newInstances.push( this.initOn( wrapper.getFirst( isDomWidgetElement ) ) ); + } + } + } + + // If only single widget was initialized and focusInited was passed, focus it. + if ( options && options.focusInited && newInstances.length == 1 ) + newInstances[ 0 ].focus(); + } + + // Unwraps widget element and clean up element. + // + // This function is used to clean up pasted widgets. + // It should have similar result to widget#destroy plus + // some additional adjustments, specific for pasting. + // + // @param {CKEDITOR.htmlParser.element} el + function cleanUpWidgetElement( el ) { + var parent = el.parent; + if ( parent.type == CKEDITOR.NODE_ELEMENT && parent.attributes[ 'data-cke-widget-wrapper' ] ) + parent.replaceWith( el ); + } + + // Similar to cleanUpWidgetElement, but works on DOM and finds + // widget elements by its own. + // + // Unlike cleanUpWidgetElement it will wrap element back. + // + // @param {CKEDITOR.dom.element} container + function cleanUpAllWidgetElements( widgetsRepo, container ) { + var wrappers = container.find( '.cke_widget_wrapper' ), + wrapper, element, + i = 0, + l = wrappers.count(); + + for ( ; i < l; ++i ) { + wrapper = wrappers.getItem( i ); + element = wrapper.getFirst( isDomWidgetElement ); + // If wrapper contains widget element - unwrap it and wrap again. + if ( element.type == CKEDITOR.NODE_ELEMENT && element.data( 'widget' ) ) { + element.replace( wrapper ); + widgetsRepo.wrapElement( element ); + } else { + // Otherwise - something is wrong... clean this up. + wrapper.remove(); + } + } + } + + // Creates {@link CKEDITOR.filter} instance for given widget, editable and rules. + // + // Once filter for widget-editable pair is created it is cached, so the same instance + // will be returned when method is executed again. + // + // @param {String} widgetName + // @param {String} editableName + // @param {CKEDITOR.plugins.widget.nestedEditableDefinition} editableDefinition The nested editable definition. + // @returns {CKEDITOR.filter} Filter instance or `null` if rules are not defined. + // @context CKEDITOR.plugins.widget.repository + function createEditableFilter( widgetName, editableName, editableDefinition ) { + if ( !editableDefinition.allowedContent ) + return null; + + var editables = this._.filters[ widgetName ]; + + if ( !editables ) + this._.filters[ widgetName ] = editables = {}; + + var filter = editables[ editableName ]; + + if ( !filter ) + editables[ editableName ] = filter = new CKEDITOR.filter( editableDefinition.allowedContent ); + + return filter; + } + + // Creates an iterator function which when executed on all + // elements in DOM tree will gather elements that should be wrapped + // and initialized as widgets. + function createUpcastIterator( widgetsRepo ) { + var toBeWrapped = [], + upcasts = widgetsRepo._.upcasts, + upcastCallbacks = widgetsRepo._.upcastCallbacks; + + return { + toBeWrapped: toBeWrapped, + + iterator: function( element ) { + var upcast, upcasted, + data, + i, + upcastsLength, + upcastCallbacksLength; + + // Wrapper found - find widget element, add it to be + // cleaned up (unwrapped) and wrapped and stop iterating in this branch. + if ( 'data-cke-widget-wrapper' in element.attributes ) { + element = element.getFirst( isParserWidgetElement ); + + if ( element ) + toBeWrapped.push( [ element ] ); + + // Do not iterate over descendants. + return false; + } + // Widget element found - add it to be cleaned up (just in case) + // and wrapped and stop iterating in this branch. + else if ( 'data-widget' in element.attributes ) { + toBeWrapped.push( [ element ] ); + + // Do not iterate over descendants. + return false; + } + else if ( ( upcastsLength = upcasts.length ) ) { + // Ignore elements with data-cke-widget-upcasted to avoid multiple upcasts (#11533). + // Do not iterate over descendants. + if ( element.attributes[ 'data-cke-widget-upcasted' ] ) + return false; + + // Check element with upcast callbacks first. + // If any of them return false abort upcasting. + for ( i = 0, upcastCallbacksLength = upcastCallbacks.length; i < upcastCallbacksLength; ++i ) { + if ( upcastCallbacks[ i ]( element ) === false ) + return; + // Return nothing in order to continue iterating over ascendants. + // See http://dev.ckeditor.com/ticket/11186#comment:6 + } + + for ( i = 0; i < upcastsLength; ++i ) { + upcast = upcasts[ i ]; + data = {}; + + if ( ( upcasted = upcast[ 0 ]( element, data ) ) ) { + // If upcast function returned element, upcast this one. + // It can be e.g. a new element wrapping the original one. + if ( upcasted instanceof CKEDITOR.htmlParser.element ) + element = upcasted; + + // Set initial data attr with data from upcast method. + element.attributes[ 'data-cke-widget-data' ] = encodeURIComponent( JSON.stringify( data ) ); + element.attributes[ 'data-cke-widget-upcasted' ] = 1; + + toBeWrapped.push( [ element, upcast[ 1 ] ] ); + + // Do not iterate over descendants. + return false; + } + } + } + } + }; + } + + // Finds a first parent that matches query. + // + // @param {CKEDITOR.dom.element} element + // @param {Function} query + function findParent( element, query ) { + var parent = element; + + while ( ( parent = parent.getParent() ) ) { + if ( query( parent ) ) + return true; + } + return false; + } + + // Gets nested editable if node is its descendant or the editable itself. + // + // @param {CKEDITOR.dom.element} guard Stop ancestor search on this node (usually editor's editable). + // @param {CKEDITOR.dom.node} node Start search from this node. + // @returns {CKEDITOR.dom.element} Element or null. + function getNestedEditable( guard, node ) { + if ( !node || node.equals( guard ) ) + return null; + + if ( isDomNestedEditable( node ) ) + return node; + + return getNestedEditable( guard, node.getParent() ); + } + + function getWrapperAttributes( inlineWidget ) { + return { + // tabindex="-1" means that it can receive focus by code. + tabindex: -1, + contenteditable: 'false', + 'data-cke-widget-wrapper': 1, + 'data-cke-filter': 'off', + // Class cke_widget_new marks widgets which haven't been initialized yet. + 'class': 'cke_widget_wrapper cke_widget_new cke_widget_' + + ( inlineWidget ? 'inline' : 'block' ) + }; + } + + // Inserts element at given index. + // It will check DTD and split ancestor elements up to the first + // that can contain this element. + // + // @param {CKEDITOR.htmlParser.element} parent + // @param {Number} index + // @param {CKEDITOR.htmlParser.element} element + function insertElement( parent, index, element ) { + // Do not split doc fragment... + if ( parent.type == CKEDITOR.NODE_ELEMENT ) { + var parentAllows = CKEDITOR.dtd[ parent.name ]; + // Parent element is known (included in DTD) and cannot contain + // this element. + if ( parentAllows && !parentAllows[ element.name ] ) { + var parent2 = parent.split( index ), + parentParent = parent.parent; + + // Element will now be inserted at right parent's index. + index = parent2.getIndex(); + + // If left part of split is empty - remove it. + if ( !parent.children.length ) { + index -= 1; + parent.remove(); + } + + // If right part of split is empty - remove it. + if ( !parent2.children.length ) + parent2.remove(); + + // Try inserting as grandpas' children. + return insertElement( parentParent, index, element ); + } + } + + // Finally we can add this element. + parent.add( element, index ); + } + + // @param {CKEDITOR.htmlParser.element} + function isParserWidgetElement( element ) { + return element.type == CKEDITOR.NODE_ELEMENT && !!element.attributes[ 'data-widget' ]; + } + + // @param {CKEDITOR.dom.element} + function isDomWidgetElement( element ) { + return element.type == CKEDITOR.NODE_ELEMENT && element.hasAttribute( 'data-widget' ); + } + + // Whether for this definition and element widget should be created in inline or block mode. + function isWidgetInline( widgetDef, elementName ) { + return typeof widgetDef.inline == 'boolean' ? widgetDef.inline : !!CKEDITOR.dtd.$inline[ elementName ]; + } + + // @param {CKEDITOR.htmlParser.element} + function isParserWidgetWrapper( element ) { + return element.type == CKEDITOR.NODE_ELEMENT && element.attributes[ 'data-cke-widget-wrapper' ]; + } + + // @param {CKEDITOR.dom.element} + function isDomWidgetWrapper( element ) { + return element.type == CKEDITOR.NODE_ELEMENT && element.hasAttribute( 'data-cke-widget-wrapper' ); + } + + // @param {CKEDITOR.dom.element} + function isDomNestedEditable( node ) { + return node.type == CKEDITOR.NODE_ELEMENT && node.hasAttribute( 'data-cke-widget-editable' ); + } + + // @param {CKEDITOR.dom.element} + function isDomTemp( element ) { + return element.hasAttribute( 'data-cke-temp' ); + } + + // @param {CKEDITOR.dom.element} + function isDomDragHandler( element ) { + return element.type == CKEDITOR.NODE_ELEMENT && element.hasAttribute( 'data-cke-widget-drag-handler' ); + } + + // @param {CKEDITOR.dom.element} + function isDomDragHandlerContainer( element ) { + return element.type == CKEDITOR.NODE_ELEMENT && element.hasClass( 'cke_widget_drag_handler_container' ); + } + + function finalizeNativeDrop( editor, sourceWidget, range ) { + // Save the snapshot with the state before moving widget. + // Focus widget, so when we'll undo the DnD, widget will be focused. + sourceWidget.focus(); + editor.fire( 'saveSnapshot' ); + + // Lock snapshot to group all steps of moving widget from the original place to the new one. + editor.fire( 'lockSnapshot', { dontUpdate: true } ); + + range.select(); + + var widgetHtml = sourceWidget.wrapper.getOuterHtml(); + sourceWidget.wrapper.remove(); + editor.widgets.destroy( sourceWidget, true ); + editor.execCommand( 'paste', widgetHtml ); + + editor.fire( 'unlockSnapshot' ); + } + + function getRangeAtDropPosition( editor, dropEvt ) { + var $evt = dropEvt.data.$, + $range, + range = editor.createRange(); + + // Make testing possible. + if ( dropEvt.data.testRange ) + return dropEvt.data.testRange; + + // Webkits. + if ( document.caretRangeFromPoint ) { + $range = editor.document.$.caretRangeFromPoint( $evt.clientX, $evt.clientY ); + range.setStart( CKEDITOR.dom.node( $range.startContainer ), $range.startOffset ); + range.collapse( true ); + } + // FF. + else if ( $evt.rangeParent ) { + range.setStart( CKEDITOR.dom.node( $evt.rangeParent ), $evt.rangeOffset ); + range.collapse( true ); + } + // IEs. + else if ( document.body.createTextRange ) { + $range = editor.document.getBody().$.createTextRange(); + $range.moveToPoint( $evt.clientX, $evt.clientY ); + var id = 'cke-temp-' + ( new Date() ).getTime(); + $range.pasteHTML( '\u200b' ); + + var span = editor.document.getById( id ); + range.moveToPosition( span, CKEDITOR.POSITION_BEFORE_START ); + span.remove(); + } else { + return null; + } + + return range; + } + + function onEditableKey( widget, keyCode ) { + var focusedEditable = widget.focusedEditable, + range; + + // CTRL+A. + if ( keyCode == CKEDITOR.CTRL + 65 ) { + var bogus = focusedEditable.getBogus(); + + range = widget.editor.createRange(); + range.selectNodeContents( focusedEditable ); + // Exclude bogus if exists. + if ( bogus ) + range.setEndAt( bogus, CKEDITOR.POSITION_BEFORE_START ); + + range.select(); + // Cancel event - block default. + return false; + } + // DEL or BACKSPACE. + else if ( keyCode == 8 || keyCode == 46 ) { + var ranges = widget.editor.getSelection().getRanges(); + + range = ranges[ 0 ]; + + // Block del or backspace if at editable's boundary. + return !( ranges.length == 1 && range.collapsed && + range.checkBoundaryOfElement( focusedEditable, CKEDITOR[ keyCode == 8 ? 'START' : 'END' ] ) ); + } + } + + function setFocusedEditable( widgetsRepo, widget, editableElement, offline ) { + var editor = widgetsRepo.editor; + + editor.fire( 'lockSnapshot' ); + + if ( editableElement ) { + var editableName = editableElement.data( 'cke-widget-editable' ), + editableInstance = widget.editables[ editableName ]; + + widgetsRepo.widgetHoldingFocusedEditable = widget; + widget.focusedEditable = editableInstance; + editableElement.addClass( 'cke_widget_editable_focused' ); + + if ( editableInstance.filter ) + editor.setActiveFilter( editableInstance.filter ); + editor.setActiveEnterMode( editableInstance.enterMode, editableInstance.shiftEnterMode ); + } else { + if ( !offline ) + widget.focusedEditable.removeClass( 'cke_widget_editable_focused' ); + + widget.focusedEditable = null; + widgetsRepo.widgetHoldingFocusedEditable = null; + editor.setActiveFilter( null ); + editor.setActiveEnterMode( null, null ); + } + + editor.fire( 'unlockSnapshot' ); + } + + function setupContextMenu( editor ) { + if ( !editor.contextMenu ) + return; + + editor.contextMenu.addListener( function( element ) { + var widget = editor.widgets.getByElement( element, true ); + + if ( widget ) + return widget.fire( 'contextMenu', {} ); + } ); + } + + // And now we've got two problems - original problem and RegExp. + // Some softeners: + // * FF tends to copy all blocks up to the copybin container. + // * IE tends to copy only the copybin, without its container. + // * We use spans on IE and blockless editors, but divs in other cases. + var pasteReplaceRegex = new RegExp( + '^' + + '(?:<(?:div|span)(?: data-cke-temp="1")?(?: id="cke_copybin")?(?: data-cke-temp="1")?>)?' + + '(?:<(?:div|span)(?: style="[^"]+")?>)?' + + ']*data-cke-copybin-start="1"[^>]*>.?([\\s\\S]+)]*data-cke-copybin-end="1"[^>]*>.?' + + '(?:)?' + + '(?:)?' + + '$' + ); + + function pasteReplaceFn( match, wrapperHtml ) { + // Avoid polluting pasted data with any whitspaces, + // what's going to break check whether only one widget was pasted. + return CKEDITOR.tools.trim( wrapperHtml ); + } + + function setupDragAndDrop( widgetsRepo ) { + var editor = widgetsRepo.editor, + lineutils = CKEDITOR.plugins.lineutils; + + editor.on( 'contentDom', function() { + var editable = editor.editable(), + // #11123 Firefox needs to listen on document, because otherwise event won't be fired. + // #11086 IE8 cannot listen on document. + dropTarget = ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 ) || editable.isInline() ? editable : editor.document; + + editable.attachListener( dropTarget, 'drop', function( evt ) { + var dataStr = evt.data.$.dataTransfer.getData( 'text' ), + dataObj, + sourceWidget, + range; + + if ( !dataStr ) + return; + + try { + dataObj = JSON.parse( dataStr ); + } catch ( e ) { + // Do nothing - data couldn't be parsed so it's not a CKEditor's data. + return; + } + + if ( dataObj.type != 'cke-widget' ) + return; + + evt.data.preventDefault(); + + // Something went wrong... maybe someone is dragging widgets between editors/windows/tabs/browsers/frames. + if ( dataObj.editor != editor.name || !( sourceWidget = widgetsRepo.instances[ dataObj.id ] ) ) + return; + + // Try to determine a DOM position at which drop happened. If none of methods + // which we support succeeded abort. + range = getRangeAtDropPosition( editor, evt ); + if ( !range ) + return; + + // #11132 Hack to prevent cursor loss on Firefox. Without timeout widget is + // correctly pasted but then cursor is invisible (although it works) and can be restored + // only by blurring editable. + if ( CKEDITOR.env.gecko ) + setTimeout( finalizeNativeDrop, 0, editor, sourceWidget, range ); + else + finalizeNativeDrop( editor, sourceWidget, range ); + } ); + + // Register Lineutils's utilities as properties of repo. + CKEDITOR.tools.extend( widgetsRepo, { + finder: new lineutils.finder( editor, { + lookups: { + // Element is block but not list item and not in nested editable. + 'default': function( el ) { + if ( el.is( CKEDITOR.dtd.$listItem ) ) + return; + + if ( !el.is( CKEDITOR.dtd.$block ) ) + return; + + while ( el ) { + if ( isDomNestedEditable( el ) ) + return; + + el = el.getParent(); + } + + return CKEDITOR.LINEUTILS_BEFORE | CKEDITOR.LINEUTILS_AFTER; + } + } + } ), + locator: new lineutils.locator( editor ), + liner: new lineutils.liner( editor, { + lineStyle: { + cursor: 'move !important', + 'border-top-color': '#666' + }, + tipLeftStyle: { + 'border-left-color': '#666' + }, + tipRightStyle: { + 'border-right-color': '#666' + } + } ) + }, true ); + } ); + } + + // Setup mouse observer which will trigger: + // * widget focus on widget click, + // * widget#doubleclick forwarded from editor#doubleclick. + function setupMouseObserver( widgetsRepo ) { + var editor = widgetsRepo.editor; + + editor.on( 'contentDom', function() { + var editable = editor.editable(), + evtRoot = editable.isInline() ? editable : editor.document, + widget, + mouseDownOnDragHandler; + + editable.attachListener( evtRoot, 'mousedown', function( evt ) { + var target = evt.data.getTarget(); + + // #10887 Clicking scrollbar in IE8 will invoke event with empty target object. + if ( !target.type ) + return false; + + widget = widgetsRepo.getByElement( target ); + mouseDownOnDragHandler = 0; // Reset. + + // Widget was clicked, but not editable nested in it. + if ( widget ) { + // Ignore mousedown on drag and drop handler if the widget is inline. + // Block widgets are handled by Lineutils. + if ( widget.inline && target.type == CKEDITOR.NODE_ELEMENT && target.hasAttribute( 'data-cke-widget-drag-handler' ) ) { + mouseDownOnDragHandler = 1; + return; + } + + if ( !getNestedEditable( widget.wrapper, target ) ) { + evt.data.preventDefault(); + if ( !CKEDITOR.env.ie ) + widget.focus(); + } else { + // Reset widget so mouseup listener is not confused. + widget = null; + } + } + } ); + + // Focus widget on mouseup if mousedown was fired on drag handler. + // Note: mouseup won't be fired at all if widget was dragged and dropped, so + // this code will be executed only when drag handler was clicked. + editable.attachListener( evtRoot, 'mouseup', function() { + if ( widget && mouseDownOnDragHandler ) { + mouseDownOnDragHandler = 0; + widget.focus(); + } + } ); + + // On IE it is not enough to block mousedown. If widget wrapper (element with + // contenteditable=false attribute) is clicked directly (it is a target), + // then after mouseup/click IE will select that element. + // It is not possible to prevent that default action, + // so we force fake selection after everything happened. + if ( CKEDITOR.env.ie ) { + editable.attachListener( evtRoot, 'mouseup', function() { + if ( widget ) { + setTimeout( function() { + widget.focus(); + widget = null; + } ); + } + } ); + } + } ); + + editor.on( 'doubleclick', function( evt ) { + var widget = widgetsRepo.getByElement( evt.data.element ); + + // Not in widget or in nested editable. + if ( !widget || getNestedEditable( widget.wrapper, evt.data.element ) ) + return; + + return widget.fire( 'doubleclick', { element: evt.data.element } ); + }, null, null, 1 ); + } + + // Setup editor#key observer which will forward it + // to focused widget. + function setupKeyboardObserver( widgetsRepo ) { + var editor = widgetsRepo.editor; + + editor.on( 'key', function( evt ) { + var focused = widgetsRepo.focused, + widgetHoldingFocusedEditable = widgetsRepo.widgetHoldingFocusedEditable, + ret; + + if ( focused ) + ret = focused.fire( 'key', { keyCode: evt.data.keyCode } ); + else if ( widgetHoldingFocusedEditable ) + ret = onEditableKey( widgetHoldingFocusedEditable, evt.data.keyCode ); + + return ret; + }, null, null, 1 ); + } + + // Setup copybin on native copy and cut events in order to handle copy and cut commands + // if user accepted security alert on IEs. + // Note: when copying or cutting using keystroke, copySingleWidget will be first executed + // by the keydown listener. Conflict between two calls will be resolved by copy_bin existence check. + function setupNativeCutAndCopy( widgetsRepo ) { + var editor = widgetsRepo.editor; + + editor.on( 'contentDom', function() { + var editable = editor.editable(); + + editable.attachListener( editable, 'copy', eventListener ); + editable.attachListener( editable, 'cut', eventListener ); + } ); + + function eventListener( evt ) { + if ( widgetsRepo.focused ) + copySingleWidget( widgetsRepo.focused, evt.name == 'cut' ); + } + } + + // Setup selection observer which will trigger: + // * widget select & focus on selection change, + // * nested editable focus (related properites and classes) on selection change, + // * deselecting and blurring all widgets on data, + // * blurring widget on editor blur. + function setupSelectionObserver( widgetsRepo ) { + var editor = widgetsRepo.editor; + + editor.on( 'selectionCheck', function() { + widgetsRepo.fire( 'checkSelection' ); + } ); + + widgetsRepo.on( 'checkSelection', widgetsRepo.checkSelection, widgetsRepo ); + + editor.on( 'selectionChange', function( evt ) { + var nestedEditable = getNestedEditable( editor.editable(), evt.data.selection.getStartElement() ), + newWidget = nestedEditable && widgetsRepo.getByElement( nestedEditable ), + oldWidget = widgetsRepo.widgetHoldingFocusedEditable; + + if ( oldWidget ) { + if ( oldWidget !== newWidget || !oldWidget.focusedEditable.equals( nestedEditable ) ) { + setFocusedEditable( widgetsRepo, oldWidget, null ); + + if ( newWidget && nestedEditable ) + setFocusedEditable( widgetsRepo, newWidget, nestedEditable ); + } + } + // It may happen that there's no widget even if editable was found - + // e.g. if selection was automatically set in editable although widget wasn't initialized yet. + else if ( newWidget && nestedEditable ) { + setFocusedEditable( widgetsRepo, newWidget, nestedEditable ); + } + } ); + + // Invalidate old widgets early - immediately on dataReady. + editor.on( 'dataReady', function() { + // Deselect and blur all widgets. + stateUpdater( widgetsRepo ).commit(); + } ); + + editor.on( 'blur', function() { + var widget; + + if ( ( widget = widgetsRepo.focused ) ) + blurWidget( widgetsRepo, widget ); + + if ( ( widget = widgetsRepo.widgetHoldingFocusedEditable ) ) + setFocusedEditable( widgetsRepo, widget, null ); + } ); + } + + // Set up actions like: + // * processing in toHtml/toDataFormat, + // * pasting handling, + // * insertion handling, + // * editable reload handling (setData, mode switch, undo/redo), + // * DOM invalidation handling, + // * widgets checks. + function setupWidgetsLifecycle( widgetsRepo ) { + setupWidgetsLifecycleStart( widgetsRepo ); + setupWidgetsLifecycleEnd( widgetsRepo ); + + widgetsRepo.on( 'checkWidgets', checkWidgets ); + widgetsRepo.editor.on( 'contentDomInvalidated', widgetsRepo.checkWidgets, widgetsRepo ); + } + + function setupWidgetsLifecycleEnd( widgetsRepo ) { + var editor = widgetsRepo.editor, + downcastingSessions = {}; + + // Listen before htmlDP#htmlFilter is applied to cache all widgets, because we'll + // loose data-cke-* attributes. + editor.on( 'toDataFormat', function( evt ) { + // To avoid conflicts between htmlDP#toDF calls done at the same time + // (e.g. nestedEditable#getData called during downcasting some widget) + // mark every toDataFormat event chain with the downcasting session id. + var id = CKEDITOR.tools.getNextNumber(), + toBeDowncasted = []; + evt.data.downcastingSessionId = id; + downcastingSessions[ id ] = toBeDowncasted; + + evt.data.dataValue.forEach( function( element ) { + var attrs = element.attributes, + widget, widgetElement; + + // Wrapper. + // Perform first part of downcasting (cleanup) and cache widgets, + // because after applying DP's filter all data-cke-* attributes will be gone. + if ( 'data-cke-widget-id' in attrs ) { + widget = widgetsRepo.instances[ attrs[ 'data-cke-widget-id' ] ]; + if ( widget ) { + widgetElement = element.getFirst( isParserWidgetElement ); + toBeDowncasted.push( { + wrapper: element, + element: widgetElement, + widget: widget, + editables: {} + } ); + + // If widget did not have data-cke-widget attribute before upcasting remove it. + if ( widgetElement.attributes[ 'data-cke-widget-keep-attr' ] != '1' ) + delete widgetElement.attributes[ 'data-widget' ]; + } + } + // Nested editable. + else if ( 'data-cke-widget-editable' in attrs ) { + // Save the reference to this nested editable in the closest widget to be downcasted. + // Nested editables are downcasted in the successive toDataFormat to create an opportunity + // for dataFilter's "excludeNestedEditable" option to do its job (that option relies on + // contenteditable="true" attribute) (#11372). + toBeDowncasted[ toBeDowncasted.length - 1 ].editables[ attrs[ 'data-cke-widget-editable' ] ] = element; + + // Don't check children - there won't be next wrapper or nested editable which we + // should process in this session. + return false; + } + }, CKEDITOR.NODE_ELEMENT, true ); + }, null, null, 8 ); + + // Listen after dataProcessor.htmlFilter and ACF were applied + // so wrappers securing widgets' contents are removed after all filtering was done. + editor.on( 'toDataFormat', function( evt ) { + // Ignore some unmarked sessions. + if ( !evt.data.downcastingSessionId ) + return; + + var toBeDowncasted = downcastingSessions[ evt.data.downcastingSessionId ], + toBe, widget, widgetElement, retElement, editableElement, e; + + while ( ( toBe = toBeDowncasted.shift() ) ) { + widget = toBe.widget; + widgetElement = toBe.element; + retElement = widget._.downcastFn && widget._.downcastFn.call( widget, widgetElement ); + + // Replace nested editables' content with their output data. + for ( e in toBe.editables ) { + editableElement = toBe.editables[ e ]; + + delete editableElement.attributes.contenteditable; + editableElement.setHtml( widget.editables[ e ].getData() ); + } + + // Returned element always defaults to widgetElement. + if ( !retElement ) + retElement = widgetElement; + + toBe.wrapper.replaceWith( retElement ); + } + }, null, null, 13 ); + + + editor.on( 'contentDomUnload', function() { + widgetsRepo.destroyAll( true ); + } ); + } + + function setupWidgetsLifecycleStart( widgetsRepo ) { + var editor = widgetsRepo.editor, + processedWidgetOnly, + snapshotLoaded; + + // Listen after ACF (so data are filtered), + // but before dataProcessor.dataFilter was applied (so we can secure widgets' internals). + editor.on( 'toHtml', function( evt ) { + var upcastIterator = createUpcastIterator( widgetsRepo ), + toBeWrapped; + + evt.data.dataValue.forEach( upcastIterator.iterator, CKEDITOR.NODE_ELEMENT, true ); + + // Clean up and wrap all queued elements. + while ( ( toBeWrapped = upcastIterator.toBeWrapped.pop() ) ) { + cleanUpWidgetElement( toBeWrapped[ 0 ] ); + widgetsRepo.wrapElement( toBeWrapped[ 0 ], toBeWrapped[ 1 ] ); + } + + // Used to determine whether only widget was pasted. + processedWidgetOnly = evt.data.dataValue.children.length == 1 && + isParserWidgetWrapper( evt.data.dataValue.children[ 0 ] ); + }, null, null, 8 ); + + editor.on( 'dataReady', function() { + // Clean up all widgets loaded from snapshot. + if ( snapshotLoaded ) + cleanUpAllWidgetElements( widgetsRepo, editor.editable() ); + snapshotLoaded = 0; + + // Some widgets were destroyed on contentDomUnload, + // some on loadSnapshot, but that does not include + // e.g. setHtml on inline editor or widgets removed just + // before setting data. + widgetsRepo.destroyAll( true ); + widgetsRepo.initOnAll(); + } ); + + // Set flag so dataReady will know that additional + // cleanup is needed, because snapshot containing widgets was loaded. + editor.on( 'loadSnapshot', function( evt ) { + // Primitive but sufficient check which will prevent from executing + // heavier cleanUpAllWidgetElements if not needed. + if ( ( /data-cke-widget/ ).test( evt.data ) ) + snapshotLoaded = 1; + + widgetsRepo.destroyAll( true ); + }, null, null, 9 ); + + // Handle pasted single widget. + editor.on( 'paste', function( evt ) { + evt.data.dataValue = evt.data.dataValue.replace( pasteReplaceRegex, pasteReplaceFn ); + } ); + + // Listen with high priority to check widgets after data was inserted. + editor.on( 'insertText', checkNewWidgets, null, null, 999 ); + editor.on( 'insertHtml', checkNewWidgets, null, null, 999 ); + + function checkNewWidgets() { + editor.fire( 'lockSnapshot' ); + + // Init only new for performance reason. + // Focus inited if only widget was processed. + widgetsRepo.checkWidgets( { initOnlyNew: true, focusInited: processedWidgetOnly } ); + + editor.fire( 'unlockSnapshot' ); + } + } + + // Helper for coordinating which widgets should be + // selected/deselected and which one should be focused/blurred. + function stateUpdater( widgetsRepo ) { + var currentlySelected = widgetsRepo.selected, + toBeSelected = [], + toBeDeselected = currentlySelected.slice( 0 ), + focused = null; + + return { + select: function( widget ) { + if ( CKEDITOR.tools.indexOf( currentlySelected, widget ) < 0 ) + toBeSelected.push( widget ); + + var index = CKEDITOR.tools.indexOf( toBeDeselected, widget ); + if ( index >= 0 ) + toBeDeselected.splice( index, 1 ); + + return this; + }, + + focus: function( widget ) { + focused = widget; + return this; + }, + + commit: function() { + var focusedChanged = widgetsRepo.focused !== focused, + widget, isDirty; + + widgetsRepo.editor.fire( 'lockSnapshot' ); + + if ( focusedChanged && ( widget = widgetsRepo.focused ) ) + blurWidget( widgetsRepo, widget ); + + while ( ( widget = toBeDeselected.pop() ) ) { + currentlySelected.splice( CKEDITOR.tools.indexOf( currentlySelected, widget ), 1 ); + // Widget could be destroyed in the meantime - e.g. data could be set. + if ( widget.isInited() ) { + isDirty = widget.editor.checkDirty(); + + widget.setSelected( false ); + + !isDirty && widget.editor.resetDirty(); + } + } + + if ( focusedChanged && focused ) { + isDirty = widgetsRepo.editor.checkDirty(); + + widgetsRepo.focused = focused; + widgetsRepo.fire( 'widgetFocused', { widget: focused } ); + focused.setFocused( true ); + + !isDirty && widgetsRepo.editor.resetDirty(); + } + + while ( ( widget = toBeSelected.pop() ) ) { + currentlySelected.push( widget ); + widget.setSelected( true ); + } + + widgetsRepo.editor.fire( 'unlockSnapshot' ); + } + }; + } + + + // + // WIDGET helpers --------------------------------------------------------- + // + + // LEFT, RIGHT, UP, DOWN, DEL, BACKSPACE - unblock default fake sel handlers. + var keystrokesNotBlockedByWidget = { 37: 1, 38: 1, 39: 1, 40: 1, 8: 1, 46: 1 }; + + // Applies or removes style's classes from widget. + // @param {CKEDITOR.style} style Custom widget style. + // @param {Boolean} apply Whether to apply or remove style. + function applyRemoveStyle( widget, style, apply ) { + var changed = 0, + classes = getStyleClasses( style ), + updatedClasses = widget.data.classes || {}, + cl; + + // Ee... Something is wrong with this style. + if ( !classes ) + return; + + // Clone, because we need to break reference. + updatedClasses = CKEDITOR.tools.clone( updatedClasses ); + + while ( ( cl = classes.pop() ) ) { + if ( apply ) { + if ( !updatedClasses[ cl ] ) + changed = updatedClasses[ cl ] = 1; + } else { + if ( updatedClasses[ cl ] ) { + delete updatedClasses[ cl ]; + changed = 1; + } + } + } + if ( changed ) + widget.setData( 'classes', updatedClasses ); + } + + function cancel( evt ) { + evt.cancel(); + } + + function copySingleWidget( widget, isCut ) { + var editor = widget.editor, + doc = editor.document; + + // We're still handling previous copy/cut. + // When keystroke is used to copy/cut this will also prevent + // conflict with copySingleWidget called again for native copy/cut event. + if ( doc.getById( 'cke_copybin' ) ) + return; + + // [IE] Use span for copybin and its container to avoid bug with expanding editable height by + // absolutely positioned element. + var copybinName = ( editor.blockless || CKEDITOR.env.ie ) ? 'span' : 'div', + copybin = doc.createElement( copybinName ), + copybinContainer = doc.createElement( copybinName ), + // IE8 always jumps to the end of document. + needsScrollHack = CKEDITOR.env.ie && CKEDITOR.env.version < 9; + + copybinContainer.setAttributes( { + id: 'cke_copybin', + 'data-cke-temp': '1' + } ); + + // Position copybin element outside current viewport. + copybin.setStyles( { + position: 'absolute', + width: '1px', + height: '1px', + overflow: 'hidden' + } ); + + copybin.setStyle( editor.config.contentsLangDirection == 'ltr' ? 'left' : 'right', '-5000px' ); + + copybin.setHtml( '\u200b' + widget.wrapper.getOuterHtml() + '\u200b' ); + + // Save snapshot with the current state. + editor.fire( 'saveSnapshot' ); + + // Ignore copybin. + editor.fire( 'lockSnapshot' ); + + copybinContainer.append( copybin ); + editor.editable().append( copybinContainer ); + + var listener1 = editor.on( 'selectionChange', cancel, null, null, 0 ), + listener2 = widget.repository.on( 'checkSelection', cancel, null, null, 0 ); + + if ( needsScrollHack ) { + var docElement = doc.getDocumentElement().$, + scrollTop = docElement.scrollTop; + } + + // Once the clone of the widget is inside of copybin, select + // the entire contents. This selection will be copied by the + // native browser's clipboard system. + var range = editor.createRange(); + range.selectNodeContents( copybin ); + range.select(); + + if ( needsScrollHack ) + docElement.scrollTop = scrollTop; + + setTimeout( function() { + // [IE] Focus widget before removing copybin to avoid scroll jump. + if ( !isCut ) + widget.focus(); + + copybinContainer.remove(); + + listener1.removeListener(); + listener2.removeListener(); + + editor.fire( 'unlockSnapshot' ); + + if ( isCut ) { + widget.repository.del( widget ); + editor.fire( 'saveSnapshot' ); + } + }, 100 ); // Use 100ms, so Chrome (@Mac) will be able to grab the content. + } + + // Extracts classes array from style instance. + function getStyleClasses( style ) { + var attrs = style.getDefinition().attributes, + classes = attrs && attrs[ 'class' ]; + + return classes ? classes.split( /\s+/ ) : null; + } + + // [IE] Force keeping focus because IE sometimes forgets to fire focus on main editable + // when blurring nested editable. + // @context widget + function onEditableBlur() { + var active = CKEDITOR.document.getActive(), + editor = this.editor, + editable = editor.editable(); + + // If focus stays within editor override blur and set currentActive because it should be + // automatically changed to editable on editable#focus but it is not fired. + if ( ( editable.isInline() ? editable : editor.document.getWindow().getFrame() ).equals( active ) ) + editor.focusManager.focus( editable ); + } + + // Force selectionChange when editable was focused. + // Similar to hack in selection.js#~620. + // @context widget + function onEditableFocus() { + // Gecko does not support 'DOMFocusIn' event on which we unlock selection + // in selection.js to prevent selection locking when entering nested editables. + if ( CKEDITOR.env.gecko ) + this.editor.unlockSelection(); + + // We don't need to force selectionCheck on Webkit, because on Webkit + // we do that on DOMFocusIn in selection.js. + if ( !CKEDITOR.env.webkit ) { + this.editor.forceNextSelectionCheck(); + this.editor.selectionChange( 1 ); + } + } + + // Setup listener on widget#data which will update (remove/add) classes + // by comparing newly set classes with the old ones. + function setupDataClassesListener( widget ) { + // Note: previousClasses and newClasses may be null! + // Tip: for ( cl in null ) is correct. + var previousClasses = null; + + widget.on( 'data', function() { + var newClasses = this.data.classes, + cl; + + // When setting new classes one need to remember + // that he must break reference. + if ( previousClasses == newClasses ) + return; + + for ( cl in previousClasses ) { + // Avoid removing and adding classes again. + if ( !( newClasses && newClasses[ cl ] ) ) + this.removeClass( cl ); + } + for ( cl in newClasses ) + this.addClass( cl ); + + previousClasses = newClasses; + } ); + } + + function setupDragHandler( widget ) { + if ( !widget.draggable ) + return; + + var editor = widget.editor, + // Use getLast to find wrapper's direct descendant (#12022). + container = widget.wrapper.getLast( isDomDragHandlerContainer ), + img; + + // Reuse drag handler if already exists (#11281). + if ( container ) + img = container.findOne( 'img' ); + else { + container = new CKEDITOR.dom.element( 'span', editor.document ); + container.setAttributes( { + 'class': 'cke_reset cke_widget_drag_handler_container', + // Split background and background-image for IE8 which will break on rgba(). + style: 'background:rgba(220,220,220,0.5);background-image:url(' + editor.plugins.widget.path + 'images/handle.png)' + } ); + + img = new CKEDITOR.dom.element( 'img', editor.document ); + img.setAttributes( { + 'class': 'cke_reset cke_widget_drag_handler', + 'data-cke-widget-drag-handler': '1', + src: CKEDITOR.tools.transparentImageData, + width: DRAG_HANDLER_SIZE, + title: editor.lang.widget.move, + height: DRAG_HANDLER_SIZE + } ); + widget.inline && img.setAttribute( 'draggable', 'true' ); + + container.append( img ); + widget.wrapper.append( container ); + } + + widget.wrapper.on( 'mouseenter', widget.updateDragHandlerPosition, widget ); + setTimeout( function() { + widget.on( 'data', widget.updateDragHandlerPosition, widget ); + }, 50 ); + + if ( widget.inline ) { + img.on( 'dragstart', function( evt ) { + evt.data.$.dataTransfer.setData( 'text', JSON.stringify( { type: 'cke-widget', editor: editor.name, id: widget.id } ) ); + } ); + } else { + img.on( 'mousedown', onBlockWidgetDrag, widget ); + } + + widget.dragHandlerContainer = container; + } + + function onBlockWidgetDrag() { + var finder = this.repository.finder, + locator = this.repository.locator, + liner = this.repository.liner, + editor = this.editor, + editable = editor.editable(), + listeners = [], + sorted = [], + + // Harvest all possible relations and display some closest. + relations = finder.greedySearch(), + + buffer = CKEDITOR.tools.eventsBuffer( 50, function() { + locations = locator.locate( relations ); + + // There's only a single line displayed for D&D. + sorted = locator.sort( y, 1 ); + + if ( sorted.length ) { + liner.prepare( relations, locations ); + liner.placeLine( sorted[ 0 ] ); + liner.cleanup(); + } + } ), + + locations, y; + + // Let's have the "dragging cursor" over entire editable. + editable.addClass( 'cke_widget_dragging' ); + + // Cache mouse position so it is re-used in events buffer. + listeners.push( editable.on( 'mousemove', function( evt ) { + y = evt.data.$.clientY; + buffer.input(); + } ) ); + + function onMouseUp() { + var l; + + buffer.reset(); + + // Stop observing events. + while ( ( l = listeners.pop() ) ) + l.removeListener(); + + onBlockWidgetDrop.call( this, sorted ); + } + + // Mouseup means "drop". This is when the widget is being detached + // from DOM and placed at range determined by the line (location). + listeners.push( editor.document.once( 'mouseup', onMouseUp, this ) ); + + // Mouseup may occur when user hovers the line, which belongs to + // the outer document. This is, of course, a valid listener too. + listeners.push( CKEDITOR.document.once( 'mouseup', onMouseUp, this ) ); + } + + function onBlockWidgetDrop( sorted ) { + var finder = this.repository.finder, + liner = this.repository.liner, + editor = this.editor, + editable = this.editor.editable(); + + if ( !CKEDITOR.tools.isEmpty( liner.visible ) ) { + // Retrieve range for the closest location. + var range = finder.getRange( sorted[ 0 ] ); + + // Focus widget (it could lost focus after mousedown+mouseup) + // and save this state as the one where we want to be taken back when undoing. + this.focus(); + editor.fire( 'saveSnapshot' ); + // Group all following operations in one snapshot. + editor.fire( 'lockSnapshot', { dontUpdate: 1 } ); + + // Reset the fake selection, which will be invalidated by insertElementIntoRange. + // This avoids a situation when getSelection() still returns a fake selection made + // on widget which in the meantime has been moved to other place. That could cause + // an error thrown e.g. by saveSnapshot or stateUpdater. + editor.getSelection().reset(); + + // Attach widget at the place determined by range. + editable.insertElementIntoRange( this.wrapper, range ); + + // Focus again the dropped widget. + this.focus(); + + // Unlock snapshot and save new one, which will contain all changes done + // in this method. + editor.fire( 'unlockSnapshot' ); + editor.fire( 'saveSnapshot' ); + } + + // Clean-up custom cursor for editable. + editable.removeClass( 'cke_widget_dragging' ); + + // Clean-up all remaining lines. + liner.hideVisible(); + } + + function setupEditables( widget ) { + var editableName, + editableDef, + definedEditables = widget.editables; + + widget.editables = {}; + + if ( !widget.editables ) + return; + + for ( editableName in definedEditables ) { + editableDef = definedEditables[ editableName ]; + widget.initEditable( editableName, typeof editableDef == 'string' ? { selector: editableDef } : editableDef ); + } + } + + function setupMask( widget ) { + if ( !widget.mask ) + return; + + // Reuse mask if already exists (#11281). + var img = widget.wrapper.findOne( '.cke_widget_mask' ); + + if ( !img ) { + img = new CKEDITOR.dom.element( 'img', widget.editor.document ); + img.setAttributes( { + src: CKEDITOR.tools.transparentImageData, + 'class': 'cke_reset cke_widget_mask' + } ); + widget.wrapper.append( img ); + } + + widget.mask = img; + } + + // Replace parts object containing: + // partName => selector pairs + // with: + // partName => element pairs + function setupParts( widget ) { + if ( widget.parts ) { + var parts = {}, + el, partName; + + for ( partName in widget.parts ) { + el = widget.wrapper.findOne( widget.parts[ partName ] ); + parts[ partName ] = el; + } + widget.parts = parts; + } + } + + function setupWidget( widget, widgetDef ) { + setupWrapper( widget ); + setupParts( widget ); + setupEditables( widget ); + setupMask( widget ); + setupDragHandler( widget ); + setupDataClassesListener( widget ); + + // #11145: [IE8] Non-editable content of widget is draggable. + if ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 ) { + widget.wrapper.on( 'dragstart', function( evt ) { + var target = evt.data.getTarget(); + + // Allow text dragging inside nested editables or dragging inline widget's drag handler. + if ( !getNestedEditable( widget, target ) && !( widget.inline && isDomDragHandler( target ) ) ) + evt.data.preventDefault(); + } ); + } + + widget.wrapper.removeClass( 'cke_widget_new' ); + widget.element.addClass( 'cke_widget_element' ); + + widget.on( 'key', function( evt ) { + var keyCode = evt.data.keyCode; + + // ENTER. + if ( keyCode == 13 ) { + widget.edit(); + // CTRL+C or CTRL+X. + } else if ( keyCode == CKEDITOR.CTRL + 67 || keyCode == CKEDITOR.CTRL + 88 ) { + copySingleWidget( widget, keyCode == CKEDITOR.CTRL + 88 ); + return; // Do not preventDefault. + } else if ( keyCode in keystrokesNotBlockedByWidget || ( CKEDITOR.CTRL & keyCode ) || ( CKEDITOR.ALT & keyCode ) ) { + // Pass chosen keystrokes to other plugins or default fake sel handlers. + // Pass all CTRL/ALT keystrokes. + return; + } + + return false; + }, null, null, 999 ); + // Listen with high priority so it's possible + // to overwrite this callback. + + widget.on( 'doubleclick', function( evt ) { + if ( widget.edit() ) { + // We have to cancel event if edit method opens a dialog, otherwise + // link plugin may open extra dialog (#12140). + evt.cancel(); + } + } ); + + if ( widgetDef.data ) + widget.on( 'data', widgetDef.data ); + + if ( widgetDef.edit ) + widget.on( 'edit', widgetDef.edit ); + } + + function setupWidgetData( widget, startupData ) { + var widgetDataAttr = widget.element.data( 'cke-widget-data' ); + + if ( widgetDataAttr ) + widget.setData( JSON.parse( decodeURIComponent( widgetDataAttr ) ) ); + if ( startupData ) + widget.setData( startupData ); + + // Populate classes if they are not preset. + if ( !widget.data.classes ) + widget.setData( 'classes', widget.getClasses() ); + + // Unblock data and... + widget.dataReady = true; + + // Write data to element because this was blocked when data wasn't ready. + writeDataToElement( widget ); + + // Fire data event first time, because this was blocked when data wasn't ready. + widget.fire( 'data', widget.data ); + } + + function setupWrapper( widget ) { + // Retrieve widget wrapper. Assign an id to it. + var wrapper = widget.wrapper = widget.element.getParent(); + wrapper.setAttribute( 'data-cke-widget-id', widget.id ); + } + + function writeDataToElement( widget ) { + widget.element.data( 'cke-widget-data', encodeURIComponent( JSON.stringify( widget.data ) ) ); + } + + // + // WIDGET STYLE HANDLER --------------------------------------------------- + // + + ( function() { + + /** + * The class representing a widget style. It is an {@link CKEDITOR#STYLE_OBJECT object} like + * the styles handler for widgets. + * + * **Note:** This custom style handler does not support all methods of the {@link CKEDITOR.style} class. + * Not supported methods: {@link #applyToRange}, {@link #removeFromRange}, {@link #applyToObject}. + * + * @since 4.4 + * @class CKEDITOR.style.customHandlers.widget + * @extends CKEDITOR.style + */ + CKEDITOR.style.addCustomHandler( { + type: 'widget', + + setup: function( styleDefinition ) { + /** + * The name of widget to which this style can be applied. + * It is extracted from style definition's `widget` property. + * + * @property {String} widget + */ + this.widget = styleDefinition.widget; + }, + + apply: function( editor ) { + // Before CKEditor 4.4 wasn't a required argument, so we need to + // handle a case when it wasn't provided. + if ( !( editor instanceof CKEDITOR.editor ) ) + return; + + // Theoretically we could bypass checkApplicable, get widget from + // widgets.focused and check its name, what would be faster, but then + // this custom style would work differently than the default style + // which checks if it's applicable before applying or removeing itself. + if ( this.checkApplicable( editor.elementPath(), editor ) ) + editor.widgets.focused.applyStyle( this ); + }, + + remove: function( editor ) { + // Before CKEditor 4.4 wasn't a required argument, so we need to + // handle a case when it wasn't provided. + if ( !( editor instanceof CKEDITOR.editor ) ) + return; + + if ( this.checkApplicable( editor.elementPath(), editor ) ) + editor.widgets.focused.removeStyle( this ); + }, + + checkActive: function( elementPath, editor ) { + return this.checkElementMatch( elementPath.lastElement, 0, editor ); + }, + + checkApplicable: function( elementPath, editor ) { + // Before CKEditor 4.4 wasn't a required argument, so we need to + // handle a case when it wasn't provided. + if ( !( editor instanceof CKEDITOR.editor ) ) + return false; + + return this.checkElement( elementPath.lastElement ); + }, + + checkElementMatch: checkElementMatch, + + checkElementRemovable: checkElementMatch, + + /** + * Checks if an element is a {@link CKEDITOR.plugins.widget#wrapper wrapper} of a + * widget whose name matches the {@link #widget widget name} specified in the style definition. + * + * @param {CKEDITOR.dom.element} element + * @returns {Boolean} + */ + checkElement: function( element ) { + if ( !isDomWidgetWrapper( element ) ) + return false; + + var widgetElement = element.getFirst( isDomWidgetElement ); + return widgetElement && widgetElement.data( 'widget' ) == this.widget; + }, + + buildPreview: function( label ) { + return label || this._.definition.name; + }, + + /** + * Returns allowed content rules which should be registered for this style. + * Uses widget's {@link CKEDITOR.plugins.widget.definition#styleableElements} to make a rule + * allowing classes on specified elements or use widget's + * {@link CKEDITOR.plugins.widget.definition#styleToAllowedContentRules} method to transform a style + * into allowed content rules. + * + * @param {CKEDITOR.editor} The editor instance. + * @returns {CKEDITOR.filter.allowedContentRules} + */ + toAllowedContentRules: function( editor ) { + if ( !editor ) + return null; + + var widgetDef = editor.widgets.registered[ this.widget ], + classes, + rule = {}; + + if ( !widgetDef ) + return null; + + if ( widgetDef.styleableElements ) { + classes = this.getClassesArray(); + if ( !classes ) + return null; + + rule[ widgetDef.styleableElements ] = { + classes: classes, + propertiesOnly: true + }; + return rule; + } + if ( widgetDef.styleToAllowedContentRules ) + return widgetDef.styleToAllowedContentRules( this ); + return null; + }, + + /** + * Returns classes defined in the style in form of an array. + * + * @returns {String[]} + */ + getClassesArray: function() { + var classes = this._.definition.attributes && this._.definition.attributes[ 'class' ]; + + return classes ? CKEDITOR.tools.trim( classes ).split( /\s+/ ) : null; + }, + + /** + * Not implemented. + * + * @method applyToRange + */ + applyToRange: notImplemented, + + /** + * Not implemented. + * + * @method removeFromRange + */ + removeFromRange: notImplemented, + + /** + * Not implemented. + * + * @method applyToObject + */ + applyToObject: notImplemented + } ); + + function notImplemented() {} + + // @context style + function checkElementMatch( element, fullMatch, editor ) { + // Before CKEditor 4.4 wasn't a required argument, so we need to + // handle a case when it wasn't provided. + if ( !editor ) + return false; + + if ( !this.checkElement( element ) ) + return false; + + var widget = editor.widgets.getByElement( element, true ); + return widget && widget.checkStyleActive( this ); + } + + } )(); + + // + // EXPOSE PUBLIC API ------------------------------------------------------ + // + + CKEDITOR.plugins.widget = Widget; + Widget.repository = Repository; + Widget.nestedEditable = NestedEditable; +} )(); + +/** + * An event fired when a widget definition is registered by the {@link CKEDITOR.plugins.widget.repository#add} method. + * It is possible to modify the definition being registered. + * + * @event widgetDefinition + * @member CKEDITOR.editor + * @param {CKEDITOR.plugins.widget.definition} data Widget definition. + */ + +/** + * This is an abstract class that describes the definition of a widget. + * It is a type of {@link CKEDITOR.plugins.widget.repository#add} method's second argument. + * + * Widget instances inherit from registered widget definitions, although not in a prototypal way. + * They are simply extended with corresponding widget definitions. Note that not all properties of + * the widget definition become properties of a widget. Some, like {@link #data} or {@link #edit}, become + * widget's events listeners. + * + * @class CKEDITOR.plugins.widget.definition + * @abstract + * @mixins CKEDITOR.feature + */ + +/** + * Widget definition name. It is automatically set when the definition is + * {@link CKEDITOR.plugins.widget.repository#add registered}. + * + * @property {String} name + */ + +/** + * The method executed while initializing a widget, after a widget instance + * is created, but before it is ready. It is executed before the first + * {@link CKEDITOR.plugins.widget#event-data} is fired so it is common to + * use the `init` method to populate widget data with information loaded from + * the DOM, like for exmaple: + * + * init: function() { + * this.setData( 'width', this.element.getStyle( 'width' ) ); + * + * if ( this.parts.caption.getStyle( 'display' ) != 'none' ) + * this.setData( 'showCaption', true ); + * } + * + * @property {Function} init + */ + +/** + * The function to be used to upcast an element to this widget or a + * comma-separated list of upcast methods from the {@link #upcasts} object. + * + * The upcast function **is not** executed in the widget context (because the widget + * does not exist yet) and two arguments are passed: + * + * * `element` ({@link CKEDITOR.htmlParser.element}) – The element to be checked. + * * `data` (`Object`) – The object which can be extended with data which will then be passed to the widget. + * + * An element will be upcasted if a function returned `true` or an instance of + * a {@link CKEDITOR.htmlParser.element} if upcasting meant DOM structure changes + * (in this case the widget will be initialized on the returned element). + * + * @property {String/Function} upcast + */ + +/** + * The object containing functions which can be used to upcast this widget. + * Only those pointed by the {@link #upcast} property will be used. + * + * In most cases it is appropriate to use {@link #upcast} directly, + * because majority of widgets need just one method. + * However, in some cases the widget author may want to expose more than one variant + * and then this property may be used. + * + * upcasts: { + * // This function may upcast only figure elements. + * figure: function() { + * // ... + * }, + * // This function may upcast only image elements. + * image: function() { + * // ... + * }, + * // More variants... + * } + * + * // Then, widget user may choose which upcast methods will be enabled. + * editor.on( 'widgetDefinition', function( evt ) { + * if ( evt.data.name == 'image' ) + * evt.data.upcast = 'figure,image'; // Use both methods. + * } ); + * + * @property {Object} upcasts + */ + +/** + * The function to be used to downcast this widget or + * a name of the downcast option from the {@link #downcasts} object. + * + * The downcast funciton will be executed in the {@link CKEDITOR.plugins.widget} context + * and with `widgetElement` ({@link CKEDITOR.htmlParser.element}) argument which is + * the widget's main element. + * + * The function may return an instance of the {@link CKEDITOR.htmlParser.node} class if the widget + * needs to be downcasted to a different node than the widget's main element. + * + * @property {String/Function} downcast + */ + +/** + * The object containing functions which can be used to downcast this widget. + * Only the one pointed by the {@link #downcast} property will be used. + * + * In most cases it is appropriate to use {@link #downcast} directly, + * because majority of widgets have just one variant of downcasting (or none at all). + * However, in some cases the widget author may want to expose more than one variant + * and then this property may be used. + * + * downcasts: { + * // This downcast may transform the widget into the figure element. + * figure: function() { + * // ... + * }, + * // This downcast may transform the widget into the image element with data-* attributes. + * image: function() { + * // ... + * } + * } + * + * // Then, the widget user may choose one of the downcast options when setting up his editor. + * editor.on( 'widgetDefinition', function( evt ) { + * if ( evt.data.name == 'image' ) + * evt.data.downcast = 'figure'; + * } ); + * + * @property downcasts + */ + +/** + * If set, it will be added as the {@link CKEDITOR.plugins.widget#event-edit} event listener. + * This means that it will be executed when a widget is being edited. + * See the {@link CKEDITOR.plugins.widget#method-edit} method. + * + * @property {Function} edit + */ + +/** + * If set, it will be added as the {@link CKEDITOR.plugins.widget#event-data} event listener. + * This means that it will be executed every time the {@link CKEDITOR.plugins.widget#property-data widget data} changes. + * + * @property {Function} data + */ + +/** + * The method to be executed when the widget's command is executed in order to insert a new widget + * (widget of this type is not focused). If not defined, then the default action will be + * performed which means that: + * + * * An instance of the widget will be created in a detached {@link CKEDITOR.dom.documentFragment document fragment}, + * * The {@link CKEDITOR.plugins.widget#method-edit} method will be called to trigger widget editing, + * * The widget element will be inserted into DOM. + * + * @property {Function} insert + */ + +/** + * The name of a dialog window which will be opened on {@link CKEDITOR.plugins.widget#method-edit}. + * If not defined, then the {@link CKEDITOR.plugins.widget#method-edit} method will not perform any action and + * widget's command will insert a new widget without opening a dialog window first. + * + * @property {String} dialog + */ + +/** + * The template which will be used to create a new widget element (when the widget's command is executed). + * This string is populated with {@link #defaults default values} by using the {@link CKEDITOR.template} format. + * Therefore it has to be a valid {@link CKEDITOR.template} argument. + * + * @property {String} template + */ + +/** + * The data object which will be used to populate the data of a newly created widget. + * See {@link CKEDITOR.plugins.widget#property-data}. + * + * defaults: { + * showCaption: true, + * align: 'none' + * } + * + * @property defaults + */ + +/** + * An object containing definitions of widget components (part name => CSS selector). + * + * parts: { + * image: 'img', + * caption: 'div.caption' + * } + * + * @property parts + */ + +/** + * An object containing definitions of nested editables (editable name => {@link CKEDITOR.plugins.widget.nestedEditable.definition}). + * + * editables: { + * header: 'h1', + * content: { + * selector: 'div.content', + * allowedContent: 'p strong em; a[!href]' + * } + * } + * + * @property editables + */ + +/** + * Widget name displayed in elements path. + * + * @property {String} pathName + */ + +/** + * If set to `true`, the widget's element will be covered with a transparent mask. + * This will prevent its content from being clickable, which matters in case + * of special elements like embedded Flash or iframes that generate a separate "context". + * + * @property {Boolean} mask + */ + +/** + * If set to `true/false`, it will force the widget to be either an inline or a block widget. + * If not set, the widget type will be determined from the widget element. + * + * Widget type influences whether a block (`div`) or an inline (`span`) element is used + * for the wrapper. + * + * @property {Boolean} inline + */ + +/** + * The label for the widget toolbar button. + * + * editor.widgets.add( 'simplebox', { + * button: 'Create a simple box' + * } ); + * + * editor.widgets.add( 'simplebox', { + * button: editor.lang.simplebox.title + * } ); + * + * @property {String} button + */ + +/** + * Whether widget should be draggable. Defaults to `true`. + * If set to `false` drag handler will not be displayed when hovering widget. + * + * @property {Boolean} draggable + */ + +/** + * Names of element(s) (separated by spaces) for which the {@link CKEDITOR.filter} should allow classes + * defined in the widget styles. For example if your widget is upcasted from a simple `
` + * element, then in order to make it styleable you can set: + * + * editor.widgets.add( 'customWidget', { + * upcast: function( element ) { + * return element.name == 'div'; + * }, + * + * // ... + * + * styleableElements: 'div' + * } ); + * + * Then, when the following style is defined: + * + * { + * name: 'Thick border', type: 'widget', widget: 'customWidget', + * attributes: { 'class': 'thickBorder' } + * } + * + * a rule allowing the `thickBorder` class for `div` elements will be registered in the {@link CKEDITOR.filter}. + * + * If you need to have more freedom when transforming widget style to allowed content rules, + * you can use the {@link #styleToAllowedContentRules} callback. + * + * @since 4.4 + * @property {String} styleableElements + */ + +/** + * Function transforming custom widget's {@link CKEDITOR.style} instance into + * {@link CKEDITOR.filter.allowedContentRules}. It may be used when a static + * {@link #styleableElements} property is not enough to inform the {@link CKEDITOR.filter} + * what HTML features should be enabled when allowing the given style. + * + * In most cases, when style's classes just have to be added to element name(s) used by + * the widget element, it is recommended to use simpler {@link #styleableElements} property. + * + * In order to get parsed classes from the style definition you can use + * {@link CKEDITOR.style.customHandlers.widget#getClassesArray}. + * + * For example, if you want to use the [object format of allowed content rules](#!/guide/dev_allowed_content_rules-section-object-format), + * to specify `match` validator, your implementation could look like this: + * + * editor.widgets.add( 'customWidget', { + * // ... + * + * styleToAllowedContentRules: funciton( style ) { + * // Retrieve classes defined in the style. + * var classes = style.getClassesArray(); + * + * // Do something crazy - for example return allowed content rules in object format, + * // with custom match property and propertiesOnly flag. + * return { + * h1: { + * match: isWidgetElement, + * propertiesOnly: true, + * classes: classes + * } + * }; + * } + * } ); + * + * @since 4.4 + * @property {Function} styleToAllowedContentRules + * @param {CKEDITOR.style.customHandlers.widget} style The style to be transformed. + * @returns {CKEDITOR.filter.allowedContentRules} + */ + +/** + * This is an abstract class that describes the definition of a widget's nested editable. + * It is a type of values in the {@link CKEDITOR.plugins.widget.definition#editables} object. + * + * In the simplest case the definition is a string which is a CSS selector used to + * find an element that will become a nested editable inside the widget. Note that + * the widget element can be a nested editable, too. + * + * In the more advanced case a definition is an object with a required `selector` property. + * + * editables: { + * header: 'h1', + * content: { + * selector: 'div.content', + * allowedContent: 'p strong em; a[!href]' + * } + * } + * + * @class CKEDITOR.plugins.widget.nestedEditable.definition + * @abstract + */ + +/** + * The CSS selector used to find an element which will become a nested editable. + * + * @property {String} selector + */ + +/** + * The [Advanced Content Filter](#!/guide/dev_advanced_content_filter) rules + * which will be used to limit the content allowed in this nested editable. + * This option is similar to {@link CKEDITOR.config#allowedContent} and one can + * use it to limit the editor features available in the nested editable. + * + * @property {CKEDITOR.filter.allowedContentRules} allowedContent + */ + +/** + * Nested editable name displayed in elements path. + * + * @property {String} pathName + */ Index: lams_central/web/includes/javascript/ckconfig_custom.js =================================================================== diff -u -ra3b7e180b74ccf08ee8882759518c902db4b63da -r7cb2670e5527930c6c50678be8770e6946ee7916 --- lams_central/web/includes/javascript/ckconfig_custom.js (.../ckconfig_custom.js) (revision a3b7e180b74ccf08ee8882759518c902db4b63da) +++ lams_central/web/includes/javascript/ckconfig_custom.js (.../ckconfig_custom.js) (revision 7cb2670e5527930c6c50678be8770e6946ee7916) @@ -1,5 +1,5 @@ CKEDITOR.config.toolbar_Default = [ - ['Source','-','Maximize', 'Preview','PasteFromWord','Undo','Redo','Bold','Italic','Underline', '-','Subscript','Superscript','NumberedList','BulletedList','-','Outdent','Indent','JustifyLeft','JustifyCenter','JustifyRight','JustifyBlock','TextColor','BGColor','equation','-',], + ['Source','-','Maximize', 'Preview','PasteFromWord','Undo','Redo','Bold','Italic','Underline', '-','Subscript','Superscript','NumberedList','BulletedList','-','Outdent','Indent','JustifyLeft','JustifyCenter','JustifyRight','JustifyBlock','TextColor','BGColor','Mathjax','-'], ['Paint_Button','MoviePlayer','Kaltura','Image','Link','Iframe','Table','HorizontalRule','Smiley','SpecialChar','Templates','Format','Font','FontSize','About'] ] ; // removing Video Recorder from default tool bar LDEV-2961 @@ -11,7 +11,7 @@ ['Bold','Italic','Underline', '-','Subscript','Superscript'], ['NumberedList','BulletedList','-','Outdent','Indent'], ['JustifyLeft','JustifyCenter','JustifyRight','JustifyBlock'], - ['equation','About'], + ['Mathjax','About'], ['TextColor','BGColor'], ['Image','Table','HorizontalRule','Smiley','SpecialChar'], ['Format','Font','FontSize'] @@ -23,7 +23,7 @@ ['Bold','Italic','Underline', '-','Subscript','Superscript'], ['NumberedList','BulletedList','-','Outdent','Indent'], ['JustifyLeft','JustifyCenter','JustifyRight','JustifyBlock'], - ['equation','About'], + ['Mathjax','About'], ['TextColor','BGColor'], ['Table','HorizontalRule','Smiley','SpecialChar'], ['Format','Font','FontSize'] @@ -36,14 +36,14 @@ ['NumberedList','BulletedList','-','Outdent','Indent'], ['JustifyLeft','JustifyCenter','JustifyRight','JustifyBlock'], ['wikilink','Link','Image'], - ['equation','About'], + ['Mathjax','About'], ['TextColor','BGColor'], ['Table','HorizontalRule','Smiley','SpecialChar'], ['Format','Font','FontSize'] ] ; CKEDITOR.config.toolbar_CustomPedplanner = [ - ['Source','-','Maximize','Preview','PasteFromWord','Bold','Italic','Underline', '-','NumberedList','BulletedList','-','Outdent','Indent','JustifyLeft','JustifyCenter','JustifyRight','JustifyBlock','TextColor','BGColor','equation'], + ['Source','-','Maximize','Preview','PasteFromWord','Bold','Italic','Underline', '-','NumberedList','BulletedList','-','Outdent','Indent','JustifyLeft','JustifyCenter','JustifyRight','JustifyBlock','TextColor','BGColor','Mathjax'], ['Image','Link','Iframe','Table','Smiley','Font','FontSize'] ] ; @@ -65,7 +65,7 @@ CKEDITOR.config.format_tags = 'div;h1;h2;h3;h4;h5;h6;pre;address;p' ; CKEDITOR.config.enterMode = 'div'; CKEDITOR.plugins.addExternal('wikilink', CKEDITOR.basePath + '../tool/lawiki10/wikilink/', 'plugin.js'); -CKEDITOR.config.extraPlugins = 'kaltura,wikilink,equation,paint,movieplayer,iframe'; +CKEDITOR.config.extraPlugins = 'kaltura,lineutils,widget,wikilink,mathjax,paint,movieplayer,iframe'; CKEDITOR.config.enterMode = CKEDITOR.ENTER_DIV; CKEDITOR.config.removePlugins = 'elementspath'; CKEDITOR.config.allowedContent = true;