Current File : /home/jvzmxxx/wiki/extensions/Flow/modules/editor/ext.flow.editor.js
( function ( $, mw ) {
	'use strict';

	/**
	 * This is more of an EditorFacade/EditorDispatcher or something, and should be renamed.
	 * It's not the base class, nor is it an actual editor.
	 * @class
	 */
	mw.flow.editor = {
		/**
		 * Specific editor to be used.
		 *
		 * @property {Object}
		 */
		editor: null,

		/**
		 * Array of target instances.
		 *
		 * The first entry is null to make sure that the reference saved to a data-
		 * attribute is never index 0; a 0-value will make :data(flow-editor)
		 * selector not find a result.
		 *
		 * @property {Object}
		 */
		editors: [ null ],

		init: function () {
			var editorList = mw.config.get( 'wgFlowEditorList' ),
				index = editorList.indexOf( 'none' );

			// determine editor instance to use, depending on availability
			mw.flow.editor.loadEditor( index );
		},

		loadEditor: function ( editorIndex ) {
			var editorList = mw.config.get( 'wgFlowEditorList' ),
				editor;

			if ( !editorIndex || editorIndex < 0 || editorIndex >= editorList.length ) {
				editorIndex = 0;
			}

			if ( editorList[ editorIndex ] ) {
				editor = editorList[ editorIndex ];
			} else {
				editor = 'none';
			}
			if ( editor === 'wikitext' ) {
				editor = 'none';
			}

			mw.loader.using( 'ext.flow.editors.' + editor, function () {
				// Some editors only work under certain circumstances
				if ( !mw.flow.editors[ editor ].static.isSupported() ) {
					mw.flow.editor.loadEditor( editorIndex + 1 );
				} else {
					mw.flow.editor.editor = mw.flow.editors[ editor ];
				}
			} );
		},

		/**
		 * @param {jQuery} $node
		 * @param {string} [content] Existing content to load, in any format
		 * @return {jQuery.Promise} Will resolve once editor instance is loaded
		 */
		load: function ( $node, content ) {
			/**
			 * When calling load(), loadEditor() may not yet have completed loading the
			 * dependencies. To make sure it doesn't break, this will in interval,
			 * check for it and only start loading once initialization is complete.
			 *
			 * @private
			 */
			var tryLoad = function ( $node, content ) {
				if ( mw.flow.editor.editor === null ) {
					return;
				} else {
					clearInterval( interval );
				}

				// doublecheck if editor doesn't already exist for this node
				if ( !mw.flow.editor.getEditor( $node ) ) {
					mw.flow.editor.create( $node, content );
				}

				deferred.resolve();
			},
			deferred = $.Deferred(),
			interval = setInterval( $.proxy( tryLoad, this, $node, content ), 10 );

			return deferred.promise();
		},

		/**
		 * @param {jQuery} $node
		 */
		destroy: function ( $node ) {
			var editor = mw.flow.editor.getEditor( $node );

			if ( editor ) {
				editor.destroy();

				// destroy reference
				mw.flow.editor.editors[ $.inArray( editor, mw.flow.editor.editors ) ] = null;
				$node
					.removeData( 'flow-editor' )
					// instead of .show(), we just want to remove the .hide() we've applied since
					// there may be other CSS rules still hiding this node
					.css( 'display', '' )
					.closest( '.flow-editor' ).removeClass( 'flow-editor-' + editor.constructor.static.name );
			}
		},

		/**
		 * Get the editor's text format.
		 *
		 * @param {jQuery} [$node]
		 * @return {string}
		 */
		getFormat: function ( $node ) {
			var editor;

			if ( $node ) {
				editor = mw.flow.editor.getEditor( $node );
			}

			if ( editor ) {
				return editor.constructor.static.format;
			} else {
				return mw.flow.editor.editor.static.format;
			}
		},

		/**
		 * Get the raw, unconverted, content, in the current editor's format.
		 *
		 * @param {jQuery} $node
		 * @return {string}
		 */
		getRawContent: function ( $node ) {
			var editor = mw.flow.editor.getEditor( $node );
			return editor.getRawContent() || '';
		},

		/**
		 * Initialize an editor object with given content & tie it to the given node.
		 *
		 * @param {jQuery} $node
		 * @param {string} content
		 * @return {Object}
		 */
		create: function ( $node, content ) {
			$node.data( 'flow-editor', mw.flow.editor.editors.length )
				.closest( '.flow-editor' ).addClass( 'flow-editor-' + mw.flow.editor.editor.static.name );

			mw.flow.editor.editors.push( new mw.flow.editor.editor( $node, content ) );
			return mw.flow.editor.getEditor( $node );
		},

		/**
		 * Returns editor object associated with a given node.
		 *
		 * @param {jQuery} $node
		 * @return {Object}
		 */
		getEditor: function ( $node ) {
			return mw.flow.editor.editors[ $node.data( 'flow-editor' ) ];
		},

		/**
		 * Returns true if the given $node has an associated editor instance.
		 *
		 * @param {jQuery} $node
		 * @return {boolean}
		 */
		exists: function ( $node ) {
			return mw.flow.editor.editors.hasOwnProperty( $node.data( 'flow-editor' ) );
		},

		/**
		 * Changes the default editor to desiredEditor and converts $node to that
		 * type of editor.
		 *
		 * @todo Should support $node containing multiple editing nodes, such
		 *  as selecting all active editors in the page and switching all of
		 *  them to the desiredEditor. Currently you will need to $node.each()
		 *  and call switchEditor for each iteration.
		 *
		 * @param {jQuery} $node
		 * @param {string} desiredEditor
		 * @return {jQuery.Promise} Will resolve once editor instance is loaded
		 */
		switchEditor: function ( $node, desiredEditor ) {
			var editorList = mw.config.get( 'wgFlowEditorList' ),
				editor = mw.flow.editor.getEditor( $node );

			if ( !editor ) {
				// $node is not an editor
				return $.Deferred().reject( 'not-an-editor' ).promise();
			} else if ( editorList.indexOf( desiredEditor ) === -1 ) {
				// desiredEditor does not exist
				return $.Deferred().reject( 'unknown-editor-type' ).promise();
			}

			function markPending( pending ) {
				// Make editor disabled and pending while switching
				// HACK: when these things are OOUI widgets we'll be able to use real OOUI facilities for this
				$node.prop( 'disabled', pending );
				$node.closest( '.flow-editor' ).toggleClass( 'oo-ui-texture-pending', pending );
			}

			function updateEditorPreference() {
				if ( mw.user.options.get( 'flow-editor' ) !== desiredEditor ) {
					new mw.Api().saveOption( 'flow-editor', desiredEditor );
					// ensure we also see that preference in the current page
					mw.user.options.set( 'flow-editor', desiredEditor );
				}
			}

			markPending( true );

			return mw.loader.using( 'ext.flow.editors.' + desiredEditor )

				.then( function () {
					if ( !mw.flow.editors[ desiredEditor ].static.isSupported() ) {
						return $.Deferred().reject( 'editor-not-supported' );
					}

					var content = editor.getRawContent(),
						oldFormat = editor.constructor.static.format,
						newFormat;

					mw.flow.editor.editor = mw.flow.editors[ desiredEditor ];
					newFormat = mw.flow.editor.editor.static.format;

					// prepare data to feed into conversion
					return {
						from: oldFormat,
						to: newFormat,
						content: content
					};
				} )

				// convert content to new editor format
				.then( function ( data ) {
					// no need to fire API request if there's no content, or if
					// content format doesn't change for new editor
					if ( data.from === data.to || data.content === '' ) {
						return $.Deferred().resolve( {
							'flow-parsoid-utils': {
								format: data.to,
								content: data.content
							}
						} );
					}

					return new mw.Api().post( {
						action: 'flow-parsoid-utils',
						from: data.from,
						to: data.to,
						content: data.content,
						title: mw.config.get( 'wgPageName' )
					} );
				} )
				.then( null, function () {
					// Map Parsoid failure to 'failed-parsoid-convert'
					return 'failed-parsoid-convert';
				} )

				// load new editor with converted data
				.then( function ( data ) {
					// Stop listening for changes on old editor
					mw.flow.editor.getEditor( $node ).off( 'change', updateEditorPreference );
					// Destroy old editor
					mw.flow.editor.destroy( $node );
					// Load new editor
					return mw.flow.editor.load( $node, data[ 'flow-parsoid-utils' ].content );
				} )

				// Unmark pending, store editor preference
				.then( function () {
					markPending( false );

					// If the user actually makes a change, set their editor preference to this editor
					if ( !mw.user.isAnon() ) {
						// Can't use .once() because that doesn't work well with .off()
						mw.flow.editor.getEditor( $node ).on( 'change', updateEditorPreference );
					}
				} )

				// anything that results in a reject() will be logged
				.fail( function ( rejectionCode ) {
					mw.flow.debug( '[switchEditor] Could not switch to ' + desiredEditor + ' : ' + rejectionCode );
					markPending( false );
				} );
		},

		focus: function ( $node ) {
			var editor = mw.flow.editor.getEditor( $node );

			if ( editor && editor.focus ) {
				editor.focus();
			} else {
				$node.focus();
			}
		},

		moveCursorToEnd: function ( $node ) {
			var editor = mw.flow.editor.getEditor( $node );

			if ( editor && editor.moveCursorToEnd ) {
				return editor.moveCursorToEnd();
			} else {
				$node.selectRange( $node.val().length );
			}
		}
	};
	$( mw.flow.editor.init );
}( jQuery, mediaWiki ) );