FEATURE: Stage post edits immediately (#13249)

FEATURE: Stage post edits immediately (#13249)

After editing a post, it is refreshed by two ways. One of them is triggered by the client side which will route the client to the edited post and force a reload this way. The other way is via Message Bus.

This commit ignores both of the ways and tries to update the post immediately and then refresh the post stream.

diff --git a/app/assets/javascripts/discourse/app/lib/transform-post.js b/app/assets/javascripts/discourse/app/lib/transform-post.js
index 44dbe1d..b2d5e01 100644
--- a/app/assets/javascripts/discourse/app/lib/transform-post.js
+++ b/app/assets/javascripts/discourse/app/lib/transform-post.js
@@ -148,6 +148,7 @@ export default function transformPost(
   postAtts.actionCodeWho = post.action_code_who;
   postAtts.topicUrl = topic.get("url");
   postAtts.isSaving = post.isSaving;
+  postAtts.staged = post.staged;
 
   if (post.notice) {
     postAtts.notice = post.notice;
diff --git a/app/assets/javascripts/discourse/app/models/composer.js b/app/assets/javascripts/discourse/app/models/composer.js
index 9851d48..5fee773 100644
--- a/app/assets/javascripts/discourse/app/models/composer.js
+++ b/app/assets/javascripts/discourse/app/models/composer.js
@@ -961,23 +961,29 @@ const Composer = RestModel.extend({
     this.set("composeState", SAVING);
 
     const rollback = throwAjaxError((error) => {
-      post.set("cooked", oldCooked);
+      post.setProperties({ cooked: oldCooked, staged: false });
+      this.appEvents.trigger("post-stream:refresh", { id: post.id });
+
       this.set("composeState", OPEN);
       if (error.jqXHR && error.jqXHR.status === 409) {
         this.set("editConflict", true);
       }
     });
 
+    post.setProperties({ cooked: props.cooked, staged: true });
+    this.appEvents.trigger("post-stream:refresh", { id: post.id });
+
     return promise
       .then(() => {
-        // rest model only sets props after it is saved
-        post.set("cooked", props.cooked);
         return post.save(props).then((result) => {
           this.clearState();
           return result;
         });
       })
-      .catch(rollback);
+      .catch(rollback)
+      .finally(() => {
+        post.set("staged", false);
+      });
   },
 
   serialize(serializer, dest) {
diff --git a/app/assets/javascripts/discourse/app/widgets/post.js b/app/assets/javascripts/discourse/app/widgets/post.js
index c2e2bfd..c1330f4 100644
--- a/app/assets/javascripts/discourse/app/widgets/post.js
+++ b/app/assets/javascripts/discourse/app/widgets/post.js
@@ -735,7 +735,7 @@ export default createWidget("post", {
     }
     const classNames = ["topic-post", "clearfix"];
 
-    if (attrs.id === -1 || attrs.isSaving) {
+    if (attrs.id === -1 || attrs.isSaving || attrs.staged) {
       classNames.push("staged");
     }
     if (attrs.selected) {
diff --git a/app/assets/javascripts/discourse/tests/acceptance/composer-test.js b/app/assets/javascripts/discourse/tests/acceptance/composer-test.js
index 45035e0..cd0bb88 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/composer-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/composer-test.js
@@ -346,6 +346,43 @@ acceptance("Composer", function (needs) {
     );
   });
 
+  test("Editing a post stages new content", async function (assert) {
+    await visit("/t/internationalization-localization/280");
+    await click(".topic-post:nth-of-type(1) button.show-more-actions");
+    await click(".topic-post:nth-of-type(1) button.edit");
+
+    await fillIn(".d-editor-input", "will return empty json");
+    await fillIn("#reply-title", "This is the new text for the title");
+    await click("#reply-control button.create");
+
+    assert.equal(find(".topic-post.staged").length, 1);
+    assert.ok(
+      find(".topic-post:nth-of-type(1)")[0].className.includes("staged")
+    );
+    assert.equal(
+      find(".topic-post.staged .cooked").text().trim(),
+      "will return empty json"
+    );
+  });
+
+  test("Editing a post can rollback to old content", async function (assert) {
+    await visit("/t/internationalization-localization/280");
+    await click(".topic-post:nth-of-type(1) button.show-more-actions");
+    await click(".topic-post:nth-of-type(1) button.edit");
+
+    await fillIn(".d-editor-input", "this will 409");
+    await fillIn("#reply-title", "This is the new text for the title");
+    await click("#reply-control button.create");
+
+    assert.equal(find(".topic-post.staged").length, 0);
+    assert.equal(
+      find(".topic-post .cooked")[0].innerText,
+      "Any plans to support localization of UI elements, so that I (for example) could set up a completely German speaking forum?"
+    );
+
+    await click(".bootbox.modal .btn-primary");
+  });
+
   test("Composer can switch between edits", async function (assert) {
     await visit("/t/this-is-a-test-topic/9");
 
diff --git a/app/assets/javascripts/discourse/tests/helpers/create-pretender.js b/app/assets/javascripts/discourse/tests/helpers/create-pretender.js
index 3398721..0276932 100644
--- a/app/assets/javascripts/discourse/tests/helpers/create-pretender.js
+++ b/app/assets/javascripts/discourse/tests/helpers/create-pretender.js
@@ -479,6 +479,8 @@ export function applyDefaultHandlers(pretender) {
     const data = parsePostData(request.requestBody);
     if (data.post.raw === "this will 409") {
       return response(409, { errors: ["edit conflict"] });
+    } else if (data.post.raw === "will return empty json") {
+      return response(200, {});
     }
     data.post.id = request.params.post_id;
     data.post.version = 2;

GitHub sha: e81a5182

This commit appears in #13249 which was approved by eviltrout and ZogStriP. It was merged by nbianca.