Make the save dialog an actual dialog

Major changes:
* Create a MW specific save dialog class
* Widgetize save dialog elements
* Simplification of viewPageTarget

Minor changes:
* Added getWindow method to windowSet and setTitle methods to window class
* Add transition css properties to dialog styles

Bug: 48566
Bug: 50722
Bug: 51918
Bug: 52175
Bug: 53313
Change-Id: I8c0db01fb8477a9b3d3dfe2a6073ac67869ce40e
This commit is contained in:
Ed Sanders 2013-10-07 11:01:43 +01:00 committed by Rob Moen
parent 5a1a960b19
commit 972c9a46b7
10 changed files with 606 additions and 799 deletions

View file

@ -185,7 +185,7 @@ $messages['en'] = array(
'visualeditor-savedialog-label-save' => 'Save page', 'visualeditor-savedialog-label-save' => 'Save page',
'visualeditor-savedialog-label-warning' => 'Warning', 'visualeditor-savedialog-label-warning' => 'Warning',
'visualeditor-savedialog-title-conflict' => 'Conflict', 'visualeditor-savedialog-title-conflict' => 'Conflict',
'visualeditor-savedialog-title-nochanges' => 'No changes', 'visualeditor-savedialog-title-nochanges' => 'No changes to review',
'visualeditor-savedialog-title-review' => 'Review your changes', 'visualeditor-savedialog-title-review' => 'Review your changes',
'visualeditor-savedialog-title-save' => 'Save your changes', 'visualeditor-savedialog-title-save' => 'Save your changes',
'visualeditor-savedialog-warning-dirty' => 'Your edit may have been corrupted please review before saving.', 'visualeditor-savedialog-warning-dirty' => 'Your edit may have been corrupted please review before saving.',

View file

@ -502,6 +502,7 @@ $wgResourceModules += array(
've/ui/layouts/ve.ui.PanelLayout.js', 've/ui/layouts/ve.ui.PanelLayout.js',
've/ui/layouts/ve.ui.StackPanelLayout.js', 've/ui/layouts/ve.ui.StackPanelLayout.js',
've-mw/ui/dialogs/ve.ui.MWSaveDialog.js',
've-mw/ui/dialogs/ve.ui.MWMetaDialog.js', 've-mw/ui/dialogs/ve.ui.MWMetaDialog.js',
've-mw/ui/dialogs/ve.ui.MWBetaWelcomeDialog.js', 've-mw/ui/dialogs/ve.ui.MWBetaWelcomeDialog.js',
've-mw/ui/dialogs/ve.ui.MWMediaInsertDialog.js', 've-mw/ui/dialogs/ve.ui.MWMediaInsertDialog.js',
@ -559,6 +560,7 @@ $wgResourceModules += array(
'unicodejs.wordbreak', 'unicodejs.wordbreak',
'ext.visualEditor.base', 'ext.visualEditor.base',
'mediawiki.Title', 'mediawiki.Title',
'mediawiki.action.history.diff',
'jquery.autoEllipsis', 'jquery.autoEllipsis',
), ),
'messages' => array( 'messages' => array(

View file

@ -121,226 +121,10 @@
color: #555; color: #555;
} }
/* Save dialog styles */
.ve-init-mw-viewPageTarget-toolbarTracker {
position: absolute;
top: 0;
height: 0;
overflow: visible;
}
.ve-init-mw-viewPageTarget-toolbarTracker-floating {
position: fixed;
z-index: 100;
}
.ve-init-mw-viewPageTarget-saveDialog .ve-ui-pushButtonWidget {
float: right;
margin-left: 0.5em;
font-size: 0.8em;
}
.ve-init-mw-viewPageTarget-saveDialog-working {
display: none;
float: right;
height: 2em;
width: 128px;
margin-right: 1em;
background-position: right center;
background-repeat: no-repeat;
}
.ve-init-mw-viewPageTarget-saveDialog {
display: none;
top: 0.25em;
right: 0.5em;
width: 29em;
min-width: 29em;
font-family: sans-serif;
position: absolute;
border: solid 1px #ccc;
border-radius: 0.25em;
background-color: #fff;
box-shadow: 0 0.15em 0.5em 0 rgba(0, 0, 0, 0.2);
padding: 2.5em 0.75em 0.75em 0.75em;
margin: 0 0 0 0.5em;
z-index: 3;
/* slide-diff can get quite long, handle overflow */
/* max-height set from javascript */
overflow-x: auto;
}
.ve-init-mw-viewPageTarget-saveDialog-head {
position: absolute;
top: 0.4em;
left: 0.5em;
right: 0.5em;
}
.ve-init-mw-viewPageTarget-saveDialog-title {
height: 2em;
line-height: 2em;
color: #333;
font-size: 0.9em;
float: left;
margin: 0 0.5em;
}
.ve-init-mw-viewPageTarget-saveDialog-prevButton {
float: left;
position: relative;
top: 0.1em;
width: 1.5em;
height: 1.5em;
cursor: pointer;
opacity: 0.8;
/* @see ve.init.mw.Icons */
background-position: left top;
background-repeat: no-repeat;
padding-right: 0.5em;
border-right: 1px solid #eee;
margin-right: 0.5em;
}
.ve-init-mw-viewPageTarget-saveDialog-closeButton {
float: right;
position: relative;
top: 0.1em;
width: 1.5em;
height: 1.5em;
cursor: pointer;
opacity: 0.8;
/* @see ve.init.mw.Icons */
background-position: right top;
background-repeat: no-repeat;
}
.ve-init-mw-viewPageTarget-saveDialog-body {
border-top: 1px solid #ddd;
padding-top: 1em;
}
.ve-init-mw-viewPageTarget-saveDialog-slide {
display: none;
}
.ve-init-mw-viewPageTarget-saveDialog-slide-review .ve-init-mw-viewPageTarget-saveDialog-viewer {
margin-bottom: 1em;
}
.ve-init-mw-viewPageTarget-saveDialog-slide-review .ve-init-mw-viewPageTarget-saveDialog-viewer pre {
margin: 0;
}
.ve-init-mw-viewPageTarget-saveDialog-slide-review .ve-init-mw-viewPageTarget-saveDialog-viewer .diff {
font-size: 0.8em;
}
.ve-init-mw-viewPageTarget-saveDialog-foot {
padding-top: 1em;
}
.ve-init-mw-viewPageTarget-saveDialog-dirtymsg,
.ve-init-mw-viewPageTarget-saveDialog-license,
.ve-init-mw-viewPageTarget-saveDialog-report-notice {
font-size: 0.7em;
line-height: 1.25em;
padding: 0;
margin: 0;
color: #999;
}
.ve-init-mw-viewPageTarget-saveDialog-dirtymsg {
float: right;
}
.ve-init-mw-viewPageTarget-saveDialog-summary,
.ve-init-mw-viewPageTarget-saveDialog-report {
background-color: #fff;
border: solid 1px #cccccc;
padding: 0.5em;
border-radius: 0.25em 0.25em 0 0;
}
.ve-init-mw-viewPageTarget-saveDialog-report {
margin-bottom: 1em;
border-radius: 0.25em;
}
.ve-init-mw-viewPageTarget-saveDialog-summary-focused,
.ve-init-mw-viewPageTarget-saveDialog-report-focused {
border-color: #aaa;
}
.ve-init-mw-viewPageTarget-saveDialog-conflict {
margin-bottom: 1em;
}
.ve-init-mw-viewPageTarget-saveDialog-messages,
.ve-init-mw-viewPageTarget-saveDialog-conflict,
.ve-init-mw-viewPageTarget-saveDialog-nochanges {
font-size: 0.8em;
}
.ve-init-mw-viewPageTarget-saveDialog-options {
position: relative;
background-color: #f7f7f7;
margin-bottom: 1em;
border: solid 1px #cccccc;
border-top: none;
border-radius: 0 0 0.25em 0.25em;
min-height: 2.25em;
}
.ve-init-mw-viewPageTarget-saveDialog-body label {
font-size: 0.8em;
line-height: 3em;
}
.ve-init-mw-viewPageTarget-saveDialog input[type="checkbox"] {
margin: 0 0.5em 0 1em;
line-height: 3em;
}
.ve-init-mw-viewPageTarget-saveDialog-body .ve-init-mw-viewPageTarget-saveDialog-editSummary-label {
line-height: 2em;
}
.ve-init-mw-viewPageTarget-saveDialog-editSummaryCount {
position: absolute;
right: 0;
top: 0;
bottom: 0;
border-left: solid 1px #eee;
line-height: 3em;
padding: 0 1em;
color: #aaa;
}
.ve-init-mw-viewPageTarget-saveDialog-editSummary,
.ve-init-mw-viewPageTarget-saveDialog-problem {
border: none;
background-color: transparent;
margin: 0;
padding: 0;
resize: none;
font-size: 0.8em;
font-family: sans-serif;
height: 5em;
}
.ve-init-mw-viewPageTarget-saveDialog-editSummary:focus,
.ve-init-mw-viewPageTarget-saveDialog-problem:focus {
outline: none;
}
/* Images */ /* Images */
.ve-init-mw-viewPageTarget-loading, .ve-init-mw-viewPageTarget-loading,
.ve-init-mw-viewPageTarget-saveDialog-working { .ve-ui-mwSaveDialog-working {
/* @embed */ /* @embed */
background-image: url(images/loading-ltr.gif); background-image: url(images/loading-ltr.gif);
} }

View file

@ -34,9 +34,7 @@ ve.init.mw.ViewPageTarget = function VeInitMwViewPageTarget() {
this.toolbarOffset = null; this.toolbarOffset = null;
this.toolbarCancelButton = null; this.toolbarCancelButton = null;
this.toolbarSaveButton = null; this.toolbarSaveButton = null;
this.saveDialogSlideHistory = []; this.saveDialog = null;
this.saveDialogSaveButton = null;
this.saveDialogReviewGoodButton = null;
this.toolbarEditNoticesButton = null; this.toolbarEditNoticesButton = null;
this.toolbarEditNotices = null; this.toolbarEditNotices = null;
this.toolbarBetaNoticesButton = null; this.toolbarBetaNoticesButton = null;
@ -62,7 +60,6 @@ ve.init.mw.ViewPageTarget = function VeInitMwViewPageTarget() {
this.actFromPopState = false; this.actFromPopState = false;
this.scrollTop = null; this.scrollTop = null;
this.currentUri = currentUri; this.currentUri = currentUri;
this.messages = {};
this.section = currentUri.query.vesection || null; this.section = currentUri.query.vesection || null;
this.sectionPositionRestored = false; this.sectionPositionRestored = false;
this.sectionTitleRestored = false; this.sectionTitleRestored = false;
@ -153,59 +150,6 @@ ve.init.mw.ViewPageTarget.compatibility = {
} }
}; };
// TODO: Accessibility tooltips and logical tab order for prevButton and closeButton.
ve.init.mw.ViewPageTarget.saveDialogTemplate = '\
<div class="ve-init-mw-viewPageTarget-saveDialog-head">\
<div class="ve-init-mw-viewPageTarget-saveDialog-prevButton"></div>\
<div class="ve-init-mw-viewPageTarget-saveDialog-closeButton"></div>\
<div class="ve-init-mw-viewPageTarget-saveDialog-title"></div>\
</div>\
<div class="ve-init-mw-viewPageTarget-saveDialog-body">\
<div class="ve-init-mw-viewPageTarget-saveDialog-slide ve-init-mw-viewPageTarget-saveDialog-slide-save">\
<label class="ve-init-mw-viewPageTarget-saveDialog-editSummary-label"\
for="ve-init-mw-viewPageTarget-saveDialog-editSummary"\
id="ve-init-mw-viewPageTarget-saveDialog-editSummary-label"></label>\
<div class="ve-init-mw-viewPageTarget-saveDialog-summary">\
<textarea name="editSummary" class="ve-init-mw-viewPageTarget-saveDialog-editSummary"\
id="ve-init-mw-viewPageTarget-saveDialog-editSummary" type="text"\
rows="4"></textarea>\
</div>\
<div class="ve-init-mw-viewPageTarget-saveDialog-options">\
<div class="ve-init-mw-viewPageTarget-saveDialog-checkboxes">\
</div>\
<label class="ve-init-mw-viewPageTarget-saveDialog-editSummaryCount"></label>\
</div>\
<div class="ve-init-mw-viewPageTarget-saveDialog-messages"></div>\
<div class="ve-init-mw-viewPageTarget-saveDialog-actions">\
<div class="ve-init-mw-viewPageTarget-saveDialog-dirtymsg"></div>\
<div class="ve-init-mw-viewPageTarget-saveDialog-working"></div>\
</div>\
<div style="clear: both;"></div>\
<div class="ve-init-mw-viewPageTarget-saveDialog-foot">\
<p class="ve-init-mw-viewPageTarget-saveDialog-license"></p>\
</div>\
</div>\
<div class="ve-init-mw-viewPageTarget-saveDialog-slide ve-init-mw-viewPageTarget-saveDialog-slide-review">\
<div class="ve-init-mw-viewPageTarget-saveDialog-viewer"></div>\
<div class="ve-init-mw-viewPageTarget-saveDialog-actions">\
<div class="ve-init-mw-viewPageTarget-saveDialog-working"></div>\
</div>\
<div style="clear: both;"></div>\
</div>\
<div class="ve-init-mw-viewPageTarget-saveDialog-slide ve-init-mw-viewPageTarget-saveDialog-slide-conflict">\
<div class="ve-init-mw-viewPageTarget-saveDialog-conflict">\
</div>\
<div class="ve-init-mw-viewPageTarget-saveDialog-actions">\
<div class="ve-init-mw-viewPageTarget-saveDialog-working"></div>\
</div>\
<div style="clear: both;"></div>\
</div>\
<div class="ve-init-mw-viewPageTarget-saveDialog-slide ve-init-mw-viewPageTarget-saveDialog-slide-nochanges">\
<div class="ve-init-mw-viewPageTarget-saveDialog-nochanges">\
</div>\
</div>\
</div>';
/* Methods */ /* Methods */
/** /**
@ -256,10 +200,9 @@ ve.init.mw.ViewPageTarget.prototype.deactivate = function ( override ) {
this.detachToolbarButtons(); this.detachToolbarButtons();
} }
this.resetSaveDialog(); this.saveDialog.reset();
this.hideSaveDialog(); this.saveDialog.close();
this.detachSaveDialog(); // Check we got as far as setting up the surface
if ( this.active ) { if ( this.active ) {
// If we got as far as setting up the surface, tear that down // If we got as far as setting up the surface, tear that down
this.tearDownSurface(); this.tearDownSurface();
@ -298,7 +241,6 @@ ve.init.mw.ViewPageTarget.prototype.onLoad = function ( doc ) {
this.setupToolbarBetaNotices(); this.setupToolbarBetaNotices();
this.setupSaveDialog(); this.setupSaveDialog();
this.attachToolbarButtons(); this.attachToolbarButtons();
this.attachSaveDialog();
this.restoreScrollPosition(); this.restoreScrollPosition();
this.restoreEditSection(); this.restoreEditSection();
this.setupBeforeUnloadHandler(); this.setupBeforeUnloadHandler();
@ -394,9 +336,8 @@ ve.init.mw.ViewPageTarget.prototype.onSave = function ( html, newid ) {
mw.config.set( 'wgCurRevisionId', newid ); mw.config.set( 'wgCurRevisionId', newid );
this.revid = newid; this.revid = newid;
} }
this.saveDialog.close();
this.hideSaveDialog(); this.saveDialog.reset();
this.resetSaveDialog();
this.replacePageContent( html ); this.replacePageContent( html );
this.setupSectionEditLinks(); this.setupSectionEditLinks();
this.tearDownBeforeUnloadHandler(); this.tearDownBeforeUnloadHandler();
@ -422,21 +363,21 @@ ve.init.mw.ViewPageTarget.prototype.onSaveError = function ( jqXHR, status, data
var api, editApi, var api, editApi,
viewPage = this; viewPage = this;
this.saveDialogSaveButton.setDisabled( false ); this.saveDialog.saveButton.setDisabled( false );
this.$saveDialogLoadingIcon.hide(); this.saveDialog.$loadingIcon.hide();
this.clearMessage( 'api-save-error' ); this.saveDialog.clearMessage( 'api-save-error' );
// Handle empty response // Handle empty response
if ( !data ) { if ( !data ) {
this.showMessage( this.saveDialog.showMessage(
'api-save-error', 'api-save-error',
ve.msg( 'visualeditor-saveerror', 'Empty server response' ), ve.msg( 'visualeditor-saveerror', 'Empty server response' ),
{ {
wrap: 'error' wrap: 'error'
} }
); );
this.saveDialogSaveButton.setDisabled( true ); this.saveDialog.saveButton.setDisabled( true );
return; return;
} }
@ -444,7 +385,7 @@ ve.init.mw.ViewPageTarget.prototype.onSaveError = function ( jqXHR, status, data
// Handle spam blacklist error (either from core or from Extension:SpamBlacklist) // Handle spam blacklist error (either from core or from Extension:SpamBlacklist)
if ( editApi && editApi.spamblacklist ) { if ( editApi && editApi.spamblacklist ) {
this.showMessage( this.saveDialog.showMessage(
'api-save-error', 'api-save-error',
// TODO: Use mediawiki.language equivalant of Language.php::listToText once it exists // TODO: Use mediawiki.language equivalant of Language.php::listToText once it exists
ve.msg( 'spamprotectiontext' ) + ' ' + ve.msg( 'spamprotectionmatch', editApi.spamblacklist.split( '|' ).join( ', ' ) ), ve.msg( 'spamprotectiontext' ) + ' ' + ve.msg( 'spamprotectionmatch', editApi.spamblacklist.split( '|' ).join( ', ' ) ),
@ -452,14 +393,14 @@ ve.init.mw.ViewPageTarget.prototype.onSaveError = function ( jqXHR, status, data
wrap: 'error' wrap: 'error'
} }
); );
this.saveDialogSaveButton.setDisabled( true ); this.saveDialog.saveButton.setDisabled( true );
return; return;
} }
// Handle warnings/errors from Extension:AbuseFilter // Handle warnings/errors from Extension:AbuseFilter
// TODO: Move this to a plugin // TODO: Move this to a plugin
if ( editApi && editApi.info && editApi.info.indexOf( 'Hit AbuseFilter:' ) === 0 && editApi.warning ) { if ( editApi && editApi.info && editApi.info.indexOf( 'Hit AbuseFilter:' ) === 0 && editApi.warning ) {
this.showMessage( this.saveDialog.showMessage(
'api-save-error', 'api-save-error',
$.parseHTML( editApi.warning ), $.parseHTML( editApi.warning ),
{ wrap: false } { wrap: false }
@ -473,8 +414,8 @@ ve.init.mw.ViewPageTarget.prototype.onSaveError = function ( jqXHR, status, data
// Handle token errors // Handle token errors
if ( data.error && data.error.code === 'badtoken' ) { if ( data.error && data.error.code === 'badtoken' ) {
api = new mw.Api(); api = new mw.Api();
viewPage.saveDialogSaveButton.setDisabled( true ); viewPage.saveDialog.saveButton.setDisabled( true );
viewPage.$saveDialogLoadingIcon.show(); viewPage.saveDialog.$loadingIcon.show();
api.get( { api.get( {
// action=query&meta=userinfo and action=tokens&type=edit can't be combined // action=query&meta=userinfo and action=tokens&type=edit can't be combined
// but action=query&meta=userinfo and action=query&prop=info can, however // but action=query&meta=userinfo and action=query&prop=info can, however
@ -489,7 +430,7 @@ ve.init.mw.ViewPageTarget.prototype.onSaveError = function ( jqXHR, status, data
'intoken': 'edit' 'intoken': 'edit'
} ) } )
.always( function () { .always( function () {
viewPage.$saveDialogLoadingIcon.hide(); viewPage.saveDialog.$loadingIcon.hide();
} ) } )
.done( function ( data ) { .done( function ( data ) {
var badTokenText, userMsg, var badTokenText, userMsg,
@ -511,7 +452,7 @@ ve.init.mw.ViewPageTarget.prototype.onSaveError = function ( jqXHR, status, data
viewPage.saveDocument(); viewPage.saveDocument();
} else { } else {
// The now current session is a different user // The now current session is a different user
viewPage.saveDialogSaveButton.setDisabled( false ); viewPage.saveDialog.saveButton.setDisabled( false );
// Trailing space is to separate from the other message. // Trailing space is to separate from the other message.
badTokenText = document.createTextNode( mw.msg( 'visualeditor-savedialog-error-badtoken' ) + ' ' ); badTokenText = document.createTextNode( mw.msg( 'visualeditor-savedialog-error-badtoken' ) + ' ' );
@ -526,7 +467,7 @@ ve.init.mw.ViewPageTarget.prototype.onSaveError = function ( jqXHR, status, data
'wgUserName': null 'wgUserName': null
} ); } );
viewPage.showMessage( viewPage.saveDialog.showMessage(
'api-save-error', 'api-save-error',
$( badTokenText ).add( $( badTokenText ).add(
$.parseHTML( mw.message( 'visualeditor-savedialog-identify-anon' ).parse() ) $.parseHTML( mw.message( 'visualeditor-savedialog-identify-anon' ).parse() )
@ -545,7 +486,7 @@ ve.init.mw.ViewPageTarget.prototype.onSaveError = function ( jqXHR, status, data
.replace( /\$1/g, userInfo.name ) .replace( /\$1/g, userInfo.name )
); );
viewPage.showMessage( viewPage.saveDialog.showMessage(
'api-save-error', 'api-save-error',
$( badTokenText ).add( $( badTokenText ).add(
$.parseHTML( mw.message( userMsg ).parse() ) $.parseHTML( mw.message( userMsg ).parse() )
@ -573,9 +514,9 @@ ve.init.mw.ViewPageTarget.prototype.onSaveError = function ( jqXHR, status, data
input: new ve.ui.TextInputWidget(), input: new ve.ui.TextInputWidget(),
id: editApi.captcha.id id: editApi.captcha.id
}; };
this.showMessage( this.saveDialog.showMessage(
'api-save-error', 'api-save-error',
$( '<div>').append( $( '<div>' ).append(
// msg: simplecaptcha-edit, fancycaptcha-edit, .. // msg: simplecaptcha-edit, fancycaptcha-edit, ..
$( '<p>' ).append( $( '<p>' ).append(
$( '<strong>' ).text( mw.msg( 'captcha-label' ) ), $( '<strong>' ).text( mw.msg( 'captcha-label' ) ),
@ -594,7 +535,7 @@ ve.init.mw.ViewPageTarget.prototype.onSaveError = function ( jqXHR, status, data
} }
// Handle (other) unknown and/or unrecoverable errors // Handle (other) unknown and/or unrecoverable errors
this.showMessage( this.saveDialog.showMessage(
'api-save-error', 'api-save-error',
document.createTextNode( document.createTextNode(
( editApi && editApi.info ) || ( editApi && editApi.info ) ||
@ -607,7 +548,7 @@ ve.init.mw.ViewPageTarget.prototype.onSaveError = function ( jqXHR, status, data
wrap: 'error' wrap: 'error'
} }
); );
this.saveDialogSaveButton.setDisabled( true ); this.saveDialog.saveButton.setDisabled( true );
}; };
/** /**
@ -619,18 +560,7 @@ ve.init.mw.ViewPageTarget.prototype.onSaveError = function ( jqXHR, status, data
ve.init.mw.ViewPageTarget.prototype.onShowChanges = function ( diffHtml ) { ve.init.mw.ViewPageTarget.prototype.onShowChanges = function ( diffHtml ) {
// Invalidate the viewer diff on next change // Invalidate the viewer diff on next change
this.surface.getModel().connect( this, { 'transact': 'onSurfaceModelTransact' } ); this.surface.getModel().connect( this, { 'transact': 'onSurfaceModelTransact' } );
this.saveDialog.setDiffAndReview( diffHtml );
mw.loader.using( 'mediawiki.action.history.diff', ve.bind( function () {
this.$saveDialog
.find( '.ve-init-mw-viewPageTarget-saveDialog-viewer' )
.empty().append( diffHtml );
this.$saveDialogLoadingIcon.hide();
this.saveDialogReviewGoodButton.setDisabled( false );
}, this ), ve.bind( function () {
this.onSaveError( null, 'Module load failed' );
}, this ) );
}; };
/** /**
@ -642,13 +572,7 @@ ve.init.mw.ViewPageTarget.prototype.onShowChanges = function ( diffHtml ) {
ve.init.mw.ViewPageTarget.prototype.onSerialize = function ( wikitext ) { ve.init.mw.ViewPageTarget.prototype.onSerialize = function ( wikitext ) {
// Invalidate the viewer wikitext on next change // Invalidate the viewer wikitext on next change
this.surface.getModel().connect( this, { 'transact': 'onSurfaceModelTransact' } ); this.surface.getModel().connect( this, { 'transact': 'onSurfaceModelTransact' } );
this.saveDialog.setDiffAndReview( $( '<pre>' ).text( wikitext ) );
this.$saveDialog
.find( '.ve-init-mw-viewPageTarget-saveDialog-viewer' )
.empty().append( $( '<pre>' ).text( wikitext ) );
this.$saveDialogLoadingIcon.hide();
this.saveDialogReviewGoodButton.setDisabled( false );
}; };
/** /**
@ -660,7 +584,7 @@ ve.init.mw.ViewPageTarget.prototype.onSerialize = function ( wikitext ) {
*/ */
ve.init.mw.ViewPageTarget.prototype.onShowChangesError = function ( jqXHR, status ) { ve.init.mw.ViewPageTarget.prototype.onShowChangesError = function ( jqXHR, status ) {
alert( ve.msg( 'visualeditor-differror', status ) ); alert( ve.msg( 'visualeditor-differror', status ) );
this.$saveDialogLoadingIcon.hide(); this.saveDialog.$loadingIcon.hide();
}; };
/** /**
@ -672,7 +596,7 @@ ve.init.mw.ViewPageTarget.prototype.onShowChangesError = function ( jqXHR, statu
*/ */
ve.init.mw.ViewPageTarget.prototype.onSerializeError = function ( jqXHR, status ) { ve.init.mw.ViewPageTarget.prototype.onSerializeError = function ( jqXHR, status ) {
alert( ve.msg( 'visualeditor-serializeerror', status ) ); alert( ve.msg( 'visualeditor-serializeerror', status ) );
this.$saveDialogLoadingIcon.hide(); this.saveDialog.$loadingIcon.hide();
}; };
/** /**
@ -681,8 +605,8 @@ ve.init.mw.ViewPageTarget.prototype.onSerializeError = function ( jqXHR, status
* @method * @method
*/ */
ve.init.mw.ViewPageTarget.prototype.onEditConflict = function () { ve.init.mw.ViewPageTarget.prototype.onEditConflict = function () {
this.$saveDialogLoadingIcon.hide(); this.saveDialog.$loadingIcon.hide();
this.swapSaveDialog( 'conflict' ); this.saveDialog.swapPanel( 'conflict' );
}; };
/** /**
@ -691,8 +615,9 @@ ve.init.mw.ViewPageTarget.prototype.onEditConflict = function () {
* @method * @method
*/ */
ve.init.mw.ViewPageTarget.prototype.onNoChanges = function () { ve.init.mw.ViewPageTarget.prototype.onNoChanges = function () {
this.$saveDialogLoadingIcon.hide(); this.saveDialog.$loadingIcon.hide();
this.swapSaveDialog( 'nochanges' ); this.saveDialog.swapPanel( 'nochanges' );
this.saveDialog.reviewGoodButton.setDisabled( false );
}; };
/** /**
@ -778,10 +703,7 @@ ve.init.mw.ViewPageTarget.prototype.onToolbarFeedbackToolClick = function () {
*/ */
ve.init.mw.ViewPageTarget.prototype.onSurfaceModelTransact = function () { ve.init.mw.ViewPageTarget.prototype.onSurfaceModelTransact = function () {
// Clear the diff // Clear the diff
this.$saveDialog this.saveDialog.$reviewViewer.empty();
.find( '.ve-init-mw-viewPageTarget-saveDialog-slide-review .ve-init-mw-viewPageTarget-saveDialog-viewer' )
.empty();
this.surface.getModel().disconnect( this, { 'transact': 'onSurfaceModelTransact' } ); this.surface.getModel().disconnect( this, { 'transact': 'onSurfaceModelTransact' } );
}; };
@ -833,8 +755,28 @@ ve.init.mw.ViewPageTarget.prototype.updateToolbarSaveButtonState = function () {
* *
* @method * @method
*/ */
ve.init.mw.ViewPageTarget.prototype.onSaveDialogReviewButtonClick = function () { ve.init.mw.ViewPageTarget.prototype.onSaveDialogReview = function () {
this.swapSaveDialog( 'review' ); var doc = this.surface.getModel().getDocument();
this.sanityCheckVerified = true;
this.saveDialog.setSanityCheck( this.sanityCheckVerified );
if ( !this.saveDialog.$reviewViewer.find( 'table, pre' ).length ) {
this.saveDialog.reviewGoodButton.setDisabled( true );
this.saveDialog.$loadingIcon.show();
if ( this.pageExists ) {
// Has no callback, handled via target.onShowChanges
this.showChanges(
ve.dm.converter.getDomFromData( doc.getFullData(), doc.getStore(), doc.getInternalList() )
);
} else {
this.serialize(
ve.dm.converter.getDomFromData( doc.getFullData(), doc.getStore(), doc.getInternalList() ),
ve.bind( this.onSerialize, this )
);
}
} else {
this.saveDialog.swapPanel( 'review' );
}
}; };
/** /**
@ -842,7 +784,7 @@ ve.init.mw.ViewPageTarget.prototype.onSaveDialogReviewButtonClick = function ()
* *
* @method * @method
*/ */
ve.init.mw.ViewPageTarget.prototype.onSaveDialogSaveButtonClick = function () { ve.init.mw.ViewPageTarget.prototype.onSaveDialogSave = function () {
this.saveDocument(); this.saveDocument();
}; };
@ -864,17 +806,17 @@ ve.init.mw.ViewPageTarget.prototype.saveDocument = function () {
if ( if (
+mw.user.options.get( 'forceeditsummary' ) && +mw.user.options.get( 'forceeditsummary' ) &&
saveOptions.summary === '' && saveOptions.summary === '' &&
!this.messages.missingsummary !this.saveDialog.messages.missingsummary
) { ) {
this.showMessage( this.saveDialog.showMessage(
'missingsummary', 'missingsummary',
// Wrap manually since this core message already includes a bold "Warning:" label // Wrap manually since this core message already includes a bold "Warning:" label
$( '<p>' ).append( ve.init.platform.getParsedMessage( 'missingsummary' ) ), $( '<p>' ).append( ve.init.platform.getParsedMessage( 'missingsummary' ) ),
{ wrap: false } { wrap: false }
); );
} else { } else {
this.saveDialogSaveButton.setDisabled( true ); this.saveDialog.saveButton.setDisabled( true );
this.$saveDialogLoadingIcon.show(); this.saveDialog.$loadingIcon.show();
this.save( this.save(
ve.dm.converter.getDomFromData( doc.getFullData(), doc.getStore(), doc.getInternalList() ), ve.dm.converter.getDomFromData( doc.getFullData(), doc.getStore(), doc.getInternalList() ),
saveOptions saveOptions
@ -882,21 +824,12 @@ ve.init.mw.ViewPageTarget.prototype.saveDocument = function () {
} }
}; };
/**
* Handle clicks on the review "Good" button in the save dialog.
*
* @method
*/
ve.init.mw.ViewPageTarget.prototype.onSaveDialogReviewGoodButtonClick = function () {
this.swapSaveDialog( 'save' );
};
/** /**
* Handle clicks on the resolve conflict button in the conflict dialog. * Handle clicks on the resolve conflict button in the conflict dialog.
* *
* @method * @method
*/ */
ve.init.mw.ViewPageTarget.prototype.onSaveDialogResolveConflictButtonClick = function () { ve.init.mw.ViewPageTarget.prototype.onSaveDialogResolveConflict= function () {
var doc = this.surface.getModel().getDocument(); var doc = this.surface.getModel().getDocument();
// Get Wikitext from the DOM, and set up a submit call when it's done // Get Wikitext from the DOM, and set up a submit call when it's done
this.serialize( this.serialize(
@ -915,21 +848,21 @@ ve.init.mw.ViewPageTarget.prototype.onSaveDialogResolveConflictButtonClick = fun
*/ */
ve.init.mw.ViewPageTarget.prototype.getSaveOptions = function () { ve.init.mw.ViewPageTarget.prototype.getSaveOptions = function () {
var options = { var options = {
'summary': this.$saveDialog.find( '#ve-init-mw-viewPageTarget-saveDialog-editSummary' ).val(), 'summary': this.saveDialog.editSummaryInput.$input.val(),
'captchaid': this.captcha && this.captcha.id, 'captchaid': this.captcha && this.captcha.id,
'captchaword': this.captcha && this.captcha.input.getValue() 'captchaword': this.captcha && this.captcha.input.getValue()
}; };
if ( this.sanityCheckPromise.state() === 'rejected' ) { if ( this.sanityCheckPromise.state() === 'rejected' ) {
options.needcheck = 1; options.needcheck = 1;
} }
if ( this.$saveDialog.find( '#wpMinoredit' ).prop( 'checked' ) ) { if ( this.saveDialog.$saveOptions.find( '#wpMinoredit' ).prop( 'checked' ) ) {
options.minor = 1; options.minor = 1;
} }
if ( this.$saveDialog.find( '#wpWatchthis' ).prop( 'checked' ) ) { if ( this.saveDialog.$saveOptions.find( '#wpWatchthis' ).prop( 'checked' ) ) {
options.watch = 1; options.watch = 1;
} }
this.$saveDialog this.saveDialog.$saveOptions
.find( '.ve-init-mw-viewPageTarget-saveDialog-checkboxes' ) .find( '.ve-ui-mwSaveDialog-checkboxes' )
.find( 'input:not(#wpMinoredit, #wpWatchthis)' ) .find( 'input:not(#wpMinoredit, #wpWatchthis)' )
.each( function () { .each( function () {
var $this = $( this ); var $this = $( this );
@ -942,33 +875,6 @@ ve.init.mw.ViewPageTarget.prototype.getSaveOptions = function () {
return options; return options;
}; };
/**
* Handle clicks on the close button in the save dialog.
*
* @method
* @param {jQuery.Event} e Mouse click event
*/
ve.init.mw.ViewPageTarget.prototype.onSaveDialogCloseButtonClick = function () {
this.hideSaveDialog();
};
/**
* Handle clicks on the previous view button in the save dialog.
*
* @method
* @param {jQuery.Event} e Mouse click event
*/
ve.init.mw.ViewPageTarget.prototype.onSaveDialogPrevButtonClick = function () {
var history = this.saveDialogSlideHistory;
if ( history.length < 2 ) {
throw new Error( 'PrevButton was triggered without a history' );
}
// Pop off current slide
history.pop();
// Navigate to last slide
this.swapSaveDialog( history[ history.length -1 ], { fromHistory: true } );
};
/** /**
* Set up the list of edit notices. * Set up the list of edit notices.
* *
@ -1379,155 +1285,25 @@ ve.init.mw.ViewPageTarget.prototype.detachToolbarButtons = function () {
ve.init.mw.ViewPageTarget.prototype.setupSaveDialog = function () { ve.init.mw.ViewPageTarget.prototype.setupSaveDialog = function () {
var sectionTitle = '', viewPage = this; var sectionTitle = '', viewPage = this;
// Save button on "save" slide viewPage.saveDialog = this.surface.getDialogs().getWindow( 'mwSave' );
this.saveDialogSaveButton = new ve.ui.PushButtonWidget( {
'label': ve.msg(
// visualeditor-savedialog-label-restore, visualeditor-savedialog-label-save
'visualeditor-savedialog-label-' + ( viewPage.restoring ? 'restore' : 'save' )
),
'flags': ['constructive']
} );
this.saveDialogSaveButton.connect( this, { 'click': 'onSaveDialogSaveButtonClick' } );
// Review button on "save" slide
this.saveDialogReviewButton = new ve.ui.PushButtonWidget( {
'label': ve.msg(
'visualeditor-savedialog-label-review'
)
} );
this.saveDialogReviewButton.connect( this, { 'click': 'onSaveDialogReviewButtonClick' } );
this.saveDialogReviewGoodButton = new ve.ui.PushButtonWidget( {
'label': ve.msg( 'visualeditor-savedialog-label-review-good' ),
'flags': ['constructive']
} );
this.saveDialogReviewGoodButton.connect(
this, { 'click': 'onSaveDialogReviewGoodButtonClick' }
);
this.saveDialogResolveConflictButton = new ve.ui.PushButtonWidget( {
'label': ve.msg( 'visualeditor-savedialog-label-resolve-conflict' ),
'flags': ['constructive']
} );
this.saveDialogResolveConflictButton.connect( this, { 'click': 'onSaveDialogResolveConflictButtonClick' } );
if ( viewPage.section ) { if ( viewPage.section ) {
sectionTitle = viewPage.$document.find( 'h1, h2, h3, h4, h5, h6' ).eq( viewPage.section - 1 ).text(); sectionTitle = viewPage.$document.find( 'h1, h2, h3, h4, h5, h6' ).eq( viewPage.section - 1 ).text();
sectionTitle = '/* ' + ve.graphemeSafeSubstring( sectionTitle, 0, 244 ) + ' */ '; sectionTitle = '/* ' + ve.graphemeSafeSubstring( sectionTitle, 0, 244 ) + ' */ ';
viewPage.saveDialog.editSummaryInput.$input.val( sectionTitle );
viewPage.sectionTitleRestored = true; viewPage.sectionTitleRestored = true;
if ( viewPage.sectionPositionRestored ) { if ( viewPage.sectionPositionRestored ) {
viewPage.onSectionRestored(); viewPage.onSectionRestored();
} }
} }
viewPage.$saveDialog // Connect to save dialog
// Must not use replaceWith because that can't be used on fragement roots, viewPage.saveDialog.connect( this, {
// plus, we want to preserve the reference and class names of the wrapper. 'save': 'onSaveDialogSave',
.empty().append( this.constructor.saveDialogTemplate ) 'review': 'onSaveDialogReview',
// Attach buttons 'resolve': 'onSaveDialogResolveConflict'
.find( '.ve-init-mw-viewPageTarget-saveDialog-slide-save' ) } );
.find( '.ve-init-mw-viewPageTarget-saveDialog-actions' ) // Setup checkboxes
.prepend( viewPage.saveDialogSaveButton.$, viewPage.saveDialogReviewButton.$ ) viewPage.saveDialog.setupCheckboxes( ve.getObjectValues( viewPage.checkboxes ).join( '\n' ) );
.end()
.end()
.find( '.ve-init-mw-viewPageTarget-saveDialog-slide-review' )
.find( '.ve-init-mw-viewPageTarget-saveDialog-actions' )
.prepend( viewPage.saveDialogReviewGoodButton.$ )
.end()
.end()
.find( '.ve-init-mw-viewPageTarget-saveDialog-slide-conflict' )
.find( '.ve-init-mw-viewPageTarget-saveDialog-actions' )
.prepend( viewPage.saveDialogResolveConflictButton.$ )
.end()
.end()
.find( '.ve-init-mw-viewPageTarget-saveDialog-closeButton' )
.click( ve.bind( viewPage.onSaveDialogCloseButtonClick, viewPage ) )
.end()
.find( '.ve-init-mw-viewPageTarget-saveDialog-prevButton' )
.click( ve.bind( viewPage.onSaveDialogPrevButtonClick, viewPage ) )
.end()
// Attach contents
.find( '#ve-init-mw-viewPageTarget-saveDialog-editSummary-label' )
.html( ve.init.platform.getParsedMessage( 'summary' ) )
.end()
.find( '#ve-init-mw-viewPageTarget-saveDialog-editSummary' )
.attr( {
'placeholder': ve.msg( 'visualeditor-editsummary' )
} )
.val( sectionTitle )
.placeholder()
.byteLimit( viewPage.editSummaryByteLimit )
.on( {
'focus': function () {
$( this ).parent().addClass(
've-init-mw-viewPageTarget-saveDialog-summary-focused'
);
},
'blur': function () {
$( this ).parent().removeClass(
've-init-mw-viewPageTarget-saveDialog-summary-focused'
);
},
'keyup keydown mouseup cut paste change focus blur': function () {
var $textarea = $( this ),
$editSummaryCount = $textarea
.closest( '.ve-init-mw-viewPageTarget-saveDialog-slide-save' )
.find( '.ve-init-mw-viewPageTarget-saveDialog-editSummaryCount' );
// TODO: This looks a bit weird, there is no unit in the UI, just numbers
// Users likely assume characters but then it seems to count down quicker
// than expected. Facing users with the word "byte" is bad? (bug 40035)
setTimeout( function () {
$editSummaryCount.text(
viewPage.editSummaryByteLimit - $.byteLength( $textarea.val() )
);
} );
}
} )
.end()
.find( '.ve-init-mw-viewPageTarget-saveDialog-editSummaryCount' )
.text( viewPage.editSummaryByteLimit )
.end()
.find( '.ve-init-mw-viewPageTarget-saveDialog-checkboxes' )
.html( ve.getObjectValues( viewPage.checkboxes ).join( '\n' ) )
.find( 'a' )
.attr( 'target', '_blank' )
.end()
.find( '#wpMinoredit' )
.prop( 'checked', +mw.user.options.get( 'minordefault' ) )
.end()
.find( '#wpWatchthis' )
.prop( 'checked',
mw.user.options.get( 'watchdefault' ) ||
( mw.user.options.get( 'watchcreations' ) && !viewPage.pageExists ) ||
mw.config.get( 'wgVisualEditor' ).isPageWatched
)
.end()
.end()
.find( '.ve-init-mw-viewPageTarget-saveDialog-license' )
.html( ve.init.platform.getParsedMessage( 'copyrightwarning' ) )
.end()
.find( '.ve-init-mw-viewPageTarget-saveDialog-conflict' )
.html( ve.init.platform.getParsedMessage( 'visualeditor-editconflict' ) )
.end()
.find( '.ve-init-mw-viewPageTarget-saveDialog-nochanges' )
.html( ve.init.platform.getParsedMessage( 'visualeditor-diff-nochanges' ) )
;
// Get reference to loading icon
viewPage.$saveDialogLoadingIcon = viewPage.$saveDialog
.find( '.ve-init-mw-viewPageTarget-saveDialog-working' );
// Hook onto the 'watch' event on by mediawiki.page.watch.ajax.js
// Triggered when mw.page.watch.updateWatchLink(link, action) is called
$( '#ca-watch, #ca-unwatch' )
.on(
'watchpage.mw',
function ( e, action ) {
viewPage.$saveDialog
.find( '#wpWatchthis' )
.prop( 'checked', ( action === 'watch' ) );
}
);
}; };
/** /**
@ -1536,208 +1312,11 @@ ve.init.mw.ViewPageTarget.prototype.setupSaveDialog = function () {
* @method * @method
*/ */
ve.init.mw.ViewPageTarget.prototype.showSaveDialog = function () { ve.init.mw.ViewPageTarget.prototype.showSaveDialog = function () {
var viewPage = this; this.toolbarBetaNotices.hide();
this.toolbarEditNotices.hide();
viewPage.surface.disable(); this.saveDialog.setSanityCheck( this.sanityCheckVerified );
viewPage.$document.css( 'opacity', 0.5 ); this.saveDialog.swapPanel( 'save' );
this.surface.getDialogs().open( 'mwSave' );
viewPage.toolbarBetaNotices.hide();
viewPage.toolbarEditNotices.hide();
viewPage.swapSaveDialog( 'save' );
viewPage.$saveDialog.fadeIn( 'fast', function () {
// Initial size
viewPage.onResizeSaveDialog();
} );
$( document ).on( 'keydown.ve-savedialog', function ( e ) {
// Escape
if ( e.which === ve.Keys.ESCAPE ) {
viewPage.onSaveDialogCloseButtonClick();
}
} );
$( window ).on( 'resize.ve-savedialog', ve.bind( viewPage.onResizeSaveDialog, viewPage ) );
};
/**
* Update window-size related aspects of the save dialog
*
* @method
*/
ve.init.mw.ViewPageTarget.prototype.onResizeSaveDialog = function () {
var $d = this.$saveDialog, $w = $( window );
// Available space for css-height is window height,
// without the space between the dialog and the window top,
// without the space above/below between css-height and outerHeight.
$d.css( 'max-height',
$w.height() -
( $d.offset().top - $w.scrollTop() ) -
( $d.outerHeight( true ) - $d.height() ) -
20 // shadow
);
};
/**
* Hide the save dialog
*/
ve.init.mw.ViewPageTarget.prototype.hideSaveDialog = function () {
// Reset history on close (bug 49481)
this.saveDialogSlideHistory.length = 0;
this.$saveDialog.fadeOut( 'fast' );
if ( this.$document ) {
this.$document.focus();
}
$( document ).off( 'keydown.ve-savedialog' );
$( window ).off( 'resize', this.onResizeSaveDialog );
if ( this.surface ) {
this.surface.enable();
this.$document.css( 'opacity', '' );
}
};
/**
* Reset the fields of the save dialog.
*
* TODO: Maybe call this more cleverly only when the document changes, so that closing and
* re-opening the saveDialog doesn't remove the user input and the diff cache.
*
* @method
*/
ve.init.mw.ViewPageTarget.prototype.resetSaveDialog = function () {
this.$saveDialog
.find( '#ve-init-mw-viewPageTarget-saveDialog-editSummary' )
.val( '' )
.end()
.find( '#wpMinoredit' )
.prop( 'checked', false )
.end()
// Clear the diff
.find( '.ve-init-mw-viewPageTarget-saveDialog-viewer' )
.empty();
};
/**
* Swap state in the save dialog.
*
* @method
* @param {string} slide One of 'save', 'review', 'conflict' or 'nochanges'
* @param {Object} [options]
* @param {boolean} [options.fromHistory] Whether this swap was triggered from interaction
* with the slide history (e.g. surpresses pushing of target slide in the history again).
* @returns {jQuery} The now active slide.
* @throws {Error} Unknown saveDialog slide
*/
ve.init.mw.ViewPageTarget.prototype.swapSaveDialog = function ( slide, options ) {
var $slide, $viewer,
doc = this.surface.getModel().getDocument();
if ( ve.indexOf( slide, [ 'save', 'review', 'conflict', 'nochanges' ] ) === -1 ) {
throw new Error( 'Unknown saveDialog slide: ' + slide );
}
options = options || {};
if ( !options.fromHistory ) {
this.saveDialogSlideHistory.push( slide );
}
$slide = this.$saveDialog.find( '.ve-init-mw-viewPageTarget-saveDialog-slide-' + slide );
this.$saveDialog
// Hide "prev" button when (back) on the first slide
.find( '.ve-init-mw-viewPageTarget-saveDialog-prevButton' )
.toggle( this.saveDialogSlideHistory.length >= 2 )
.end()
// Update title to one of:
// - visualeditor-savedialog-title-save
// - visualeditor-savedialog-title-review
// - visualeditor-savedialog-title-conflict
// - visualeditor-savedialog-title-nochanges
.find( '.ve-init-mw-viewPageTarget-saveDialog-title' )
.text( ve.msg( 'visualeditor-savedialog-title-' + slide ) )
.end()
// Hide other slides
.find( '.ve-init-mw-viewPageTarget-saveDialog-slide' )
.not( $slide )
.hide();
// Old messages should not persist after slide changes
this.clearAllMessages();
// Reset save button if we disabled it for e.g. unrecoverable spam error
this.saveDialogSaveButton.setDisabled( false );
if ( slide === 'save' ) {
if ( !this.sanityCheckVerified ) {
this.showMessage( 'dirtywarning', mw.msg( 'visualeditor-savedialog-warning-dirty' ) );
}
}
if ( slide === 'review' ) {
this.sanityCheckVerified = true;
$viewer = $slide.find( '.ve-init-mw-viewPageTarget-saveDialog-viewer' );
if ( !$viewer.find( 'table, pre' ).length ) {
this.saveDialogReviewGoodButton.setDisabled( true );
this.$saveDialogLoadingIcon.show();
if ( this.pageExists ) {
// Has no callback, handled via target.onShowChanges
this.showChanges(
ve.dm.converter.getDomFromData( doc.getFullData(), doc.getStore(), doc.getInternalList() )
);
} else {
this.serialize(
ve.dm.converter.getDomFromData( doc.getFullData(), doc.getStore(), doc.getInternalList() ),
ve.bind( this.onSerialize, this )
);
}
}
this.$saveDialog.css( 'width', '100%' );
} else {
this.$saveDialog.css( 'width', '' );
}
// Show the target slide
$slide.show();
mw.hook( 've.saveDialog.stateChanged' ).fire();
if ( slide === 'save' ) {
setTimeout( function () {
var $textarea = $slide.find( '#ve-init-mw-viewPageTarget-saveDialog-editSummary' );
$textarea.focus();
// If message has be pre-filled (e.g. section edit), move cursor to end
if ( $textarea.val() !== '' ) {
ve.selectEnd( $textarea[0] );
}
} );
}
return $slide;
};
/**
* Add the save dialog to the user interface.
*
* @method
*/
ve.init.mw.ViewPageTarget.prototype.attachSaveDialog = function () {
this.surface.$globalOverlay.append(
this.$toolbarTracker.append(
this.$saveDialog
)
);
};
/**
* Remove the save dialog from the user interface.
*
* @method
*/
ve.init.mw.ViewPageTarget.prototype.detachSaveDialog = function () {
this.$saveDialog.detach();
}; };
/** /**
@ -2117,63 +1696,6 @@ ve.init.mw.ViewPageTarget.prototype.onSectionRestored = function () {
this.sectionTitleRestored = false; this.sectionTitleRestored = false;
}; };
/**
* Show a message in the save dialog.
*
* @param {string} name Message's unique name
* @param {string|jQuery|Array} message Message content (string of HTML, jQuery object or array of
* Node objects)
* @param {Object} [options]
* @param {boolean} [options.wrap="warning"] Whether to wrap the message in a paragraph and if
* so, how. One of "warning", "error" or false.
*/
ve.init.mw.ViewPageTarget.prototype.showMessage = function ( name, message, options ) {
var $message;
if ( !this.messages[name] ) {
options = options || {};
if ( options.wrap === undefined ) {
options.wrap = 'warning';
}
$message = $( '<div class="ve-init-mw-viewPageTarget-saveDialog-message"></div>' );
if ( options.wrap !== false ) {
$message.append( $( '<p>').append(
// visualeditor-savedialog-label-error
// visualeditor-savedialog-label-warning
$( '<strong>' ).text( mw.msg( 'visualeditor-savedialog-label-' + options.wrap ) ),
document.createTextNode( mw.msg( 'colon-separator' ) ),
message
) );
} else {
$message.append( message );
}
this.$saveDialog.find( '.ve-init-mw-viewPageTarget-saveDialog-messages' )
.append( $message );
this.messages[name] = $message;
}
};
/**
* Remove a message from the save dialog.
* @param {string} name Message's unique name
*/
ve.init.mw.ViewPageTarget.prototype.clearMessage = function ( name ) {
if ( this.messages[name] ) {
this.messages[name].remove();
delete this.messages[name];
}
};
/**
* Remove all messages from the save dialog.
*/
ve.init.mw.ViewPageTarget.prototype.clearAllMessages = function () {
this.$saveDialog
.find( '.ve-init-mw-viewPageTarget-saveDialog-messages' )
.empty();
this.messages = {};
};
/** /**
* Add onbeforunload handler. * Add onbeforunload handler.
* *

View file

@ -0,0 +1,377 @@
/*!
* VisualEditor UserInterface MWSaveDialog class.
*
* @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
/*global mw */
/**
* Dialog for saving MediaWiki articles.
*
* @class
* @extends ve.ui.MWDialog
*
* @constructor
* @param {ve.ui.Surface} surface
* @param {Object} [config] Config options
*/
ve.ui.MWSaveDialog = function VeUiMWSaveDialog( surface, config ) {
// Configuration initialization
config = ve.extendObject( { 'small': true }, config );
// Parent constructor
ve.ui.MWDialog.call( this, surface, config );
// Properties
this.sanityCheckVerified = false;
this.editSummaryByteLimit = 255;
this.restoring = false;
this.messages = {};
};
/* Inheritance */
ve.inheritClass( ve.ui.MWSaveDialog, ve.ui.MWDialog );
/* Static Properties */
ve.ui.MWSaveDialog.static.name = 'mwSave';
ve.ui.MWSaveDialog.static.titleMessage = 'visualeditor-savedialog-title-save';
/* Methods */
/**
* @inheritdoc
*/
ve.ui.MWSaveDialog.prototype.initialize = function () {
var saveDialog = this;
// Parent method
ve.ui.MWDialog.prototype.initialize.call( this );
// Properties
this.savePanel = new ve.ui.PanelLayout( { '$$': this.frame.$$, 'scrollable': true } );
// Save panel
this.$editSummaryLabel = this.frame.$$( '<div>' ).addClass( 've-ui-mwSaveDialog-summaryLabel' )
.html( ve.init.platform.getParsedMessage( 'summary' ) );
this.editSummaryInput = new ve.ui.TextInputWidget(
{ '$$': this.frame.$$, 'multiline': true, 'placeholder': ve.msg( 'visualeditor-editsummary' ) }
);
this.editSummaryInput.$.addClass( 've-ui-mwSaveDialog-summary' );
this.editSummaryInput.$input
.placeholder()
.byteLimit( this.editSummaryByteLimit )
.prop( 'tabIndex', 0 );
this.editSummaryInput.on( 'change', ve.bind( function () {
var $textarea = this.editSummaryInput.$input,
$editSummaryCount = this.savePanel.$.find( '.ve-ui-mwSaveDialog-editSummary-count' );
// TODO: This looks a bit weird, there is no unit in the UI, just numbers
// Users likely assume characters but then it seems to count down quicker
// than expected. Facing users with the word "byte" is bad? (bug 40035)
setTimeout( function () {
$editSummaryCount.text(
saveDialog.editSummaryByteLimit - $.byteLength( $textarea.val() )
);
} );
}, this ) );
this.$saveOptions = this.frame.$$( '<div>' ).addClass( 've-ui-mwSaveDialog-options' ).append(
this.frame.$$( '<div>' ).addClass( 've-ui-mwSaveDialog-checkboxes' ),
new ve.ui.InputLabelWidget( { '$$': this.frame.$$, 'label': 'text' } ).$
.addClass( 've-ui-mwSaveDialog-editSummary-count' ).text( this.editSummaryByteLimit )
);
this.$saveMessages = this.frame.$$( '<div>' );
this.$saveActions = this.frame.$$( '<div>' ).append(
this.frame.$$( '<div>' ).addClass( 've-ui-mwSaveDialog-dirtymsg' )
);
this.$saveFoot = this.frame.$$( '<div>' ).addClass( 've-ui-mwSaveDialog-foot' ).append(
this.frame.$$( '<p>' ).addClass( 've-ui-mwSaveDialog-license' )
.html( ve.init.platform.getParsedMessage( 'copyrightwarning' ) )
);
this.savePanel.$.append(
this.$editSummaryLabel,
this.editSummaryInput.$,
this.$saveOptions,
this.$saveMessages,
this.$saveActions,
this.$saveFoot
);
// Review panel
this.reviewPanel = new ve.ui.PanelLayout( { '$$': this.frame.$$, 'scrollable': true } );
this.$reviewViewer = this.frame.$$( '<div>' ).addClass( 've-ui-mwSaveDialog-viewer' );
this.$reviewActions = this.frame.$$( '<div>' ).addClass( 've-ui-mwSaveDialog-actions' );
this.reviewPanel.$.append( this.$reviewViewer, this.$reviewActions );
// Conflict panel
this.conflictPanel = new ve.ui.PanelLayout( { '$$': this.frame.$$, 'scrollable': true } );
this.$conflict = this.frame.$$( '<div>' ).addClass( 've-ui-mwSaveDialog-conflict' )
.html( ve.init.platform.getParsedMessage( 'visualeditor-editconflict' ) );
this.conflictPanel.$.append( this.$conflict );
// No changes panel
this.nochangesPanel = new ve.ui.PanelLayout( { '$$': this.frame.$$, 'scrollable': true } );
this.$noChanges = this.frame.$$( '<div>' ).addClass( 've-ui-mwSaveDialog-nochanges' )
.html( ve.init.platform.getParsedMessage( 'visualeditor-diff-nochanges' ) );
this.nochangesPanel.$.append( this.$noChanges );
// Panel stack
this.panel = new ve.ui.StackPanelLayout( { '$$': this.frame.$$, 'scrollable': true } );
this.panel.$.addClass( 've-ui-mwSaveDialog-panel' );
this.panel.addItems( [this.savePanel, this.reviewPanel, this.conflictPanel, this.nochangesPanel], 0 );
/* Buttons */
// Save button for "save" panel
this.saveButton = new ve.ui.PushButtonWidget( {
'label': ve.msg(
// visualeditor-savedialog-label-restore, visualeditor-savedialog-label-save
'visualeditor-savedialog-label-' + ( this.restoring ? 'restore' : 'save' )
),
'flags': ['constructive']
} );
this.saveButton.connect( this, { 'click': 'onSaveButtonClick' } );
// Review button for "save" panel
this.reviewButton = new ve.ui.PushButtonWidget( {
'label': ve.msg( 'visualeditor-savedialog-label-review' )
} );
this.reviewButton.connect( this, { 'click': 'onReviewButtonClick' } );
// Review good button on "review" panel
this.reviewGoodButton = new ve.ui.PushButtonWidget( {
'label': ve.msg( 'visualeditor-savedialog-label-review-good' ),
'flags': ['constructive']
} );
this.reviewGoodButton.connect( this, { 'click': 'onReviewGoodButtonClick' } );
// Resolve conflict
this.resolveConflictButton = new ve.ui.PushButtonWidget( {
'label': ve.msg( 'visualeditor-savedialog-label-resolve-conflict' ),
'flags': ['constructive']
} );
this.resolveConflictButton.connect( this, { 'click': 'onResolveConflictButtonClick' } );
this.$loadingIcon = this.frame.$$( '<div>' ).addClass( 've-ui-mwSaveDialog-working' );
// Initialization
this.$body.append( this.panel.$ );
this.$foot.append(
this.reviewButton.$,
this.saveButton.$,
this.reviewGoodButton.$,
this.resolveConflictButton.$,
this.$loadingIcon
);
};
ve.ui.MWSaveDialog.prototype.onSaveButtonClick = function () {
this.emit( 'save' );
};
ve.ui.MWSaveDialog.prototype.onReviewButtonClick = function () {
this.emit( 'review' );
};
ve.ui.MWSaveDialog.prototype.onReviewGoodButtonClick = function () {
this.swapPanel( 'save' );
};
ve.ui.MWSaveDialog.prototype.onResolveConflictButtonClick = function () {
this.emit( 'resolve' );
};
/**
* Swap state in the save dialog.
*
* @param {string} panel One of 'save', 'review', 'conflict' or 'nochanges'
* @returns {jQuery} The now active panel
* @throws {Error} Unknown saveDialog panel
*/
ve.ui.MWSaveDialog.prototype.swapPanel = function ( panel ) {
var dialog = this,
panelObj = dialog[panel + 'Panel'];
if ( ve.indexOf( panel, [ 'save', 'review', 'conflict', 'nochanges' ] ) === -1 ) {
throw new Error( 'Unknown saveDialog panel: ' + panel );
}
// Update the window title
this.setTitle( ve.msg( 'visualeditor-savedialog-title-' + panel ) );
// Old messages should not persist after panel changes
this.clearAllMessages();
// Reset save button if we disabled it for e.g. unrecoverable spam error
this.saveButton.setDisabled( false );
switch( panel ) {
case 'save':
if ( !this.sanityCheckVerified ) {
this.showMessage( 'dirtywarning', mw.msg( 'visualeditor-savedialog-warning-dirty' ) );
} else {
this.saveButton.$.show();
this.reviewButton.$.show();
this.reviewGoodButton.$.hide();
this.resolveConflictButton.$.hide();
setTimeout( function () {
// fix input reference
var $textarea = dialog.editSummaryInput.$input;
$textarea.focus();
// If message has be pre-filled (e.g. section edit), move cursor to end
if ( $textarea.val() !== '' ) {
ve.selectEnd( $textarea[0] );
}
} );
}
break;
case 'conflict':
this.saveButton.$.hide();
this.reviewButton.$.hide();
this.reviewGoodButton.$.hide();
this.resolveConflictButton.$.show();
break;
case 'review':
// Make room for the diff by transitioning to a non-small window
this.$frame.removeClass( 've-ui-window-frame-small' );
/* falls through */
case 'nochanges':
this.saveButton.$.hide();
this.reviewButton.$.hide();
this.reviewGoodButton.$.show();
this.resolveConflictButton.$.hide();
break;
}
if ( panel !== 'review' ) {
// Restore original "small" size
this.$frame.addClass( 've-ui-window-frame-small' );
}
// Show the target panel
this.panel.showItem( panelObj );
mw.hook( 've.saveDialog.stateChanged' ).fire();
return dialog;
};
/**
* Show a message in the save dialog.
*
* @param {string} name Message's unique name
* @param {string|jQuery|Array} message Message content (string of HTML, jQuery object or array of
* Node objects)
* @param {Object} [options]
* @param {boolean} [options.wrap="warning"] Whether to wrap the message in a paragraph and if
* so, how. One of "warning", "error" or false.
*/
ve.ui.MWSaveDialog.prototype.showMessage = function ( name, message, options ) {
var $message;
if ( !this.messages[name] ) {
options = options || {};
if ( options.wrap === undefined ) {
options.wrap = 'warning';
}
$message = $( '<div class="ve-ui-mwSaveDialog-message"></div>' );
if ( options.wrap !== false ) {
$message.append( $( '<p>').append(
// visualeditor-savedialog-label-error
// visualeditor-savedialog-label-warning
$( '<strong>' ).text( mw.msg( 'visualeditor-savedialog-label-' + options.wrap ) ),
document.createTextNode( mw.msg( 'colon-separator' ) ),
message
) );
} else {
$message.append( message );
}
this.$saveMessages.append( $message );
this.messages[name] = $message;
}
};
/**
* Remove a message from the save dialog.
* @param {string} name Message's unique name
*/
ve.ui.MWSaveDialog.prototype.clearMessage = function ( name ) {
if ( this.messages[name] ) {
this.messages[name].remove();
delete this.messages[name];
}
};
/**
* Remove all messages from the save dialog.
*/
ve.ui.MWSaveDialog.prototype.clearAllMessages = function () {
this.$saveMessages.empty();
this.messages = {};
};
/**
* Reset the fields of the save dialog.
*
* @method
*/
ve.ui.MWSaveDialog.prototype.reset = function () {
// Reset summary input
this.editSummaryInput.$input.val( '' );
// Uncheck minoredit
this.$saveOptions.find( '.ve-ui-mwSaveDialog-checkboxes' )
.find( '#wpMinoredit' ).prop( 'checked', false );
// Clear the diff
this.$reviewViewer.empty();
};
/**
* Initialize MediaWiki page specific checkboxes
*
* @param {string} checkboxes Multiline HTML
*/
ve.ui.MWSaveDialog.prototype.setupCheckboxes = function ( checkboxes ) {
this.$saveOptions.find( '.ve-ui-mwSaveDialog-checkboxes' )
.html( checkboxes )
.find( 'a' )
.attr( 'target', '_blank' )
.end()
.find( '#wpMinoredit' )
.prop( 'checked', mw.user.options.get( 'minordefault' ) )
.prop( 'tabIndex', 0 )
.end()
.find( '#wpWatchthis' )
.prop( 'checked',
mw.user.options.get( 'watchdefault' ) ||
( mw.user.options.get( 'watchcreations' ) && !this.pageExists ) ||
mw.config.get( 'wgVisualEditor' ).isPageWatched
).prop( 'tabIndex', 0 );
// TODO: Need to set all checkboxes provided by api tabindex to 0 for proper accessibility
};
/**
* Set review content and show review panel
*
* @param {string} content Diff HTML or wikitext
*/
ve.ui.MWSaveDialog.prototype.setDiffAndReview = function ( content ) {
this.$reviewViewer.empty().append( content );
this.reviewGoodButton.setDisabled( false );
this.$loadingIcon.hide();
this.swapPanel( 'review' );
};
/**
* Set sanity check flag
*
* @param {boolean} verified Status of sanity check
*/
ve.ui.MWSaveDialog.prototype.setSanityCheck = function ( verified ) {
this.sanityCheckVerified = !!verified;
};
/* Registration */
ve.ui.dialogFactory.register( ve.ui.MWSaveDialog );

View file

@ -88,3 +88,95 @@
left: 0; left: 0;
right: 0; right: 0;
} }
/* ve.ui.MWSaveDialog */
.ve-ui-mwSaveDialog-panel {
margin: 1.25em;
}
.ve-ui-mwSaveDialog-summaryLabel {
padding: 0.25em 0;
}
.ve-ui-mwSaveDialog-summary {
width: 100%;
background-color: #fff;
border: solid 1px #ccc;
padding: 0.5em;
border-radius: 0.25em 0.25em 0 0;
}
.ve-ui-mwSaveDialog-summary textarea {
margin: 0;
padding: 0;
resize: none;
height: 80px;
border: none;
box-shadow: none;
background-color: transparent;
}
.ve-ui-mwSaveDialog-summary textarea:focus,
.ve-ui-mwSaveDialog-summary textarea:active {
box-shadow: none;
}
.ve-ui-mwSaveDialog-foot {
margin: 0.5em;
}
.ve-ui-mwSaveDialog-options {
position: relative;
background-color: #f7f7f7;
border: solid 1px #ccc;
border-top: none;
border-radius: 0 0 0.25em 0.25em;
min-height: 3em;
}
.ve-ui-mwSaveDialog-checkboxes {
margin-right: 3.25em; /* Hack to prevent overlap on edit summary count */
line-height: 3em;
padding: 0 0.75em;
}
.ve-ui-mwSaveDialog-checkboxes label {
padding-right: 0.75em;
vertical-align: middle;
}
.ve-ui-mwSaveDialog-checkboxes input {
vertical-align: middle;
}
.ve-ui-mwSaveDialog-editSummary-count {
position: absolute;
right: 0;
top: 0;
bottom: 0;
border-left: solid 1px #eee;
line-height: 3em;
padding: 0 1em;
color: #aaa;
}
.ve-ui-mwSaveDialog-working {
display: none;
float: right;
height: 2.5em;
width: 128px;
margin-right: 1em;
background-position: right center;
background-repeat: no-repeat;
}
.ve-ui-mwSaveDialog-license,
.ve-ui-mwSaveDialog-dirtymsg,
.ve-ui-mwSaveDialog-report-notice {
font-size: 0.85em;
line-height: 1.25em;
padding: 0;
margin: 0;
color: #999;
}

View file

@ -17,7 +17,6 @@
background-color: rgba(255,255,255,0.5); background-color: rgba(255,255,255,0.5);
-webkit-animation: ve-ui-fade-in 250ms ease-in-out 0 1 normal; -webkit-animation: ve-ui-fade-in 250ms ease-in-out 0 1 normal;
-moz-animation: ve-ui-fade-in 250ms ease-in-out 0 1 normal; -moz-animation: ve-ui-fade-in 250ms ease-in-out 0 1 normal;
-ms-animation: ve-ui-fade-in 250ms ease-in-out 0 1 normal;
-o-animation: ve-ui-fade-in 250ms ease-in-out 0 1 normal; -o-animation: ve-ui-fade-in 250ms ease-in-out 0 1 normal;
animation: ve-ui-fade-in 250ms ease-in-out 0 1 normal; animation: ve-ui-fade-in 250ms ease-in-out 0 1 normal;
} }
@ -25,7 +24,6 @@
.ve-ui-dialog-closing { .ve-ui-dialog-closing {
-webkit-animation: ve-ui-fade-in 250ms ease-in-out 0 1 reverse; -webkit-animation: ve-ui-fade-in 250ms ease-in-out 0 1 reverse;
-moz-animation: ve-ui-fade-in 250ms ease-in-out 0 1 reverse; -moz-animation: ve-ui-fade-in 250ms ease-in-out 0 1 reverse;
-ms-animation: ve-ui-fade-in 250ms ease-in-out 0 1 reverse;
-o-animation: ve-ui-fade-in 250ms ease-in-out 0 1 reverse; -o-animation: ve-ui-fade-in 250ms ease-in-out 0 1 reverse;
animation: ve-ui-fade-in 250ms ease-in-out 0 1 reverse; animation: ve-ui-fade-in 250ms ease-in-out 0 1 reverse;
} }
@ -45,22 +43,24 @@
border-radius: 0.5em; border-radius: 0.5em;
box-shadow: 0 0.2em 1em rgba(0, 0, 0, 0.3); box-shadow: 0 0.2em 1em rgba(0, 0, 0, 0.3);
overflow: hidden; overflow: hidden;
-webkit-transition: all 250ms ease-in-out;
-moz-transition: all 250ms ease-in-out;
-o-transition: all 250ms ease-in-out;
transition: all 250ms ease-in-out;
-webkit-animation: ve-ui-zoom-in 250ms ease-in-out 0 1 normal; -webkit-animation: ve-ui-zoom-in 250ms ease-in-out 0 1 normal;
-moz-animation: ve-ui-zoom-in 250ms ease-in-out 0 1 normal; -moz-animation: ve-ui-zoom-in 250ms ease-in-out 0 1 normal;
-ms-animation: ve-ui-zoom-in 250ms ease-in-out 0 1 normal;
-o-animation: ve-ui-zoom-in 250ms ease-in-out 0 1 normal; -o-animation: ve-ui-zoom-in 250ms ease-in-out 0 1 normal;
animation: ve-ui-zoom-in 250ms ease-in-out 0 1 normal; animation: ve-ui-zoom-in 250ms ease-in-out 0 1 normal;
} }
.ve-ui-dialog .ve-ui-window-frame.ve-ui-window-frame-small { .ve-ui-dialog .ve-ui-window-frame-small {
max-width: 600px; width: 600px;
max-height: 300px; max-height: 375px;
} }
.ve-ui-dialog-closing .ve-ui-window-frame { .ve-ui-dialog-closing .ve-ui-window-frame {
-webkit-animation: ve-ui-zoom-in 250ms ease-in-out 0 1 reverse; -webkit-animation: ve-ui-zoom-in 250ms ease-in-out 0 1 reverse;
-moz-animation: ve-ui-zoom-in 250ms ease-in-out 0 1 reverse; -moz-animation: ve-ui-zoom-in 250ms ease-in-out 0 1 reverse;
-ms-animation: ve-ui-zoom-in 250ms ease-in-out 0 1 reverse;
-o-animation: ve-ui-zoom-in 250ms ease-in-out 0 1 reverse; -o-animation: ve-ui-zoom-in 250ms ease-in-out 0 1 reverse;
animation: ve-ui-zoom-in 250ms ease-in-out 0 1 reverse; animation: ve-ui-zoom-in 250ms ease-in-out 0 1 reverse;
} }
@ -185,3 +185,7 @@
right: 0; right: 0;
box-shadow: 0 0 0.25em rgba(0,0,0,0.25); box-shadow: 0 0 0.25em rgba(0,0,0,0.25);
} }
.ve-ui-window-body .ve-ce-documentNode {
padding: 1.875em; /* 1.5/0.8 */
}

View file

@ -115,7 +115,7 @@ ve.ui.Window.prototype.initialize = function () {
// Properties // Properties
this.$title = this.$$( '<div class="ve-ui-window-title"></div>' ); this.$title = this.$$( '<div class="ve-ui-window-title"></div>' );
if ( this.getTitle() ) { if ( this.getTitle() ) {
this.$title.text( this.getTitle() ); this.setTitle();
} }
this.$icon = this.$$( '<div class="ve-ui-window-icon"></div>' ) this.$icon = this.$$( '<div class="ve-ui-window-icon"></div>' )
.addClass( 've-ui-icon-' + this.constructor.static.icon ); .addClass( 've-ui-icon-' + this.constructor.static.icon );
@ -214,8 +214,10 @@ ve.ui.Window.prototype.getTitle = function () {
}; };
/** /**
* Set the size of window frame.
* *
* @method * @param {number} [width=auto] Custom width
* @param {number} [height=auto] Custom height
*/ */
ve.ui.Window.prototype.setSize = function ( width, height ) { ve.ui.Window.prototype.setSize = function ( width, height ) {
if ( !this.frame.$content ) { if ( !this.frame.$content ) {
@ -229,8 +231,19 @@ ve.ui.Window.prototype.setSize = function ( width, height ) {
}; };
/** /**
* Set the title of the window.
* *
* @method * @param {string} [customTitle] Custom title, override the static.titleMessage
*/
ve.ui.Window.prototype.setTitle = function ( customTitle ) {
this.$title.text( customTitle || this.getTitle() );
};
/**
* Set the height of window to fit with contents.
*
* @param {number} [min=0] Min height
* @param {number} [max] Max height (defaults to content's outer height)
*/ */
ve.ui.Window.prototype.fitHeightToContents = function ( min, max ) { ve.ui.Window.prototype.fitHeightToContents = function ( min, max ) {
var height = this.frame.$content.outerHeight(); var height = this.frame.$content.outerHeight();
@ -241,7 +254,10 @@ ve.ui.Window.prototype.fitHeightToContents = function ( min, max ) {
}; };
/** /**
* Set the width of window to fit with contents.
* *
* @param {number} [min=0] Min height
* @param {number} [max] Max height (defaults to content's outer width)
*/ */
ve.ui.Window.prototype.fitWidthToContents = function ( min, max ) { ve.ui.Window.prototype.fitWidthToContents = function ( min, max ) {
var width = this.frame.$content.outerWidth(); var width = this.frame.$content.outerWidth();
@ -252,8 +268,10 @@ ve.ui.Window.prototype.fitWidthToContents = function ( min, max ) {
}; };
/** /**
* Set the position of window to fit with contents..
* *
* @method * @param {string} left Left offset
* @param {string} top Top offset
*/ */
ve.ui.Window.prototype.setPosition = function ( left, top ) { ve.ui.Window.prototype.setPosition = function ( left, top ) {
this.$.css( { 'left': left, 'top': top } ); this.$.css( { 'left': left, 'top': top } );

View file

@ -105,16 +105,13 @@ ve.ui.WindowSet.prototype.getCurrent = function () {
}; };
/** /**
* Opens a given window. * Return a given window.
* *
* Any already open dialog will be closed.
*
* @method
* @param {string} name Symbolic name of window * @param {string} name Symbolic name of window
* @param {Object} [config] Configuration options to be sent to the window class constructor * @param {Object} [config] Configuration options to be sent to the window class constructor
* @chainable * @return {ve.ui.Window} Window with specified name
*/ */
ve.ui.WindowSet.prototype.open = function ( name, config ) { ve.ui.WindowSet.prototype.getWindow = function ( name, config ) {
var win; var win;
if ( !this.factory.lookup( name ) ) { if ( !this.factory.lookup( name ) ) {
@ -133,8 +130,19 @@ ve.ui.WindowSet.prototype.open = function ( name, config ) {
this.$.append( win.$ ); this.$.append( win.$ );
win.getFrame().load(); win.getFrame().load();
} }
return this.windows[name];
};
this.windows[name].open(); /**
* Opens a given window.
*
* Any already open dialog will be closed.
*
* @param {string} name Symbolic name of window
* @param {Object} [config] Config options to be sent to the window class constructor
* @chainable
*/
ve.ui.WindowSet.prototype.open = function ( name, config ) {
this.getWindow( name, config ).open();
return this; return this;
}; };

View file

@ -51,11 +51,11 @@ ve.ui.ButtonWidget = function VeUiButtonWidget( config ) {
.append( this.$label ) .append( this.$label )
.attr( { .attr( {
'role': 'button', 'role': 'button',
'tabIndex': config.tabIndex || 0,
'title': config.title, 'title': config.title,
'href': config.href, 'href': config.href,
'target': config.target 'target': config.target
} ); } )
.prop( 'tabIndex', config.tabIndex || 0 );
this.$ this.$
.addClass( 've-ui-buttonWidget' ) .addClass( 've-ui-buttonWidget' )
.append( this.$button ); .append( this.$button );