diff --git a/app/assets/javascripts/discourse/models/composer.js.es6 b/app/assets/javascripts/discourse/models/composer.js.es6 index a86e48de2d3..aae9a31055f 100644 --- a/app/assets/javascripts/discourse/models/composer.js.es6 +++ b/app/assets/javascripts/discourse/models/composer.js.es6 @@ -387,9 +387,14 @@ const Composer = RestModel.extend({ return SAVE_ICONS[action]; }, - @computed("action", "whisper") - saveLabel(action, whisper) { - return whisper ? "composer.create_whisper" : SAVE_LABELS[action]; + @computed("action", "whisper", "editConflict") + saveLabel(action, whisper, editConflict) { + if (editConflict) { + return "composer.overwrite_edit"; + } else if (whisper) { + return "composer.create_whisper"; + } + return SAVE_LABELS[action]; }, hasMetaData: function() { @@ -727,7 +732,8 @@ const Composer = RestModel.extend({ composerOpened: null, composerTotalOpened: 0, featuredLink: null, - noBump: false + noBump: false, + editConflict: false }); }, @@ -762,6 +768,7 @@ const Composer = RestModel.extend({ const props = { raw: this.get("reply"), + raw_old: this.get("editConflict") ? null : this.get("originalText"), edit_reason: opts.editReason, image_sizes: opts.imageSizes, cooked: this.getCookedHtml() @@ -769,9 +776,12 @@ const Composer = RestModel.extend({ this.set("composeState", SAVING); - let rollback = throwAjaxError(() => { + let rollback = throwAjaxError(error => { post.set("cooked", oldCooked); this.set("composeState", OPEN); + if (error.jqXHR && error.jqXHR.status === 409) { + this.set("editConflict", true); + } }); return promise diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index eb30dd808cb..640679f6550 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -200,7 +200,7 @@ class PostsController < ApplicationController post.image_sizes = params[:image_sizes] if params[:image_sizes].present? if !guardian.send("can_edit?", post) && post.user_id == current_user.id && post.edit_time_limit_expired? - return render json: { errors: [I18n.t('too_late_to_edit')] }, status: 422 + return render_json_error(I18n.t('too_late_to_edit')) end guardian.ensure_can_edit!(post) @@ -210,6 +210,11 @@ class PostsController < ApplicationController edit_reason: params[:post][:edit_reason] } + raw_old = params[:post][:raw_old] + if raw_old.present? && raw_old != post.raw + return render_json_error(I18n.t('edit_conflict'), status: 409) + end + # to stay consistent with the create api, we allow for title & category changes here if post.is_first_post? changes[:title] = params[:title] if params[:title] diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 7eb98f0e672..918e12d8b0f 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -1372,6 +1372,7 @@ en: tags_missing: "You must choose at least {{count}} tags" save_edit: "Save Edit" + overwrite_edit: "Overwrite Edit" reply_original: "Reply on Original Topic" reply_here: "Reply Here" reply: "Reply" diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index adadf4da713..1b841ad84c3 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -304,6 +304,7 @@ en: badge: "%{display_name} badge on %{site_title}" too_late_to_edit: "That post was created too long ago. It can no longer be edited or deleted." + edit_conflict: "That post was edited by another user and your changes can no longer be saved." revert_version_same: "The current version is same as the version you are trying to revert to." excerpt_image: "image" diff --git a/spec/requests/posts_controller_spec.rb b/spec/requests/posts_controller_spec.rb index 57ffcea60bc..fcf74331fbe 100644 --- a/spec/requests/posts_controller_spec.rb +++ b/spec/requests/posts_controller_spec.rb @@ -295,6 +295,13 @@ describe PostsController do expect(post.raw).to eq("edited body") end + it 'checks for an edit conflict' do + update_params[:post][:raw_old] = 'old body' + put "/posts/#{post.id}.json", params: update_params + + expect(response.status).to eq(409) + end + it "raises an error when the post parameter is missing" do update_params.delete(:post) put "/posts/#{post.id}.json", params: update_params diff --git a/test/javascripts/acceptance/composer-edit-conflict-test.js.es6 b/test/javascripts/acceptance/composer-edit-conflict-test.js.es6 new file mode 100644 index 00000000000..3dc48b8f1da --- /dev/null +++ b/test/javascripts/acceptance/composer-edit-conflict-test.js.es6 @@ -0,0 +1,25 @@ +import { acceptance } from "helpers/qunit-helpers"; + +acceptance("Composer - Edit conflict", { + loggedIn: true, + + pretend(server, helper) { + server.put("/posts/398", () => { + return helper.response(409, { errors: ["edit conflict"] }); + }); + } +}); + +QUnit.test("Edit a post that causes an edit conflict", async assert => { + await visit("/t/internationalization-localization/280"); + await click(".topic-post:eq(0) button.show-more-actions"); + await click(".topic-post:eq(0) button.edit"); + await click("#reply-control button.create"); + assert.equal( + find("#reply-control button.create") + .text() + .trim(), + I18n.t("composer.overwrite_edit"), + "it shows the overwrite button" + ); +});