| Current File : /home/jvzmxxx/wiki/extensions/Flow/modules/flow/ui/widgets/editor/mw.flow.ui.EditorWidget.js |
( function ( $ ) {
/**
* Flow editor widget
*
* @class
* @extends OO.ui.Widget
*
* @constructor
* @param {Object} [config] Configuration options
* @cfg {string} [editor] The initial editor to load. Defaults to the editor
* set in the user's preferences.
* @cfg {string} [content] An initial content for the textarea
* @cfg {string} [contentFormat] Format of config.content
* @cfg {string} [placeholder] Placeholder text to use for the editor when empty
* @cfg {string} [termsMsgKey='flow-terms-of-use-edit'] i18n message key for the footer message
* @cfg {string} [saveMsgKey='flow-newtopic-save'] i18n message key for the save button
* @cfg {string} [cancelMsgKey='flow-cancel'] i18n message key for the cancel button
* @cfg {boolean} [autoFocus=true] Automatically focus after switching editors
* @cfg {boolean} [cancelOnEscape=true] Emit 'cancel' when Esc is pressed
* @cfg {boolean} [confirmCancel=true] Pop up a confirmation dialog if the user attempts
* to cancel when there are changes in the editor.
* @cfg {boolean} [confirmLeave=true] Pop up a confirmation dialog if the user attempts
* to navigate away when there are changes in the editor.
* @cfg {Function} [leaveCallback] Function to call when the user attempts to navigate away.
* If this function returns false, a confirmation dialog will be popped up.
* @cfg {boolean} [saveable=true] Initial state of whether editor is saveable
*/
mw.flow.ui.EditorWidget = function mwFlowUiEditorWidget( config ) {
var widget = this;
config = config || {};
// Parent constructor
mw.flow.ui.EditorWidget.parent.call( this, config );
// Mixin constructors
OO.ui.mixin.PendingElement.call( this, config );
this.initialEditor = config.editor;
this.confirmCancel = !!config.confirmCancel || config.cancelOnEscape === undefined;
this.confirmLeave = !!config.confirmLeave || config.confirmLeave === undefined;
this.leaveCallback = config.leaveCallback;
this.saveable = config.saveable !== undefined ? config.saveable : true;
this.editorControlsWidget = new mw.flow.ui.EditorControlsWidget( {
termsMsgKey: config.termsMsgKey || 'flow-terms-of-use-edit',
saveMsgKey: config.saveMsgKey || 'flow-newtopic-save',
cancelMsgKey: config.cancelMsgKey || 'flow-cancel',
saveable: this.saveable
} );
this.editorSwitcherWidget = new mw.flow.ui.EditorSwitcherWidget( {
autoFocus: config.autoFocus,
content: config.content,
contentFormat: config.contentFormat,
placeholder: config.placeholder,
saveable: this.saveable
} );
this.setPendingElement( this.editorSwitcherWidget.$element );
// Events
this.editorSwitcherWidget.connect( this, {
'switch': 'onEditorSwitcherSwitch',
change: [ 'emit', 'change' ]
} );
this.editorControlsWidget.connect( this, {
cancel: 'onEditorControlsWidgetCancel',
save: 'onEditorControlsWidgetSave'
} );
if ( config.cancelOnEscape || config.cancelOnEscape === undefined ) {
this.$element.on( 'keydown', function ( e ) {
if ( e.which === OO.ui.Keys.ESCAPE ) {
widget.onEditorControlsWidgetCancel();
e.preventDefault();
e.stopPropagation();
}
} );
}
this.$element
.append(
this.editorSwitcherWidget.$element,
this.editorControlsWidget.$element
)
.addClass( 'flow-ui-editorWidget' );
};
/* Events */
/**
* @event saveContent
* @param {string} content Content to save
* @param {string} contentFormat Format of content
*/
/**
* @event cancel
* The user clicked the cancel button.
*/
/**
* @event change
* The contents of the editor changed.
*/
/**
* @event switch
*
* A `switch` event is emitted when the widget switches between different editors.
*
* @param {jQuery.Promise} switched Promise that is resolved when the switch is complete,
* or rejected if the switch was aborted with an error.
* @param {string} newEditorName Name of the editor the widget is switching to
* @param {mw.flow.ui.AbstractEditorWidget|null} oldEditor Editor instance we're switching away from
*/
/* Initialization */
OO.inheritClass( mw.flow.ui.EditorWidget, OO.ui.Widget );
OO.mixinClass( mw.flow.ui.EditorWidget, OO.ui.mixin.PendingElement );
// TODO figure out a way not to have to wrap so many things in EditorSwitcherWidget; maybe
// merge EditorSwitcherWidget into EditorWidget?
/**
* Respond to cancel event. Verify with the user that they want to cancel if
* there is changed data in the editor.
* @fires cancel
*/
mw.flow.ui.EditorWidget.prototype.onEditorControlsWidgetCancel = function () {
var widget = this;
if ( this.confirmCancel && this.editorSwitcherWidget.hasBeenChanged() ) {
mw.flow.ui.windowManager.openWindow( 'cancelconfirm' )
.then( function ( opened ) {
opened.then( function ( closing, data ) {
if ( data && data.action === 'discard' ) {
// Remove content
widget.setContent( '', 'wikitext' );
widget.unbindBeforeUnloadHandler();
widget.emit( 'cancel' );
}
} );
} );
} else {
this.unbindBeforeUnloadHandler();
this.emit( 'cancel' );
}
};
/**
* Check whether an editor is currently active. This returns false before the first editor
* is loaded, and true after that. It also returns true if and editor switch is in progress.
* @return {boolean} Editor is active
*/
mw.flow.ui.EditorWidget.prototype.isActive = function () {
return this.editorSwitcherWidget.isActive();
};
/**
* Get the content of the editor.
* @return {string|null} Content of the editor, or null if no editor is active.
*/
mw.flow.ui.EditorWidget.prototype.getContent = function () {
return this.editorSwitcherWidget.getContent();
};
/**
* Get the format of the content returned by #getContent.
* @return {string|null} Content format, or null if no editor is active.
*/
mw.flow.ui.EditorWidget.prototype.getContentFormat = function () {
return this.editorSwitcherWidget.getContentFormat();
};
/**
* Check whether the editor is empty. If no editor is active, that is also considered empty.
* @return {boolean} Editor is empty
*/
mw.flow.ui.EditorWidget.prototype.isEmpty = function () {
return this.editorSwitcherWidget.isEmpty();
};
/**
* Get the name of the editor that would be loaded if this widget were to be
* activated right now.
*
* Note that the return value of this function can change over time as the user switches
* editors in different EditorWidgets. The editor that is actually loaded when activating
* is determined by calling this function at activation time, no earlier.
*
* @return {string} Name of initial editor that will be used
*/
mw.flow.ui.EditorWidget.prototype.getInitialEditorName = function () {
return this.initialEditor || mw.user.options.get( 'flow-editor' );
};
/**
* Get the format of the editor that would be loaded if this widget were to be
* activated right now.
* @return {string|null} Format used by initial editor, or null if no editor is active
* @see #getInitialEditorName
*/
mw.flow.ui.EditorWidget.prototype.getInitialFormat = function () {
return this.editorSwitcherWidget.getEditorFormat( this.getInitialEditorName() );
};
/**
* Toggle whether the editor is automatically focused after switching.
* @param {boolean} [autoFocus] Whether to focus automatically; if unset, flips current value
*/
mw.flow.ui.EditorWidget.prototype.toggleAutoFocus = function ( autoFocus ) {
this.editorSwitcherWidget.toggleAutoFocus( autoFocus );
};
/**
* Change the content in the editor
* @param {string} content New content to set
* @param {string} contentFormat Format of new content
* @return {jQuery.Promise} Promise resolved when new content has been set
* @see mw.flow.ui.EditorSwitcherWidget#setContent
*/
mw.flow.ui.EditorWidget.prototype.setContent = function ( content, contentFormat ) {
return this.editorSwitcherWidget.setContent( content, contentFormat );
};
/**
* Make this widget pending while switching editors.
* @private
* @param {jQuery.Promise} promise Promise resolved/rejected when switch is completed/aborted
* @fires switch
*/
mw.flow.ui.EditorWidget.prototype.onEditorSwitcherSwitch = function ( promise ) {
this.pushPending();
promise.always( this.popPending.bind( this ) );
this.emit.apply( this, [ 'switch' ].concat( Array.prototype.slice.apply( arguments ) ) );
};
/**
* Relay save event
* @private
* @fires saveContent
*/
mw.flow.ui.EditorWidget.prototype.onEditorControlsWidgetSave = function () {
this.unbindBeforeUnloadHandler();
this.emit(
'saveContent',
this.editorSwitcherWidget.getActiveEditor().getContent(),
this.editorSwitcherWidget.getActiveEditor().getFormat()
);
};
/**
* Bind the beforeunload handler, if needed and if not already bound.
*/
mw.flow.ui.EditorWidget.prototype.bindBeforeUnloadHandler = function () {
if ( !this.beforeUnloadHandler && ( this.confirmLeave || this.leaveCallback ) ) {
this.beforeUnloadHandler = this.onBeforeUnload.bind( this );
$( window ).on( 'beforeunload', this.beforeUnloadHandler );
}
};
/**
* Unbind the beforeunload handler if it is bound.
*/
mw.flow.ui.EditorWidget.prototype.unbindBeforeUnloadHandler = function () {
if ( this.beforeUnloadHandler ) {
$( window ).off( 'beforeunload', this.beforeUnloadHandler );
this.beforeUnloadHandler = null;
}
};
/**
* Respond to beforeunload event.
*
* @private
* @return {string|undefined}
*/
mw.flow.ui.EditorWidget.prototype.onBeforeUnload = function () {
if ( this.leaveCallback && this.leaveCallback() === false ) {
return mw.msg( 'flow-cancel-warning' );
}
if ( this.confirmLeave && !this.isEmpty() ) {
return mw.msg( 'flow-cancel-warning' );
}
};
/**
* Activate the first editor, if not already active.
* @return {jQuery.Promise} Promise resolved when editor switch is done
*/
mw.flow.ui.EditorWidget.prototype.activate = function () {
if ( this.isActive() ) {
this.bindBeforeUnloadHandler();
return $.Deferred().resolve().promise();
}
// Doesn't call editorSwitcherWidget.activate() because we want to
// evaluate the user preference as late as possible
var switchPromise,
editor = this.initialEditor || mw.user.options.get( 'flow-editor' ),
widget = this;
if ( editor === 'none' ) {
editor = 'wikitext';
}
if ( this.editorSwitcherWidget.isEditorAvailable( editor ) ) {
switchPromise = this.editorSwitcherWidget.switchEditor( editor );
} else {
// If the editor we want isn't available, let EditorSwitcherWidget decide which editor to use
switchPromise = this.editorSwitcherWidget.activate();
}
return switchPromise.then( function () {
widget.bindBeforeUnloadHandler();
} );
};
mw.flow.ui.EditorWidget.prototype.isDisabled = function () {
// Auto-disable when pending
return this.isPending() ||
// Parent method
mw.flow.ui.EditorWidget.parent.prototype.isDisabled.apply( this, arguments );
};
mw.flow.ui.EditorWidget.prototype.setDisabled = function ( disabled ) {
// Parent method
mw.flow.ui.EditorWidget.parent.prototype.setDisabled.call( this, disabled );
if ( this.editorSwitcherWidget && this.editorControlsWidget ) {
this.editorSwitcherWidget.setDisabled( this.isDisabled() );
this.editorControlsWidget.setDisabled( this.isDisabled() );
}
};
/**
* Toggle whether the editor is saveable
*
* This is different from setDisabled because that will also disable the cancel button.
* We want to allow them to click 'cancel' so they can collapse the editor again after
* seeing that editing is disabled.
*
* @param {boolean} [saveable] Whether the editor is saveable
*/
mw.flow.ui.EditorWidget.prototype.toggleSaveable = function ( saveable ) {
this.saveable = saveable === undefined ? !this.saveable : !!saveable;
this.editorSwitcherWidget.toggleSaveable( this.saveable );
this.editorControlsWidget.toggleSaveable( this.saveable );
};
mw.flow.ui.EditorWidget.prototype.pushPending = function () {
// Parent method
OO.ui.mixin.PendingElement.prototype.pushPending.apply( this, arguments );
// Disabled state depends on pending state
this.updateDisabled();
};
mw.flow.ui.EditorWidget.prototype.popPending = function () {
// Parent method
OO.ui.mixin.PendingElement.prototype.popPending.apply( this, arguments );
// Disabled state depends on pending state
this.updateDisabled();
};
/**
* Focus the current editor
*/
mw.flow.ui.EditorWidget.prototype.focus = function () {
this.editorSwitcherWidget.focus();
};
/**
* Destroy the widget.
*/
mw.flow.ui.EditorWidget.prototype.destroy = function () {
this.editorSwitcherWidget.destroy();
};
}( jQuery ) );