jenkins-bot has submitted this change and it was merged.
Change subject: Add a confirmation dialog for canceling edits
......................................................................
Add a confirmation dialog for canceling edits
Pop up a confirmation dialog when the user tries to cancel an
edit with changes.
Also, make all editor instances discard data when discard is
chosen.
Bug: T111115
Bug: T109365
Change-Id: Ib2977892e413468262c2be76e89678aad4737ecd
---
M Resources.php
M i18n/en.json
M i18n/qqq.json
M modules/flow-initialize.js
A modules/flow/ui/mw.flow.ui.CancelConfirmDialog.js
A modules/flow/ui/mw.flow.ui.Overlay.js
M modules/flow/ui/mw.flow.ui.js
M modules/flow/ui/widgets/editor/editors/mw.flow.ui.AbstractEditorWidget.js
M modules/flow/ui/widgets/editor/editors/mw.flow.ui.VisualEditorWidget.js
M modules/flow/ui/widgets/editor/editors/mw.flow.ui.WikitextEditorWidget.js
M modules/flow/ui/widgets/editor/mw.flow.ui.EditorSwitcherWidget.js
M modules/flow/ui/widgets/editor/mw.flow.ui.EditorWidget.js
A modules/styles/flow/mw.flow.ui.Overlay.less
13 files changed, 174 insertions(+), 5 deletions(-)
Approvals:
Catrope: Looks good to me, approved
jenkins-bot: Verified
diff --git a/Resources.php b/Resources.php
index 5fbb99a..e5d433a 100644
--- a/Resources.php
+++ b/Resources.php
@@ -374,6 +374,8 @@
'ext.flow.ui' => $flowResourceTemplate + array(
'scripts' => array(
'flow/ui/mw.flow.ui.js',
+ 'flow/ui/mw.flow.ui.Overlay.js',
+ 'flow/ui/mw.flow.ui.CancelConfirmDialog.js',
'flow/ui/widgets/mw.flow.ui.TopicMenuSelectWidget.js',
'flow/ui/widgets/mw.flow.ui.ToCWidget.js',
'flow/ui/widgets/mw.flow.ui.ReorderTopicsWidget.js',
@@ -394,6 +396,7 @@
),
'styles' => array(
'styles/flow/mw.flow.ui.less',
+ 'styles/flow/mw.flow.ui.Overlay.less',
'styles/flow/widgets/mw.flow.ui.NavigationWidget.less',
'styles/flow/widgets/mw.flow.ui.TopicMenuSelectWidget.less',
'styles/flow/widgets/mw.flow.ui.ReorderTopicsWidget.less',
@@ -410,6 +413,10 @@
'messages' => array(
'flow-error-parsoid-failure',
'flow-error-default',
+ 'flow-dialog-cancelconfirm-title',
+ 'flow-dialog-cancelconfirm-message',
+ 'flow-dialog-cancelconfirm-keep',
+ 'flow-dialog-cancelconfirm-discard',
),
'dependencies' => array (
'oojs-ui',
diff --git a/i18n/en.json b/i18n/en.json
index 7f9662e..31fdb01 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -566,6 +566,10 @@
"flow-notification-enabled-on-talkpage-email-batch-body": "Flow, the
new wiki discussion system, has been enabled on your user talk page on
{{SITENAME}}. You can get more information, provide feedback or disable the new
system any time from the Beta features section in your preferences.",
"flow-beta-feature-add-archive-template-edit-summary": "Adding archive
template",
"flow-beta-feature-remove-archive-template-edit-summary": "Removing
archive template",
+ "flow-dialog-cancelconfirm-title": "Are you sure?",
+ "flow-dialog-cancelconfirm-message": "Are you sure you want to cancel
without saving first?",
+ "flow-dialog-cancelconfirm-keep": "Continue editing",
+ "flow-dialog-cancelconfirm-discard": "Discard changes",
"flow-guidedtour-optin-welcome": "{{GENDER:$1|Welcome}} to
{{GENDER:$1|your}} new user talk page",
"flow-guidedtour-optin-welcome-description": "{{GENDER:$1|Your}} talk
page is the place where other editors can contact {{GENDER:$1|you}}. With Flow,
it's easier to start new topics and keep track of conversations.",
"flow-guidedtour-optin-find-old-conversations": "Find
{{GENDER:$1|your}} old conversations",
diff --git a/i18n/qqq.json b/i18n/qqq.json
index 9abdae5..5e83ed9 100644
--- a/i18n/qqq.json
+++ b/i18n/qqq.json
@@ -573,6 +573,10 @@
"flow-notification-enabled-on-talkpage-email-batch-body": "Email body
for notification when Flow was enabled on your talk page.\n Parameters:\n* $1 -
Title where Flow was enabled.",
"flow-beta-feature-add-archive-template-edit-summary": "Edit summary
message for the revision adding the archive template to an archived talk page.",
"flow-beta-feature-remove-archive-template-edit-summary": "Edit summary
message for the revision removing the archive template to an archived talk
page.",
+ "flow-dialog-cancelconfirm-title": "Title of the dialog that appears
when the user cancels an edit and verifies whether the user wants to discard
their edits or continue editing.",
+ "flow-dialog-cancelconfirm-message": "Title of the dialog that appears
when the user cancels an edit, asking the user if they are sure they wants to
discard their edits or continue editing.",
+ "flow-dialog-cancelconfirm-keep": "Title for the keep option in the
dialog that appears when the user cancels an edit.",
+ "flow-dialog-cancelconfirm-discard": "Title for the discard option in
the dialog that appears when the user cancels an edit.",
"flow-guidedtour-optin-welcome": "Title of the 'welcome' step in the
Flow opt-in guided tour. This message is the first to appear in the guided tour
after a user chose to convert their talk page to a Flow board.",
"flow-guidedtour-optin-welcome-description": "Description of the
'welcome' step in the flow opt-in guided tour. This message is the first to
appear in the guided tour after a user chose to convert their talk page to a
Flow board.",
"flow-guidedtour-optin-find-old-conversations": "Title of the 'find old
conversations' step in the flow opt-in guided tour that launches after the user
chose to convert their talk page to a Flow board. This message appears next to
the board description, where a link is visible to the user's old talk page.",
diff --git a/modules/flow-initialize.js b/modules/flow-initialize.js
index 677403e..8d599b9 100644
--- a/modules/flow-initialize.js
+++ b/modules/flow-initialize.js
@@ -39,6 +39,10 @@
return;
}
+ // Set up window overlay
+ $( 'body' ).append( mw.flow.ui.windowOverlay.$element );
+ mw.flow.ui.windowOverlay.$element.append(
mw.flow.ui.windowManager.$element );
+
flowBoard = mw.flow.getPrototypeMethod( 'component',
'getInstanceByElement' )( $board );
if (
diff --git a/modules/flow/ui/mw.flow.ui.CancelConfirmDialog.js
b/modules/flow/ui/mw.flow.ui.CancelConfirmDialog.js
new file mode 100644
index 0000000..42205d6
--- /dev/null
+++ b/modules/flow/ui/mw.flow.ui.CancelConfirmDialog.js
@@ -0,0 +1,46 @@
+( function ( $ ) {
+ /**
+ * Dialog for confirming with the user if they reall want to cancel
+ * the edit.
+ *
+ * @class
+ * @extends OO.ui.MessageDialog
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ */
+ mw.flow.ui.CancelConfirmDialog = function MwFlowUICancelConfirmDialog(
config ) {
+ // Parent constructor
+ mw.flow.ui.CancelConfirmDialog.parent.call( this, config );
+ };
+
+ /* Inheritance */
+
+ OO.inheritClass( mw.flow.ui.CancelConfirmDialog, OO.ui.MessageDialog );
+
+ /* Static Properties */
+
+ mw.flow.ui.CancelConfirmDialog.static.name = 'cancelconfirm';
+
+ mw.flow.ui.CancelConfirmDialog.static.verbose = true;
+
+ mw.flow.ui.CancelConfirmDialog.static.size = 'small';
+
+ mw.flow.ui.CancelConfirmDialog.static.icon = 'help';
+
+ mw.flow.ui.CancelConfirmDialog.static.title =
+ mw.msg( 'flow-dialog-cancelconfirm-title' );
+
+ mw.flow.ui.CancelConfirmDialog.static.message =
+ mw.msg( 'flow-dialog-cancelconfirm-message' );
+
+ mw.flow.ui.CancelConfirmDialog.static.actions = [
+ { action: 'discard', label: mw.msg(
'flow-dialog-cancelconfirm-discard' ), flags: [ 'primary', 'destructive' ] },
+ { action: 'keep', label: mw.msg(
'flow-dialog-cancelconfirm-keep' ), flags: 'safe' }
+ ];
+
+ /* Registration */
+
+ mw.flow.ui.windowFactory.register( mw.flow.ui.CancelConfirmDialog );
+
+}( jQuery ) );
diff --git a/modules/flow/ui/mw.flow.ui.Overlay.js
b/modules/flow/ui/mw.flow.ui.Overlay.js
new file mode 100644
index 0000000..9b86b65
--- /dev/null
+++ b/modules/flow/ui/mw.flow.ui.Overlay.js
@@ -0,0 +1,22 @@
+/**
+ * Container for content that should appear in front of everything else.
+ *
+ * @class
+ * @extends OO.ui.Element
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ */
+mw.flow.ui.Overlay = function MwFlowUiOverlay( config ) {
+ // Parent constructor
+ OO.ui.Element.call( this, config );
+
+ // Initialization
+ this.$element.addClass( 'flow-ui-overlay' );
+};
+
+/* Inheritance */
+
+OO.inheritClass( mw.flow.ui.Overlay, OO.ui.Element );
+
+mw.flow.ui.windowOverlay = new mw.flow.ui.Overlay();
diff --git a/modules/flow/ui/mw.flow.ui.js b/modules/flow/ui/mw.flow.ui.js
index 947626b..6649dee 100644
--- a/modules/flow/ui/mw.flow.ui.js
+++ b/modules/flow/ui/mw.flow.ui.js
@@ -1 +1,5 @@
mw.flow.ui = {};
+
+mw.flow.ui.windowFactory = new OO.Factory();
+mw.flow.ui.windowManager = new OO.ui.WindowManager( { factory:
mw.flow.ui.windowFactory } );
+
diff --git
a/modules/flow/ui/widgets/editor/editors/mw.flow.ui.AbstractEditorWidget.js
b/modules/flow/ui/widgets/editor/editors/mw.flow.ui.AbstractEditorWidget.js
index c25204a..b68da8f 100644
--- a/modules/flow/ui/widgets/editor/editors/mw.flow.ui.AbstractEditorWidget.js
+++ b/modules/flow/ui/widgets/editor/editors/mw.flow.ui.AbstractEditorWidget.js
@@ -26,6 +26,8 @@
mw.flow.ui.AbstractEditorWidget = function
mwFlowUiAbstractEditorWidget( config ) {
config = config || {};
+ this.initialContent = null;
+
// Parent constructor
mw.flow.ui.AbstractEditorWidget.parent.call( this, config );
};
@@ -116,11 +118,13 @@
/**
* Change the content of the editor.
*
- * @abstract
* @method
* @param {string} content New content
*/
- mw.flow.ui.AbstractEditorWidget.prototype.setContent = null;
+ mw.flow.ui.AbstractEditorWidget.prototype.setContent = function (
content ) {
+ // Cache content for comparison
+ this.initialContent = content;
+ };
/**
* Focus on the editor.
@@ -170,4 +174,13 @@
mw.flow.ui.AbstractEditorWidget.prototype.isEmpty = function () {
return !this.getContent();
};
+
+ /**
+ * Check if the information in the editor changed
+ * @return {boolean} Information has changed
+ */
+ mw.flow.ui.AbstractEditorWidget.prototype.hasBeenChanged = function () {
+ return this.initialContent !== this.getContent();
+ };
+
}( jQuery ) );
diff --git
a/modules/flow/ui/widgets/editor/editors/mw.flow.ui.VisualEditorWidget.js
b/modules/flow/ui/widgets/editor/editors/mw.flow.ui.VisualEditorWidget.js
index 0e28cfa..06df41e 100644
--- a/modules/flow/ui/widgets/editor/editors/mw.flow.ui.VisualEditorWidget.js
+++ b/modules/flow/ui/widgets/editor/editors/mw.flow.ui.VisualEditorWidget.js
@@ -185,6 +185,20 @@
return
!this.target.getSurface().getModel().getDocument().data.hasContent();
};
+ /**
+ * Check if there are any changes made to the data in the editor
+ *
+ * @return {boolean} The original content has changed
+ */
+ mw.flow.ui.VisualEditorWidget.prototype.hasBeenChanged = function () {
+ // If we haven't fully loaded yet, just return false
+ if ( !this.target ) {
+ return false;
+ }
+
+ return this.target.getSurface().getModel().hasBeenModified();
+ };
+
mw.flow.ui.VisualEditorWidget.prototype.setDisabled = function (
disabled ) {
// Parent method
mw.flow.ui.VisualEditorWidget.parent.prototype.setDisabled.call( this, disabled
);
diff --git
a/modules/flow/ui/widgets/editor/editors/mw.flow.ui.WikitextEditorWidget.js
b/modules/flow/ui/widgets/editor/editors/mw.flow.ui.WikitextEditorWidget.js
index e785abf..a30a442 100644
--- a/modules/flow/ui/widgets/editor/editors/mw.flow.ui.WikitextEditorWidget.js
+++ b/modules/flow/ui/widgets/editor/editors/mw.flow.ui.WikitextEditorWidget.js
@@ -135,6 +135,9 @@
* @inheritdoc
*/
mw.flow.ui.WikitextEditorWidget.prototype.setContent = function (
content ) {
+ // Parent method
+
mw.flow.ui.WikitextEditorWidget.parent.prototype.setContent.call( this, content
);
+
this.input.setValue( content );
};
@@ -151,7 +154,7 @@
* @inheritdoc
*/
mw.flow.ui.WikitextEditorWidget.prototype.setup = function ( content ) {
- this.input.setValue( content );
+ this.setContent( content );
return $.Deferred().resolve().promise();
};
diff --git a/modules/flow/ui/widgets/editor/mw.flow.ui.EditorSwitcherWidget.js
b/modules/flow/ui/widgets/editor/mw.flow.ui.EditorSwitcherWidget.js
index 3b9f929..e9844c8 100644
--- a/modules/flow/ui/widgets/editor/mw.flow.ui.EditorSwitcherWidget.js
+++ b/modules/flow/ui/widgets/editor/mw.flow.ui.EditorSwitcherWidget.js
@@ -563,6 +563,15 @@
};
/**
+ * Check if there are any changes made to the data in the editor
+ *
+ * @return {boolean} The original content has changed
+ */
+ mw.flow.ui.EditorSwitcherWidget.prototype.hasBeenChanged = function () {
+ var editor = this.getActiveEditor();
+ return editor && editor.hasBeenChanged();
+ };
+ /**
* Destroy the widget
*/
mw.flow.ui.EditorSwitcherWidget.prototype.destroy = function () {
diff --git a/modules/flow/ui/widgets/editor/mw.flow.ui.EditorWidget.js
b/modules/flow/ui/widgets/editor/mw.flow.ui.EditorWidget.js
index a9ac542..00e6965 100644
--- a/modules/flow/ui/widgets/editor/mw.flow.ui.EditorWidget.js
+++ b/modules/flow/ui/widgets/editor/mw.flow.ui.EditorWidget.js
@@ -17,6 +17,8 @@
* @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.
*/
mw.flow.ui.EditorWidget = function mwFlowUiEditorWidget( config ) {
var widget = this;
@@ -30,6 +32,7 @@
OO.ui.mixin.PendingElement.call( this, config );
this.initialEditor = config.editor;
+ this.confirmCancel = !!config.confirmCancel ||
config.cancelOnEscape === undefined;
this.editorControlsWidget = new
mw.flow.ui.EditorControlsWidget( {
termsMsgKey: config.termsMsgKey ||
'flow-terms-of-use-edit',
@@ -52,14 +55,14 @@
change: [ 'emit', 'change' ]
} );
this.editorControlsWidget.connect( this, {
- cancel: [ 'emit', 'cancel' ],
+ cancel: 'onEditorControlsWidgetCancel',
save: 'onEditorControlsWidgetSave'
} );
if ( config.cancelOnEscape || config.cancelOnEscape ===
undefined ) {
this.$element.on( 'keydown', function ( e ) {
if ( e.which === OO.ui.Keys.ESCAPE ) {
- widget.emit( 'cancel' );
+ widget.onEditorControlsWidgetCancel();
e.preventDefault();
e.stopPropagation();
}
@@ -112,6 +115,30 @@
// 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.emit( 'cancel' );
+ }
+ } );
+ } );
+ } else {
+ 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
diff --git a/modules/styles/flow/mw.flow.ui.Overlay.less
b/modules/styles/flow/mw.flow.ui.Overlay.less
new file mode 100644
index 0000000..060ccfe
--- /dev/null
+++ b/modules/styles/flow/mw.flow.ui.Overlay.less
@@ -0,0 +1,12 @@
+.flow-ui-overlay {
+ font-family: sans-serif;
+ position: absolute;
+ top: 0;
+ right: 0;
+ left: 0;
+ z-index: 1;
+}
+
+.flow-ui-overlay > * {
+ z-index: 1;
+}
--
To view, visit https://gerrit.wikimedia.org/r/235383
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: Ib2977892e413468262c2be76e89678aad4737ecd
Gerrit-PatchSet: 7
Gerrit-Project: mediawiki/extensions/Flow
Gerrit-Branch: master
Gerrit-Owner: Mooeypoo <[email protected]>
Gerrit-Reviewer: Catrope <[email protected]>
Gerrit-Reviewer: Mooeypoo <[email protected]>
Gerrit-Reviewer: Siebrand <[email protected]>
Gerrit-Reviewer: jenkins-bot <>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits