diff options
Diffstat (limited to 'addons/website/static/lib/ckeditor/plugins/widget/plugin.js')
| -rw-r--r-- | addons/website/static/lib/ckeditor/plugins/widget/plugin.js | 2180 |
1 files changed, 0 insertions, 2180 deletions
diff --git a/addons/website/static/lib/ckeditor/plugins/widget/plugin.js b/addons/website/static/lib/ckeditor/plugins/widget/plugin.js deleted file mode 100644 index 8763f663783..00000000000 --- a/addons/website/static/lib/ckeditor/plugins/widget/plugin.js +++ /dev/null @@ -1,2180 +0,0 @@ -/** - * @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md or http://ckeditor.com/license - */ - -'use strict'; - -(function() { - - var DRAG_HANDLER_SIZE = 14; - - CKEDITOR.plugins.add( 'widget', { - 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;' + - 'opacity:0.75;' + - 'transition:height 0s 0.2s' + // Delay hiding drag handler. - '}' + - '.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' + - '}' + - '.cke_widget_mask{' + - 'position:absolute;' + - 'top:0;' + - 'left:0;' + - 'width:100%;' + - 'height:100%' + - '}' - ); - }, - - beforeInit: function( editor ) { - editor.widgets = new Repository( editor ); - }, - - afterInit: function( editor ) { - addWidgetButtons( editor ); - setupContextMenu( editor ); - } - } ); - - /** - * @class CKEDITOR.plugins.widget.repository - * @mixins CKEDITOR.event - */ - function Repository( editor ) { - this.editor = editor; - this.registered = {}; - this.instances = {}; - this.selected = []; - this.focused = null; - this.widgetHoldingFocusedEditable = null; - this._ = { - nextId: 0, - upcasts: [], - filters: {} - }; - - setupDataProcessing( this ); - setupWidgetsObserver( this ); - setupSelectionObserver( this ); - setupMouseObserver( this ); - setupKeyboardObserver( this ); - setupDragAndDrop( this ); - } - - Repository.prototype = { - /** - * Minimum interval between selection checks. - * - * @private - */ - MIN_SELECTION_CHECK_INTERVAL: 500, - - /** - * Minimum interval between widgets checks. - * - * @private - */ - MIN_WIDGETS_CHECK_INTERVAL: 1000, - - /** - * Adds widget definition to the repository. - * - * @param {String} name - * @param {CKEDITOR.plugins.widget.definition} widgetDef - * @returns {CKEDITOR.plugins.widget.registeredDefinition} - */ - add: function( name, widgetDef ) { - // Create prototyped copy of original widget defintion, so we won't modify it. - widgetDef = CKEDITOR.tools.prototypedCopy( widgetDef ); - widgetDef.name = name; - widgetDef.repository = this; - widgetDef.definition = widgetDef; - - widgetDef._ = widgetDef._ || {}; - - this.editor.fire( 'widgetDefinition', widgetDef ); - - if ( widgetDef.template ) - widgetDef.template = new CKEDITOR.template( widgetDef.template ); - - addWidgetCommand( this.editor, widgetDef ); - addWidgetProcessors( this, widgetDef ); - - // Register widget automatically if it does not have a button. - if ( !widgetDef.button ) - this.editor.addFeature( widgetDef ); - - this.registered[ name ] = widgetDef; - - return widgetDef; - }, - - /** - * Checks selection to update widgets states (select and focus). - * - * This method is triggered by {@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 = isWidgetWrapper2; - - while ( ( wrapper = walker.next() ) ) - updater.select( this.getByElement( wrapper ) ); - - updater.commit(); - }, - - /** - * Checks if all widgets instances are still present in DOM. - * Destroys those which are not. - * - * This method is triggered by {@link #event-checkWidigets} event. - */ - checkWidgets: function() { - if ( this.editor.mode != 'wysiwyg' ) - return; - - var toBeDestroyed = [], - editable = this.editor.editable(), - instances = this.instances, - id; - - if ( !editable ) - return; - - for ( id in instances ) { - if ( !editable.contains( instances[ id ].wrapper ) ) - this.destroy( instances[ id ], true ); - } - }, - - 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 ); - }, - - /** - * Removes and destroys widget instance. - * - * @param {CKEDITOR.plugins.widget} widget - * @param {Boolean} [offline] Whether widget is offline (detached from DOM tree) - - * in this case 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 ); - }, - - /** - * Removes and destroys all widgets instances. - * - * @param {Boolean} [offline] Whether widgets are offline (detached from DOM tree) - - * in this case 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 ); - } - }, - - /** - * Gets widget instance by element which may be - * widget's wrapper or any of its children. - * - * @param {CKEDITOR.dom.element} element - * @param {Boolean} [checkWrapperOnly] Check only if `element` equals wrapper. - * @returns {CKEDITOR.plugins.widget} Widget instance or `null`. - */ - getByElement: function( element, checkWrapperOnly ) { - if ( !element ) - return null; - - var wrapper; - - for ( var id in this.instances ) { - wrapper = this.instances[ id ].wrapper; - if ( wrapper.equals( element ) || ( !checkWrapperOnly && wrapper.contains( element ) ) ) - return this.instances[ id ]; - } - - return null; - }, - - /** - * Initializes widget on given element if widget hasn't - * been initialzed on it yet. - * - * @param {CKEDITOR.dom.element} element - * @param {String/CKEDITOR.plugins.widget.definition} widgetDef Name of a widget type or a widget definition. - * Widget definition should be previously registered by {@link CKEDITOR.plugins.widget.repository#add}. - * @param startupData Widget's startup data (has precedence over defaults one). - * @returns {CKEDITOR.plugins.widget} The widget instance or null if there's no widget for 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 initialzed 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 widget by element - 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 - * haven't been initialized yet. - * - * @param {CKEDITOR.dom.element} [container=editor.editable()] Container which will be checked for not - * initialized widgets. Defaults to editor's editable element. - * @returns {CKEDITOR.plugins.widget[]} Array of widget instances which have been initialized. - */ - 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( isWidgetElement2 ) ); - if ( instance ) - newInstances.push( instance ); - } - - return newInstances; - }, - - /** - * Wraps element with a widget container. - * - * If this method is called on {@link CKEDITOR.htmlParser.element}, then it will - * also take care of fixing DOM after wrapping (wrapper may not be allowed in element's parent). - * - * @param {CKEDITOR.dom.element/CKEDITOR.htmlParser.element} The widget element to be wrapperd. - * @param {String} [widgetName] - * @returns {CKEDITOR.dom.element/CKEDITOR.htmlParser.element} The wrapper element or `null` if - * widget of this type 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 ) ); - - // 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 ) ); - - 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; - } - - // %REMOVE_START% - // Expose for tests. - , - getNestedEditable: getNestedEditable, - - createEditableFilter: createEditableFilter - - // %REMOVE_END% - }; - - CKEDITOR.event.implementOn( Repository.prototype ); - - - /** - * @class CKEDITOR.plugins.widget - * @mixins CKEDITOR.event - */ - 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, - - inline: element.getParent().getName() == 'span', - - /** - * Widget's main element. - * - * @readonly - * @property {CKEDITOR.dom.element} - */ - element: element, - - /** - * Widget's data object. - * - * Data can only be set by {@link #setData} method. - * - * @readonly - */ - data: CKEDITOR.tools.extend( {}, typeof widgetDef.defaults == 'function' ? widgetDef.defaults() : widgetDef.defaults ), - - /** - * Is data ready. Set to `true` when data from all sources - * ({@link CKEDITOR.plugins.widget.definition#defaults}, set - * in {@link #init} method and loaded from widget's element) - * are finally loaded. This is immediately followed by first {@link #event-data}. - * - * @readonly - */ - dataReady: false, - - // Revert what widgetDef could override (automatic #edit listener). - edit: Widget.prototype.edit, - - /** - * Contains nested editable which currently holds focus. - * - * @readonly - * @property {CKEDITOR.dom.element} - */ - focusedEditable: null, - - // 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 ); - - /** - * Object of widget's component elements. - * - * For every `partName => selector` pair in {@link CKEDITOR.plugins.widget.definition#parts} - * one `partName => element` pair is added to this object during - * widget initialization. - * - * @property {Object} parts - */ - - 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 = { - /** - * Destroys this widget instance. - * - * Use {@link CKEDITOR.plugins.widget.repository#destroy} when possible instead of this method. - * - * This method fires {#event-destroy} event. - * - * @param {Boolean} [offline] Whether widget is offline (detached from DOM tree) - - * in this case DOM (attributes, classes, etc.) will not be cleaned up. - */ - destroy: function( offline ) { - var editor = this.editor; - - 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 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' ] ); - } - - delete this.editables[ editableName ]; - }, - - /** - * Starts widget editing. - * - * This method fires {@link CKEDITOR.plugins.widget#event-edit} event - * which may be cancelled in order to prevent from opening dialog. - * - * Dialog name is obtained from event's data `dialog` property or - * from {@link CKEDITOR.plugins.widget.definition#dialog}. - */ - 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 ) || !evtData.dialog ) - return; - - this.editor.openDialog( evtData.dialog, function( dialog ) { - var showListener, - okListener; - - // Allow to add a custom dialog handler. - if ( !that.fire( 'dialog', dialog ) ) - return; - - // Make widget accessible beyond setup and commit. - dialog._.widget = that; - - 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(); - } ); - } ); - }, - - /** - * Initializes nested editable. - * - * **Note**: only elements from {@link CKEDITOR.dtd#$editable} may become editables. - * - * @param {String} editableName The nested editable name. - * @param {CKEDITOR.plugins.widget.nestedEditableDefinition} definition The definition of 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 - } ); - 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' ); - - 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 widget has already been initialized. This means, for example, - * that widget has mask, element styles have been transferred to wrapper etc. - * - * @returns {Boolean} - */ - isInited: function() { - return !!( this.wrapper && this.inited ); - }, - - /** - * TODO - * - * @returns {Boolean} - */ - isReady: function() { - return this.isInited() && this.ready; - }, - - /** - * Focuses widget by selecting it. - */ - focus: function() { - var sel = this.editor.getSelection(); - - if ( sel ) - sel.fake( this.wrapper ); - - // Always focus editor (not only when focusManger.hasFocus is false) (because of #10483). - this.editor.focus(); - }, - - /** - * Sets widget value(s) in {@link #propeorty-data} object. - * If given value(s) modifies current ones {@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 JSON string, so therefore {@link #property-data} should contain - * only serializable data. - * - * @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 widget's focus state. Usually executed automatically after - * widget has been focused by {@link #focus} method or selection was moved - * out of 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 widget's select state. Usually executed automatically after - * widget has been selected by {@link #focus} method or 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; - } - }; - - CKEDITOR.event.implementOn( Widget.prototype ); - - - /** - * Wrapper class for editable elements inside widgets. - * - * Don't 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; - this.filter = config.filter; - } - - NestedEditable.prototype = CKEDITOR.tools.extend( CKEDITOR.tools.prototypedCopy( CKEDITOR.dom.element.prototype ), { - setData: function( data ) { - data = this.editor.dataProcessor.toHtml( data, { - context: this.getName(), - filter: this.filter, - enterMode: this.filter ? this.filter.getAllowedEnterMode() : this.editor.enterMode - } ); - this.setHtml( data ); - }, - - getData: function() { - return this.editor.dataProcessor.toDataFormat( this.getHtml(), { - context: this.getName(), - filter: this.filter, - enterMode: this.filter ? this.filter.getAllowedEnterMode() : this.editor.enterMode - } ); - } - } ); - - - // - // 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.registeredDefinition} 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() ), - editWasCanceled = true; - - // 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 ) { - editWasCanceled = false; - - 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(); - } ); - } ); - } - // Dialog hasn't been set, so insert widget now. - else - finalizeCreation(); - }, null, null, 999 ); - - instance.edit(); - - // Remove listener in case someone canceled it before this - // listener was executed. - editListener.removeListener(); - - // In case edit was canceled - finalize creation here which should happen anyway (just without - // initial edit). - if ( editWasCanceled ) - finalizeCreation(); - } - - function finalizeCreation() { - var wrapper = temp.getFirst(); - if ( wrapper && isWidgetWrapper2( wrapper ) ) { - editor.insertElement( wrapper ); - - var widget = editor.widgets.getByElement( wrapper ); - // Fire postponed #ready event. - widget.ready = true; - widget.fire( 'ready' ); - widget.focus(); - } - } - }, - - 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 ] ); - } - // Single rule which is automatically activated. - else - widgetsRepo._.upcasts.push( [ upcast, widgetDef.name ] ); - } - - function blurWidget( widgetsRepo, widget ) { - widgetsRepo.focused = null; - - if ( widget.isInited() ) { - // Widget could be destroyed in the meantime - e.g. data could be set. - widgetsRepo.fire( 'widgetBlurred', { widget: widget } ); - widget.setFocused( false ); - } - } - - // 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( isWidgetElement2 ); - // 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 ); - } - // Otherwise - something is wrong... clean this up. - else - 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; - } - - // 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 ( isNestedEditable2( 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 isWidgetElement( element ) { - return element.type == CKEDITOR.NODE_ELEMENT && !!element.attributes[ 'data-widget' ]; - } - - // @param {CKEDITOR.dom.element} - function isWidgetElement2( 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 isWidgetWrapper( element ) { - return element.type == CKEDITOR.NODE_ELEMENT && element.attributes[ 'data-cke-widget-wrapper' ]; - } - - // @param {CKEDITOR.dom.element} - function isWidgetWrapper2( element ) { - return element.type == CKEDITOR.NODE_ELEMENT && element.hasAttribute( 'data-cke-widget-wrapper' ); - } - - // @param {CKEDITOR.dom.element} - function isNestedEditable2( node ) { - return node.type == CKEDITOR.NODE_ELEMENT && node.hasAttribute( 'data-cke-widget-editable' ); - } - - function moveSelectionToDropPosition( editor, dropEvt ) { - var $evt = dropEvt.data.$, - $range, - range = editor.createRange(); - - // Make testing possible. - if ( dropEvt.data.testRange ) { - dropEvt.data.testRange.select(); - return; - } - - // 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( '<span id="' + id + '">\u200b</span>' ); - - var span = editor.document.getById( id ); - range.moveToPosition( span, CKEDITOR.POSITION_BEFORE_START ); - span.remove(); - } - - range.select(); - } - - function moveWidget( editor, sourceWidget ) { - var widgetHtml = sourceWidget.wrapper.getOuterHtml(); - - sourceWidget.wrapper.remove(); - editor.widgets.destroy( sourceWidget, true ); - - // Create snapshot for the removed widget. - editor.fire( 'saveSnapshot' ); - - // Lock snapshot while pasting to merge those changes with the previous snapshot. - // This way we are grouping all changes done by moveWidget into one snapshot. - editor.fire( 'lockSnapshot' ); - editor.execCommand( 'paste', widgetHtml ); - editor.fire( 'unlockSnapshot' ); - } - - 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 ) { - widgetsRepo.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 ) - widgetsRepo.editor.setActiveFilter( editableInstance.filter ); - } else { - if ( !offline ) - widget.focusedEditable.removeClass( 'cke_widget_editable_focused' ); - - widget.focusedEditable = null; - widgetsRepo.widgetHoldingFocusedEditable = null; - widgetsRepo.editor.setActiveFilter( null ); - } - - widgetsRepo.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', {} ); - } ); - } - - // Set up data processing like: - // * toHtml/toDataFormat, - // * pasting handling, - // * undo/redo handling. - function setupDataProcessing( widgetsRepo ) { - var editor = widgetsRepo.editor; - - setupUpcasting( widgetsRepo ); - setupDowncasting( widgetsRepo ); - - editor.on( 'contentDomUnload', function() { - widgetsRepo.destroyAll( true ); - } ); - - // Handle pasted single widget. - editor.on( 'paste', function( evt ) { - evt.data.dataValue = evt.data.dataValue.replace( - /^(?:<div id="cke_copybin">)?<span [^>]*data-cke-copybin-start="1"[^>]*>.?<\/span>([\s\S]+)<span [^>]*data-cke-copybin-end="1"[^>]*>.?<\/span>(?:<\/div>)?$/, - '$1' - ); - } ); - } - - function setupDowncasting( widgetsRepo ) { - var editor = widgetsRepo.editor, - downcastingSessions = {}, - nestedEditableScope = false; - - // 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( isWidgetElement ); - toBeDowncasted.push( { - wrapper: element, - element: widgetElement, - widget: widget - } ); - - // 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 ) { - delete attrs[ 'contenteditable' ]; - - // Replace nested editable's content with its output data. - var editable = toBeDowncasted[ toBeDowncasted.length - 1 ].widget.editables[ attrs[ 'data-cke-widget-editable' ] ]; - element.setHtml( editable.getData() ); - - // 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 ); - }, 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; - - while ( ( toBe = toBeDowncasted.shift() ) ) { - widget = toBe.widget; - widgetElement = toBe.element; - retElement = widget._.downcastFn && widget._.downcastFn.call( widget, widgetElement ); - - // Returned element always defaults to widgetElement. - if ( !retElement ) - retElement = widgetElement; - - toBe.wrapper.replaceWith( retElement ); - } - }, null, null, 13 ); - } - - function setupDragAndDrop( widgetsRepo ) { - var editor = widgetsRepo.editor; - - editor.on( 'contentDom', function() { - var editable = editor.editable(); - - editable.attachListener( editable, 'drop', function( evt ) { - var dataStr = evt.data.$.dataTransfer.getData( 'text' ), - dataObj, - sourceWidget; - - 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; - - // Save the snapshot with the state before moving widget. - // TODO unfortunately at this stage widget is not focused any more so - // undoing will not select widget which was moved. - editor.fire( 'saveSnapshot' ); - - moveSelectionToDropPosition( editor, evt ); - - // 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( moveWidget, 0, editor, sourceWidget ); - else - moveWidget( editor, sourceWidget ); - } ); - } ); - } - - // 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(); - - widget = widgetsRepo.getByElement( target ); - mouseDownOnDragHandler = 0; // Reset. - - // Ignore mousedown on drag and drop handler. - if ( target.type == CKEDITOR.NODE_ELEMENT && target.hasAttribute( 'data-cke-widget-drag-handler' ) ) { - mouseDownOnDragHandler = 1; - return; - } - - // Widget was clicked, but not editable nested in it. - if ( widget ) { - if ( !getNestedEditable( widget.wrapper, target ) ) { - evt.data.preventDefault(); - if ( !CKEDITOR.env.ie ) - widget.focus(); - } - // Reset widget so mouseup listener is not confused. - else - 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( evt ) { - 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 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( evt ) { - // 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 ); - } ); - } - - function setupUpcasting( widgetsRepo ) { - var editor = widgetsRepo.editor, - upcasts = widgetsRepo._.upcasts, - processedWidgetOnly, - snapshotLoaded; - - 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(); - } ); - - editor.on( 'afterPaste', function() { - editor.fire( 'lockSnapshot' ); - - // Init is enough (no clean up needed), - // because inserted widgets were cleaned up by toHtml. - var newInstances = widgetsRepo.initOnAll(); - - // If just a widget was pasted and nothing more focus it. - if ( processedWidgetOnly && newInstances.length == 1 ) - newInstances[ 0 ].focus(); - - editor.fire( 'unlockSnapshot' ); - } ); - - // 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 ); - - // 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 toBeWrapped = [], - toBe, - element; - - evt.data.dataValue.forEach( function( element ) { - // 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( isWidgetElement ); - - 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 ( upcasts.length ) { - var upcast, upcasted, - data, - i = 0, - l = upcasts.length; - - for ( ; i < l; ++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' ] = JSON.stringify( data ); - - toBeWrapped.push( [ element, upcast[ 1 ] ] ); - - // Do not iterate over descendants. - return false; - } - } - } - }, CKEDITOR.NODE_ELEMENT ); - - // Clean up and wrap all queued elements. - while ( ( toBe = toBeWrapped.pop() ) ) { - cleanUpWidgetElement( toBe[ 0 ] ); - widgetsRepo.wrapElement( toBe[ 0 ], toBe[ 1 ] ); - } - - // Used to determine whether only widget was pasted. - processedWidgetOnly = evt.data.dataValue.children.length == 1 && - isWidgetWrapper( evt.data.dataValue.children[ 0 ] ); - }, null, null, 8 ); - } - - // Setup observer which will trigger checkWidgets on: - // * keyup. - function setupWidgetsObserver( widgetsRepo ) { - var editor = widgetsRepo.editor, - buffer = CKEDITOR.tools.eventsBuffer( widgetsRepo.MIN_WIDGETS_CHECK_INTERVAL, function() { - widgetsRepo.fire( 'checkWidgets' ); - } ), - ignoredKeys = { 16:1,17:1,18:1,37:1,38:1,39:1,40:1,225:1 }; // SHIFT,CTRL,ALT,LEFT,UP,RIGHT,DOWN,RIGHT ALT(FF) - - editor.on( 'contentDom', function() { - var editable = editor.editable(); - - // Schedule check on keyup, but not more often than once per MIN_CHECK_DELAY. - editable.attachListener( editable.isInline() ? editable : editor.document, 'keyup', function( evt ) { - if ( !( evt.data.getKey() in ignoredKeys ) ) - buffer.input(); - }, null, null, 999 ); - } ); - - editor.on( 'contentDomUnload', buffer.reset ); - - widgetsRepo.on( 'checkWidgets', widgetsRepo.checkWidgets, widgetsRepo ); - } - - // 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; - - 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() ) - widget.setSelected( false ); - } - - if ( focusedChanged && focused ) { - widgetsRepo.focused = focused; - widgetsRepo.fire( 'widgetFocused', { widget: focused } ); - focused.setFocused( true ); - } - - while ( ( widget = toBeSelected.pop() ) ) { - currentlySelected.push( widget ); - widget.setSelected( true ); - } - - widgetsRepo.editor.fire( 'unlockSnapshot' ); - } - }; - } - - - // - // WIDGET helpers --------------------------------------------------------- - // - - var transparentImageData = 'data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw%3D%3D', - // LEFT, RIGHT, UP, DOWN, DEL, BACKSPACE - unblock default fake sel handlers. - keystrokesNotBlockedByWidget = { 37:1,38:1,39:1,40:1,8:1,46:1 }; - - function cancel( evt ) { - evt.cancel(); - } - - function copySingleWidget( widget, isCut ) { - var editor = widget.editor, - copybin = new CKEDITOR.dom.element( 'div', editor.document ); - - copybin.setAttributes( { - id: 'cke_copybin' - } ); - - copybin.setHtml( '<span data-cke-copybin-start="1">\u200b</span>' + widget.wrapper.getOuterHtml() + '<span data-cke-copybin-end="1">\u200b</span>' ); - - // Save snapshot with the current state. - editor.fire( 'saveSnapshot' ); - - // Ignore copybin. - editor.fire( 'lockSnapshot' ); - - editor.editable().append( copybin ); - - var listener1 = editor.on( 'selectionChange', cancel, null, null, 0 ), - listener2 = widget.repository.on( 'checkSelection', cancel, null, null, 0 ); - - // 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(); - - setTimeout( function() { - copybin.remove(); - - if ( !isCut ) - widget.focus(); - - listener1.removeListener(); - listener2.removeListener(); - - editor.fire( 'unlockSnapshot' ); - - if ( isCut ) { - widget.repository.del( widget ); - editor.fire( 'saveSnapshot' ); - } - }, 0 ); - } - - // [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 ); - } - } - - // Position drag handler according to the widget's element position. - function positionDragHandler( widget ) { - var handler = widget.dragHandlerContainer; - - handler.setStyle( 'top', widget.element.$.offsetTop - DRAG_HANDLER_SIZE + 'px' ); - handler.setStyle( 'left', widget.element.$.offsetLeft + 'px' ); - } - - function setupDragHandler( widget ) { - var editor = widget.editor, - img = new CKEDITOR.dom.element( 'img', editor.document ), - container = new CKEDITOR.dom.element( 'span', editor.document ); - - container.setAttributes( { - 'class': '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.setAttributes( { - draggable: 'true', - 'class': 'cke_widget_drag_handler', - 'data-cke-widget-drag-handler': '1', - src: transparentImageData, - width: DRAG_HANDLER_SIZE, - height: DRAG_HANDLER_SIZE - } ); - - img.on( 'dragstart', function( evt ) { - evt.data.$.dataTransfer.setData( 'text', JSON.stringify( { type: 'cke-widget', editor: editor.name, id: widget.id } ) ); - } ); - - container.append( img ); - widget.wrapper.append( container ); - widget.dragHandlerContainer = container; - } - - 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; - - var img = new CKEDITOR.dom.element( 'img', widget.editor.document ); - img.setAttributes( { - src: transparentImageData, - 'class': '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 ); - - 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. - } - // Pass chosen keystrokes to other plugins or default fake sel handlers. - // Pass all CTRL keystrokes. - else if ( keyCode in keystrokesNotBlockedByWidget || ( CKEDITOR.CTRL & keyCode ) ) - return; - - return false; - }, null, null, 999 ); - // Listen with high priority so it's possible - // to overwrite this callback. - - widget.on( 'doubleclick', function( evt ) { - widget.edit(); - evt.cancel(); - } ); - - if ( widgetDef.data ) - widget.on( 'data', widgetDef.data ); - - if ( widgetDef.edit ) - widget.on( 'edit', widgetDef.edit ); - - widget.on( 'data', function() { - positionDragHandler( widget ); - }, null, null, 999 ); - } - - function setupWidgetData( widget, startupData ) { - var widgetDataAttr = widget.element.data( 'cke-widget-data' ); - - if ( widgetDataAttr ) - widget.setData( JSON.parse( widgetDataAttr ) ); - if ( startupData ) - widget.setData( startupData ); - - // 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', JSON.stringify( widget.data ) ); - } - - // - // EXPOSE PUBLIC API ------------------------------------------------------ - // - - CKEDITOR.plugins.widget = Widget; - Widget.repository = Repository; - Widget.nestedEditable = NestedEditable; -})(); - -/** - * Event fired when widget is ready (fully initialized). This event is fired after: - * - * * {@link #init} is called, - * * first {@link #data} event is fired, - * * widget is attached to document. - * - * Therefore, in case of widget creation with command which opens dialog, this event - * will be delayed after dialog is closed and widget is finally inserted into document. - * - * **Note**: if your widget does not use automatic dialog binding (i.e. you open the dialog manually) - * or other situation occurs in which widget wrapper is not attached to document at the time when it is - * initialized, you need to take care of firing {@link #ready} yourself. - * - * @event ready - * @member CKEDITOR.plugins.widget - */ - -/** - * Event fired when widget is about to be destroyed, but before it is - * fully torn down. - * - * @event destroy - * @member CKEDITOR.plugins.widget - */ - -/** - * Event fired when widget is focused. - * - * Widget can be focused by executing {@link #method-focus}. - * - * @event focus - * @member CKEDITOR.plugins.widget - */ - -/** - * Event fired when widget is blurred. - * - * @event blur - * @member CKEDITOR.plugins.widget - */ - -/** - * Event fired when widget is selected. - * - * @event select - * @member CKEDITOR.plugins.widget - */ - -/** - * Event fired when widget is deselected. - * - * @event deselect - * @member CKEDITOR.plugins.widget - */ - -/** - * Event fired by {@link #method-edit}. It can be cancelled - * in order to stop default action (opening dialog). - * - * @event edit - * @member CKEDITOR.plugins.widget - * @param data - * @param {String} data.dialog Defaults to {@link CKEDITOR.plugins.widget.definition#dialog} - * and can be changed or set by listener. - */ - -/** - * Event fired when dialog for widget editing is opened. - * This event can be cancelled in order to handle editing dialog - * in a custom manner. - * - * @event dialog - * @member CKEDITOR.plugins.widget - * @param {CKEDITOR.dialog} data The opened dialog instance. - */ - -/** - * Event fired when key is pressed on focused widget. - * This event is forwarded from {@link CKEDITOR.editor#key} event and - * has the ability to block editor's keystrokes. - * - * @event key - * @member CKEDITOR.plugins.widget - * @param data - * @param {Number} data.keyCode A number representing the key code (or combination). - */ - - /** - * Event fired when widget was double clicked. - * - * @event doubleclick - * @member CKEDITOR.plugins.widget - * @param data - * @param {CKEDITOR.dom.element} data.element The double clicked element. - */ - - /** - * Event fired when context menu is opened for a widget. - * - * @event contextMenu - * @member CKEDITOR.plugins.widget - * @param data The object contaning context menu options to be added - * for this widget. See {@link CKEDITOR.plugins.contextMenu#addListener}. - */ - -/** - * Event fired when widget instance is created, but before it is fully - * initialized. - * - * @event instanceCreated - * @member CKEDITOR.plugins.widget.repository - * @param {CKEDITOR.plugins.widget} data The widget instance. - */ - -/** - * Event fired when widget instance was destroyed. - * - * See also {@link CKEDITOR.plugins.widget#event-destroy}. - * - * @event instanceDestroyed - * @member CKEDITOR.plugins.widget.repository - * @param {CKEDITOR.plugins.widget} data The widget instance. - */ - -/** - * Event fired to trigger selection check. - * - * See {@link #method-checkSelection} method. - * - * @event checkSelection - * @member CKEDITOR.plugins.widget.repository - */ - -/** - * Event fired to trigger widgets check. - * - * See {@link #method-checkWidgets} method. - * - * @event checkWidgets - * @member CKEDITOR.plugins.widget.repository - */
\ No newline at end of file |
