jenkins-bot has submitted this change and it was merged.

Change subject: Edit conflicts
......................................................................


Edit conflicts

Done:
* Header: Non-JS
* Header: JS
* Title: Non-JS
* Title: JS
* Post: Non-JS
* Post: JS

Note: there's some duplication of tipsy code. Not sure how I feel about that.

Change-Id: I70fc7384eb0fb9929db0ad478dc624a22f7fe6a5
---
M Resources.php
M includes/Block/Header.php
M includes/Block/Topic.php
M modules/base/action.js
M modules/base/ext.flow.base.js
M modules/base/styles/actionbox.less
M modules/base/ui-functions.js
M modules/discussion/post.js
M modules/discussion/topic.js
M modules/header/forms.js
M templates/edit-header.html.php
M templates/edit-post.html.php
M templates/edit-title.html.php
13 files changed, 368 insertions(+), 29 deletions(-)

Approvals:
  EBernhardson: Looks good to me, approved
  jenkins-bot: Verified



diff --git a/Resources.php b/Resources.php
index c3f97e4..1f7d3e5 100644
--- a/Resources.php
+++ b/Resources.php
@@ -33,6 +33,7 @@
                        'mediawiki.ui',
                        'mediawiki.api',
                        'jquery.json',
+                       'jquery.tipsy',
                ),
                'messages' => array(
                        'flow-preview',
@@ -48,6 +49,8 @@
                ),
                'messages' => array(
                        'flow-error-other',
+                       'flow-edit-header-submit',
+                       'flow-edit-header-submit-overwrite',
                ),
        ),
        'ext.flow.discussion' => $flowResourceTemplate + array(
@@ -87,10 +90,11 @@
                        'flow-error-external',
                        'flow-error-external-multi',
                        'flow-edit-title-submit',
+                       'flow-edit-title-submit-overwrite',
                        'flow-edit-post-submit',
+                       'flow-edit-post-submit-overwrite',
                        'flow-paging-fwd',
                        'flow-paging-rev',
-                       'flow-edit-header-submit',
                        'flow-post-moderated-toggle-show',
                        'flow-post-moderated-toggle-hide',
                        'flow-terms-of-use-edit',
diff --git a/includes/Block/Header.php b/includes/Block/Header.php
index 3786b07..eda5f82 100644
--- a/includes/Block/Header.php
+++ b/includes/Block/Header.php
@@ -76,7 +76,8 @@
                                // handing user back to specific dialog 
indicating race condition
                                $this->addError(
                                        'prev_revision',
-                                       wfMessage( 
'flow-error-prev-revision-mismatch' )->params( 
$this->submitted['prev_revision'], $this->header->getRevisionId()->getHex() )
+                                       wfMessage( 
'flow-error-prev-revision-mismatch' )->params( 
$this->submitted['prev_revision'], $this->header->getRevisionId()->getHex() ),
+                                       array( 'revision_id' => 
$this->header->getRevisionId()->getHex() ) // save current revision ID
                                );
                        }
 
diff --git a/includes/Block/Topic.php b/includes/Block/Topic.php
index f81b43c..2796220 100644
--- a/includes/Block/Topic.php
+++ b/includes/Block/Topic.php
@@ -130,14 +130,30 @@
                if ( $len > PostRevision::MAX_TOPIC_LENGTH ) {
                        $this->addError( 'content', wfMessage( 
'flow-error-title-too-long', PostRevision::MAX_TOPIC_LENGTH ) );
                        return;
-               }
-               $topicTitle = $this->loadTopicTitle();
-               if ( !$topicTitle ) {
-                       throw new InvalidInputException( 'No revision 
associated with workflow?', 'missing-revision' );
-               }
-               if ( !$this->permissions->isAllowed( $topicTitle, 'edit-title' 
) ) {
-                       $this->addError( 'permissions', wfMessage( 
'flow-error-not-allowed' ) );
+               } elseif ( empty( $this->submitted['prev_revision'] ) ) {
+                       $this->addError( 'prev_revision', wfMessage( 
'flow-error-missing-prev-revision-identifier' ) );
                        return;
+               } else {
+                       $topicTitle = $this->loadTopicTitle();
+                       if ( !$topicTitle ) {
+                               throw new InvalidInputException( 'No revision 
associated with workflow?', 'missing-revision' );
+                       }
+                       if ( !$this->permissions->isAllowed( $topicTitle, 
'edit-title' ) ) {
+                               $this->addError( 'permissions', wfMessage( 
'flow-error-not-allowed' ) );
+                               return;
+                       } elseif ( $topicTitle->getRevisionId()->getHex() !== 
$this->submitted['prev_revision'] ) {
+                               // This is a reasonably effective way to ensure 
prev revision matches, but for guarantees against race
+                               // conditions there also exists a unique index 
on rev_prev_revision in mysql, meaning if someone else inserts against the
+                               // parent we and the submitter think is the 
latest, our insert will fail.
+                               // TODO: Catch whatever exception happens 
there, make sure the most recent revision is the one in the cache before
+                               // handing user back to specific dialog 
indicating race condition
+                               $this->addError(
+                                       'prev_revision',
+                                       wfMessage( 
'flow-error-prev-revision-mismatch' )->params( 
$this->submitted['prev_revision'], $topicTitle->getRevisionId()->getHex() ),
+                                       array( 'revision_id' => 
$topicTitle->getRevisionId()->getHex() ) // save current revision ID
+                               );
+                               return;
+                       }
                }
 
                $this->newRevision = $topicTitle->newNextRevision( $this->user, 
$this->submitted['content'], 'edit-title' );
@@ -174,6 +190,7 @@
                        return; // loadRequestedPost adds its own errors
                } elseif ( !$this->permissions->isAllowed( $post, 'reply' ) ) {
                        $this->addError( 'permissions', wfMessage( 
'flow-error-not-allowed' ) );
+                       return;
                } else {
                        $this->newRevision = $post->reply( $this->user, 
$this->submitted['content'] );
 
@@ -293,9 +310,11 @@
                if ( empty( $this->submitted['postId'] ) ) {
                        $this->addError( 'post', wfMessage( 
'flow-error-missing-postId' ) );
                        return;
-               }
-               if ( empty( $this->submitted['content'] ) ) {
+               } elseif ( empty( $this->submitted['content'] ) ) {
                        $this->addError( 'content', wfMessage( 
'flow-error-missing-content' ) );
+                       return;
+               } elseif ( empty( $this->submitted['prev_revision'] ) ) {
+                       $this->addError( 'prev_revision', wfMessage( 
'flow-error-missing-prev-revision-identifier' ) );
                        return;
                }
                $post = $this->loadRequestedPost( $this->submitted['postId'] );
@@ -304,6 +323,18 @@
                }
                if ( !$this->permissions->isAllowed( $post, 'edit-post' ) ) {
                        $this->addError( 'permissions', wfMessage( 
'flow-error-not-allowed' ) );
+                       return;
+               } elseif ( $post->getRevisionId()->getHex() !== 
$this->submitted['prev_revision'] ) {
+                       // This is a reasonably effective way to ensure prev 
revision matches, but for guarantees against race
+                       // conditions there also exists a unique index on 
rev_prev_revision in mysql, meaning if someone else inserts against the
+                       // parent we and the submitter think is the latest, our 
insert will fail.
+                       // TODO: Catch whatever exception happens there, make 
sure the most recent revision is the one in the cache before
+                       // handing user back to specific dialog indicating race 
condition
+                       $this->addError(
+                               'prev_revision',
+                               wfMessage( 'flow-error-prev-revision-mismatch' 
)->params( $this->submitted['prev_revision'], $post->getRevisionId()->getHex() 
),
+                               array( 'revision_id' => 
$post->getRevisionId()->getHex() ) // save current revision ID
+                       );
                        return;
                }
 
@@ -471,7 +502,7 @@
                        return $prefix . $templating->render( 
"flow:edit-title.html.php", array(
                                'block' => $this,
                                'topic' => $this->workflow,
-                               'topicTitle' => $topicTitle,
+                               'topicTitle' => $this->newRevision ?: 
$topicTitle, // if already submitted, use submitted revision,
                        ), $return );
 
                case 'compare-revisions':
@@ -638,7 +669,7 @@
                return $templating->render( "flow:edit-post.html.php", array(
                        'block' => $this,
                        'topic' => $this->workflow,
-                       'post' => $post,
+                       'post' => $this->newRevision ?: $post, // if already 
submitted, use submitted revision
                ), $return );
        }
 
@@ -727,6 +758,7 @@
 
                $output = array();
                $output['post-id'] = $post->getPostId()->getHex();
+               $output['revision-id'] = $post->getRevisionId()->getHex();
                $contentFormat = $post->getContentFormat();
 
                // This may force a round trip through parsoid for the wikitext 
when
diff --git a/modules/base/action.js b/modules/base/action.js
index 7743206..8c29ed8 100644
--- a/modules/base/action.js
+++ b/modules/base/action.js
@@ -15,4 +15,44 @@
        mw.flow.action.prototype.showError = function ( error, errorData ) {
                $( this.topic.$container ).flow( 'showError', arguments );
        };
+
+       /**
+        * Adds tipsy to an element, with the given text.
+        *
+        * @param {jQuery} $element
+        * @param {string} text
+        */
+       mw.flow.action.prototype.tipsy = function ( $element, text ) {
+               $element
+                       .click( function () {
+                               $( this ).tipsy( 'hide' );
+                       } )
+                       .tipsy( {
+                               fade: true,
+                               gravity: 'w',
+                               html: true,
+                               trigger: 'manual',
+                               className: 'flow-tipsy-destructive',
+                               title: function () {
+                                       /*
+                                        * I'd prefer to only return content 
here, instead of wrapping
+                                        * it in a div. But we need to add some 
padding inside the tipsy.
+                                        * Tipsy has an option "className", 
which we could use to target
+                                        * the element though CSS, but that 
className is only applied
+                                        * _after_ tipsy has calculated 
position, so it's positioning
+                                        * would then be incorrect.
+                                        * Tossing in the content inside 
another div (which does have a
+                                        * class to target) works around this 
problem.
+                                        *
+                                        * @see 
https://gerrit.wikimedia.org/r/#/c/103531/
+                                        */
+
+                                       // .html() only returns inner html, so 
attach the node to a new
+                                       // parent & grab the full html there
+                                       var $warning = $( '<div 
class="flow-tipsy-noflyout">' ).text( text );
+                                       return $( '<div>' ).append( $warning 
).html();
+                               }
+                       } )
+                       .tipsy( 'show' );
+       };
 } ( jQuery, mediaWiki ) );
diff --git a/modules/base/ext.flow.base.js b/modules/base/ext.flow.base.js
index e019ff3..f4656db 100644
--- a/modules/base/ext.flow.base.js
+++ b/modules/base/ext.flow.base.js
@@ -244,7 +244,8 @@
 mw.flow.api.changeTitle = mw.flow.api.generateTopicAction(
        'edit-title',
        [
-               'content'
+               'content',
+               'prev_revision'
        ]
 );
 
@@ -256,7 +257,8 @@
        'edit-post',
        [
                'postId',
-               'content'
+               'content',
+               'prev_revision'
        ]
 );
 
diff --git a/modules/base/styles/actionbox.less 
b/modules/base/styles/actionbox.less
index ddb7944..e2f0a45 100644
--- a/modules/base/styles/actionbox.less
+++ b/modules/base/styles/actionbox.less
@@ -93,6 +93,10 @@
                border: none;
        }
 
+       .flow-tipsy-noflyout {
+               padding: 10px 22px;
+       }
+
        // overwrite tipsy arrow
        .tipsy-arrow {
                // overwrite core bg image
diff --git a/modules/base/ui-functions.js b/modules/base/ui-functions.js
index 818405f..653fc74 100644
--- a/modules/base/ui-functions.js
+++ b/modules/base/ui-functions.js
@@ -85,7 +85,7 @@
                         * An object, with the keys 'content' and 'format'. Or 
a plain string of wikitext.
                         * @param  {function} submitFunction      Function to 
call in order to submit the form.
                         * One parameter, the content.
-                        * @param {function} loadFunction         Function to 
call once the form is loaded.
+                        * @param  {function} loadFunction        Function to 
call once the form is loaded.
                         * @return {Promise}                      A promise 
that will be resolved or rejected
                         * when the form submission has returned.
                         */
diff --git a/modules/discussion/post.js b/modules/discussion/post.js
index a74f888..ecd5fae 100644
--- a/modules/discussion/post.js
+++ b/modules/discussion/post.js
@@ -166,7 +166,7 @@
                );
 
                deferred.done( $.proxy( this.render, this ) );
-//             deferred.fail( $.proxy( this.conflict, this, deferred ) ); // 
@todo: not yet implemented
+               deferred.fail( $.proxy( this.conflict, this, deferred, data ) );
 
                return deferred;
        };
@@ -186,6 +186,65 @@
        };
 
        /**
+        * Called when submitFunction failed.
+        *
+        * @param {jQuery.Deferred} deferred
+        * @param {object} data Old (invalid) this.prepareResult return value
+        * @param {string} error
+        * @param {object} errorData
+        */
+       mw.flow.action.post.edit.prototype.conflict = function ( deferred, 
data, error, errorData ) {
+               if (
+                       error === 'block-errors' &&
+                       errorData.topic && errorData.topic.prev_revision &&
+                       errorData.topic.prev_revision.extra && 
errorData.topic.prev_revision.extra.revision_id
+               ) {
+                       var $textarea = this.post.$container.find( 'textarea' );
+
+                       /*
+                        * Overwrite data revision & content.
+                        * We'll use raw editor content & editor format to 
avoid having
+                        * to parse it.
+                        */
+                       data.content = mw.flow.editor.getRawContent( $textarea 
);
+                       data.format = mw.flow.editor.getFormat( $textarea );
+                       data.revision = 
errorData.topic.prev_revision.extra.revision_id;
+
+                       /*
+                        * At this point, we're still in the deferred's reject 
callbacks.
+                        * Only after these are completed, is the spinner 
removed and the
+                        * error message added.
+                        * I'm adding another fail-callback, which will be 
executed after
+                        * the fail has been handled. Only then, we can 
properly clean up.
+                        */
+                       deferred.fail( $.proxy( function ( data, error, 
errorData ) {
+                               /*
+                                * Tipsy will be positioned at the element 
where it's bound
+                                * to, at the time it's asked to show. It won't 
reposition
+                                * if the element moves. Since we re-launch the 
form, there
+                                * may be some movement, so let's have this as 
callback when
+                                * the form has completed loading before doing 
these changes.
+                                */
+                               var formLoaded = $.proxy( function () {
+                                       var $button = 
this.post.$container.find( '.flow-edit-post-submit' );
+                                       $button.val( mw.msg( 
'flow-edit-post-submit-overwrite' ) );
+                                       this.tipsy( $button, 
errorData.topic.prev_revision.message );
+
+                                       /*
+                                        * Trigger keyup in editor, to trick 
setupEmptyDisabler
+                                        * into believing we've made a change & 
enable submit.
+                                        */
+                                       this.post.$container.find( 'textarea' 
).keyup();
+                               }, this, data, error, errorData );
+
+                               // kill form & error message & re-launch edit 
form
+                               this.post.$container.find( 'form, .flow-error' 
).remove();
+                               this.setupEditForm( data, formLoaded );
+                       }, this, data, error, errorData ) );
+               }
+       };
+
+       /**
         * Display an error if something when wrong.
         *
         * @param {string} error
diff --git a/modules/discussion/topic.js b/modules/discussion/topic.js
index 91623a8..742303d 100644
--- a/modules/discussion/topic.js
+++ b/modules/discussion/topic.js
@@ -171,7 +171,7 @@
                );
 
                deferred.done( $.proxy( this.render, this ) );
-//             deferred.fail( $.proxy( this.conflict, this, deferred ) ); // 
@todo: not yet implemented
+               deferred.fail( $.proxy( this.conflict, this, deferred, data ) );
 
                return deferred;
        };
@@ -187,6 +187,65 @@
        };
 
        /**
+        * Called when submitFunction failed.
+        *
+        * @param {jQuery.Deferred} deferred
+        * @param {object} data Old (invalid) this.prepareResult return value
+        * @param {string} error
+        * @param {object} errorData
+        */
+       mw.flow.action.topic.edit.prototype.conflict = function ( deferred, 
data, error, errorData ) {
+               if (
+                       error === 'block-errors' &&
+                       errorData.topic && errorData.topic.prev_revision &&
+                       errorData.topic.prev_revision.extra && 
errorData.topic.prev_revision.extra.revision_id
+               ) {
+                       var $input = this.topic.$container.find( 'input' );
+
+                       /*
+                        * Overwrite data revision & content.
+                        * We'll use raw editor content & editor format to 
avoid having
+                        * to parse it.
+                        */
+                       data.content = $input.val();
+                       data.format = 'wikitext';
+                       data.revision = 
errorData.topic.prev_revision.extra.revision_id;
+
+                       /*
+                        * At this point, we're still in the deferred's reject 
callbacks.
+                        * Only after these are completed, is the spinner 
removed and the
+                        * error message added.
+                        * I'm adding another fail-callback, which will be 
executed after
+                        * the fail has been handled. Only then, we can 
properly clean up.
+                        */
+                       deferred.fail( $.proxy( function ( data, error, 
errorData ) {
+                               /*
+                                * Tipsy will be positioned at the element 
where it's bound
+                                * to, at the time it's asked to show. It won't 
reposition
+                                * if the element moves. Since we re-launch the 
form, there
+                                * may be some movement, so let's have this as 
callback when
+                                * the form has completed loading before doing 
these changes.
+                                */
+                               var formLoaded = $.proxy( function () {
+                                       var $button = 
this.topic.$container.find( '.flow-edit-title-submit' );
+                                       $button.val( mw.msg( 
'flow-edit-title-submit-overwrite' ) );
+                                       this.tipsy( $button, 
errorData.topic.prev_revision.message );
+
+                                       /*
+                                        * Trigger keyup in editor, to trick 
setupEmptyDisabler
+                                        * into believing we've made a change & 
enable submit.
+                                        */
+                                       this.topic.$container.find( 'input' 
).keyup();
+                               }, this, data, error, errorData );
+
+                               // kill form & error message & re-launch edit 
form
+                               this.topic.$container.find( 'form, flow-error' 
).remove();
+                               this.setupEditForm( data, formLoaded );
+                       }, this, data, error, errorData ) );
+               }
+       };
+
+       /**
         * Parameter supplier (to submitFunction) for flow( 'setupFormHandler' 
).
         *
         * @return {array}
diff --git a/modules/header/forms.js b/modules/header/forms.js
index 353c31c..f80fb8e 100644
--- a/modules/header/forms.js
+++ b/modules/header/forms.js
@@ -157,8 +157,7 @@
                        data.revision
                );
 
-               deferred.done( $.proxy( this.render, this ) );
-//             deferred.fail( $.proxy( this.conflict, this, deferred ) ); // 
@todo: not yet implemented
+               deferred.fail( $.proxy( this.conflict, this, deferred, data ) );
 
                return deferred;
        };
@@ -177,6 +176,65 @@
        };
 
        /**
+        * Called when submitFunction failed.
+        *
+        * @param {jQuery.Deferred} deferred
+        * @param {object} data Old (invalid) this.prepareResult return value
+        * @param {string} error
+        * @param {object} errorData
+        */
+       mw.flow.action.header.edit.prototype.conflict = function ( deferred, 
data, error, errorData ) {
+               if (
+                       error === 'block-errors' &&
+                       errorData.header && errorData.header.prev_revision &&
+                       errorData.header.prev_revision.extra && 
errorData.header.prev_revision.extra.revision_id
+               ) {
+                       var $textarea = this.header.$container.find( 'textarea' 
);
+
+                       /*
+                        * Overwrite data revision & content.
+                        * We'll use raw editor content & editor format to 
avoid having
+                        * to parse it.
+                        */
+                       data.content = mw.flow.editor.getRawContent( $textarea 
);
+                       data.format = mw.flow.editor.getFormat( $textarea );
+                       data.revision = 
errorData.header.prev_revision.extra.revision_id;
+
+                       /*
+                        * At this point, we're still in the deferred's reject 
callbacks.
+                        * Only after these are completed, is the spinner 
removed and the
+                        * error message added.
+                        * I'm adding another fail-callback, which will be 
executed after
+                        * the fail has been handled. Only then, we can 
properly clean up.
+                        */
+                       deferred.fail( $.proxy( function ( data, error, 
errorData ) {
+                               /*
+                                * Tipsy will be positioned at the element 
where it's bound
+                                * to, at the time it's asked to show. It won't 
reposition
+                                * if the element moves. Since we re-launch the 
form, there
+                                * may be some movement, so let's have this as 
callback when
+                                * the form has completed loading before doing 
these changes.
+                                */
+                               var formLoaded = $.proxy( function () {
+                                       var $button = 
this.header.$container.find( '.flow-edit-header-submit' );
+                                       $button.val( mw.msg( 
'flow-edit-header-submit-overwrite' ) );
+                                       this.tipsy( $button, 
errorData.header.prev_revision.message );
+
+                                       /*
+                                        * Trigger keyup in editor, to trick 
setupEmptyDisabler
+                                        * into believing we've made a change & 
enable submit.
+                                        */
+                                       this.header.$container.find( 'textarea' 
).keyup();
+                               }, this, data, error, errorData );
+
+                               // kill form & error message & re-launch edit 
form
+                               this.header.$container.find( 'form, flow-error' 
).remove();
+                               this.setupEditForm( data, formLoaded );
+                       }, this, data, error, errorData ) );
+               }
+       };
+
+       /**
         * Display an error if something when wrong.
         *
         * @param {string} error
diff --git a/templates/edit-header.html.php b/templates/edit-header.html.php
index 636b38f..800d08c 100644
--- a/templates/edit-header.html.php
+++ b/templates/edit-header.html.php
@@ -1,5 +1,21 @@
 <?php
 
+$revisionId = '';
+if ( $header ) {
+       // if header already exists, propagate it's revisions id
+       $revisionId = $header->getRevisionId()->getHex();
+
+       /*
+        * If we tried to submit a change against a revision that is not the 
latest,
+        * $header will be our own change; let's get the real revision id from 
the
+        * error details.
+        */
+       if ( $block->hasErrors( 'prev_revision' ) ) {
+               $error = $block->getErrorExtra( 'prev_revision' );
+               $revisionId = $error['revision_id'];
+       }
+}
+
 // owning workflow
 echo Html::openElement( 'div', array(
        'id' => 'flow-header',
@@ -20,12 +36,17 @@
        echo '</ul>';
 }
 
-echo Html::element( 'input', array( 'type' => 'hidden', 'name' => 
'wpEditToken', 'value' => $editToken) );
+echo Html::element( 'input', array(
+       'type' => 'hidden',
+       'name' => 'wpEditToken',
+       'value' => $editToken
+) );
+
 if ( $header ) {
        echo Html::element( 'input', array(
                'type' => 'hidden',
                'name' => $block->getName()."[prev_revision]",
-               'value' => $header->getRevisionId()->getHex(),
+               'value' => $revisionId
        ) );
 }
 
@@ -35,7 +56,7 @@
        array(
                'class' => 'mw-ui-input',
                'rows' => '10',
-               'data-header-id' => $header ? 
$header->getRevisionId()->getHex() : ''
+               'data-header-id' => $revisionId
        )
 );
 echo Html::openElement( 'div', array(
@@ -44,12 +65,21 @@
 echo Html::rawElement( 'div', array(
        'class' => 'flow-terms-of-use plainlinks',
 ), wfMessage( 'flow-terms-of-use-new-topic' )->parse() );
+
+// submit button text will be different if there's a more recent change already
+$submitMessage = 'flow-edit-header-submit';
+$submitClass = 'mw-ui-button mw-ui-constructive';
+if ( $block->hasErrors( 'prev_revision' ) ) {
+       $submitMessage = 'flow-edit-header-submit-overwrite';
+       $submitClass = 'mw-ui-button mw-ui-destructive';
+}
 echo Html::element( 'input', array(
        'type' => 'submit',
-       'class' => 'mw-ui-button mw-ui-constructive',
-       'value' => wfMessage( 'flow-edit-header-submit' )->plain(),
+       'class' => $submitClass,
+       'value' => wfMessage( $submitMessage )->plain(),
 ) );
 echo Html::element( 'div', array( 'class' => 'clear' ) );
+
 echo Html::closeElement( 'div' );
 echo Html::closeElement( 'form' );
 echo Html::closeElement( 'div' );
diff --git a/templates/edit-post.html.php b/templates/edit-post.html.php
index af49e6f..9b85301 100644
--- a/templates/edit-post.html.php
+++ b/templates/edit-post.html.php
@@ -1,5 +1,25 @@
 <?php
 
+$revisionId = $post->getRevisionId()->getHex();
+
+/*
+ * If we tried to submit a change against a revision that is not the latest,
+ * $header will be our own change; let's get the real revision id from the
+ * error details.
+ */
+if ( $block->hasErrors( 'prev_revision' ) ) {
+       $error = $block->getErrorExtra( 'prev_revision' );
+       $revisionId = $error['revision_id'];
+}
+
+// submit button text will be different if there's a more recent change already
+$submitMessage = 'flow-edit-post-submit';
+$submitClass = 'mw-ui-button mw-ui-constructive';
+if ( $block->hasErrors( 'prev_revision' ) ) {
+       $submitMessage = 'flow-edit-post-submit-overwrite';
+       $submitClass = 'mw-ui-button mw-ui-destructive';
+}
+
 echo Html::openElement( 'div', array(
        'class' => 'flow-topic-container flow-topic-full'
 ) );
@@ -31,6 +51,11 @@
                'name' => $block->getName() . '[postId]',
                'value' => $post->getPostId()->getHex(),
        ) ),
+       Html::element( 'input', array(
+               'type' => 'hidden',
+               'name' => $block->getName() . '[prev_revision]',
+               'value' => $revisionId
+       ) ),
        Html::textarea(
                $block->getName() . '[content]',
                $this->getContent( $post, 'wikitext' ),
@@ -47,8 +72,8 @@
                ), wfMessage( 'flow-terms-of-use-edit' )->parse() ),
                Html::element( 'input', array(
                        'type' => 'submit',
-                       'class' => 'mw-ui-button mw-ui-constructive',
-                       'value' => wfMessage( 'flow-edit-post-submit' )->plain()
+                       'class' => $submitClass,
+                       'value' => wfMessage( $submitMessage )->plain()
                ) ),
                Html::element( 'div', array( 'class' => 'clear' ) ),
        Html::closeElement( 'div' ),
diff --git a/templates/edit-title.html.php b/templates/edit-title.html.php
index a0d89e1..62df7f8 100644
--- a/templates/edit-title.html.php
+++ b/templates/edit-title.html.php
@@ -1,5 +1,25 @@
 <?php
 
+$revisionId = $topicTitle->getRevisionId()->getHex();
+
+/*
+ * If we tried to submit a change against a revision that is not the latest,
+ * $header will be our own change; let's get the real revision id from the
+ * error details.
+ */
+if ( $block->hasErrors( 'prev_revision' ) ) {
+       $error = $block->getErrorExtra( 'prev_revision' );
+       $revisionId = $error['revision_id'];
+}
+
+// submit button text will be different if there's a more recent change already
+$submitMessage = 'flow-edit-title-submit';
+$submitClass = 'mw-ui-button mw-ui-constructive';
+if ( $block->hasErrors( 'prev_revision' ) ) {
+       $submitMessage = 'flow-edit-title-submit-overwrite';
+       $submitClass = 'mw-ui-button mw-ui-destructive';
+}
+
 echo Html::openElement( 'div', array(
        'class' => 'flow-topic-container flow-topic-full'
 ) );
@@ -20,6 +40,11 @@
 }
 
 echo Html::element( 'input', array( 'type' => 'hidden', 'name' => 
'wpEditToken', 'value' => $editToken ) ),
+       Html::element( 'input', array(
+               'type' => 'hidden',
+               'name' => $block->getName() . '[prev_revision]',
+               'value' => $revisionId
+       ) ),
        Html::element(
                'input',
                array(
@@ -38,8 +63,8 @@
                Html::element( 'input',
                        array(
                                'type' => 'submit',
-                               'value' => wfMessage( 'flow-edit-title-submit' 
)->plain(),
-                               'class' => 'mw-ui-button mw-ui-constructive',
+                               'class' => $submitClass,
+                               'value' => wfMessage( $submitMessage )->plain(),
                        )
                ),
        Html::element( 'div', array( 'class' => 'clear' ) ),

-- 
To view, visit https://gerrit.wikimedia.org/r/98836
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I70fc7384eb0fb9929db0ad478dc624a22f7fe6a5
Gerrit-PatchSet: 21
Gerrit-Project: mediawiki/extensions/Flow
Gerrit-Branch: master
Gerrit-Owner: Matthias Mullie <[email protected]>
Gerrit-Reviewer: EBernhardson <[email protected]>
Gerrit-Reviewer: Matthias Mullie <[email protected]>
Gerrit-Reviewer: SG <[email protected]>
Gerrit-Reviewer: Siebrand <[email protected]>
Gerrit-Reviewer: Werdna <[email protected]>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to