FIX: Correct scrolling issues and feedback loop with edit presence (#3)

FIX: Correct scrolling issues and feedback loop with edit presence (#3)

  • FIX: retain scrollTop when messages arrive

When we amend the textarea we could cause drift of scrollTop due to the code that maintains selection start

In practice if you place the cursor at the end of a text area then scroll to top, then someone else edits, stuff could jump around.

Also, lints file

  • FIX: correct feedback loop with edit presence

Previous to this change we had a feedback loop where any editor editing would cause other people observing the shared edit to publish state saying they were editing the topic.

This ensures that we only publish state if we actual press the keyboard

Co-authored-by: Joffrey JAFFEUX j.jaffeux@gmail.com

diff --git a/assets/javascripts/initializers/shared-edits-init.js.es6 b/assets/javascripts/initializers/shared-edits-init.js.es6
index 1206914..6449dd6 100644
--- a/assets/javascripts/initializers/shared-edits-init.js.es6
+++ b/assets/javascripts/initializers/shared-edits-init.js.es6
@@ -1,12 +1,12 @@
 import { withPluginApi } from "discourse/lib/plugin-api";
 import discourseComputed, {
   on,
-  observes
+  observes,
 } from "discourse-common/utils/decorators";
 import {
   setupSharedEdit,
   teardownSharedEdit,
-  performSharedEdit
+  performSharedEdit,
 } from "../lib/shared-edits";
 
 import { ajax } from "discourse/lib/ajax";
@@ -24,7 +24,7 @@ function initWithApi(api) {
 
   api.includePostAttributes("shared_edits_enabled");
 
-  api.addPostMenuButton("sharedEdit", post => {
+  api.addPostMenuButton("sharedEdit", (post) => {
     if (!post.shared_edits_enabled || !post.canEdit) {
       return;
     }
@@ -34,7 +34,7 @@ function initWithApi(api) {
       icon: "far-edit",
       title: "shared_edits.button_title",
       className: "shared-edit create fade-out",
-      position: "last"
+      position: "last",
     };
 
     if (!post.mobileView) {
@@ -62,7 +62,7 @@ function initWithApi(api) {
     sharedEdit() {
       const post = this.findAncestorModel();
       this.appEvents.trigger("shared-edit-on-post", post);
-    }
+    },
   });
 
   api.reopenWidget("post-admin-menu", {
@@ -80,7 +80,7 @@ function initWithApi(api) {
           className: "admin-collude",
           label: attrs.shared_edits_enabled
             ? "shared_edits.disable_shared_edits"
-            : "shared_edits.enable_shared_edits"
+            : "shared_edits.enable_shared_edits",
         })
       );
 
@@ -103,13 +103,13 @@ function initWithApi(api) {
           this.scheduleRerender();
         })
         .catch(popupAjaxError);
-    }
+    },
   });
 
   api.modifyClass("component:scrolling-post-stream", {
     sharedEdit() {
       this.appEvents.trigger("shared-edit-on-post");
-    }
+    },
   });
 
   api.modifyClass("model:composer", {
@@ -118,14 +118,35 @@ function initWithApi(api) {
     @discourseComputed("action")
     editingPost() {
       return this._super(...arguments) || this.creatingSharedEdit;
-    }
+    },
+  });
+
+  api.modifyClass("component:composer-presence-display", {
+    _typing() {
+      if (this.model.action === SHARED_EDIT_ACTION) {
+        const lastKey = this.model.lastKeyPress;
+        if (!lastKey || lastKey < Date.now() - 2000) {
+          return;
+        }
+      }
+      this._super(...arguments);
+    },
+  });
+
+  api.modifyClass("component:composer-editor", {
+    @on("keyDown")
+    _trackTyping() {
+      if (this.composer.action === SHARED_EDIT_ACTION) {
+        this.composer.set("lastKeyPress", Date.now());
+      }
+    },
   });
 
   api.modifyClass("controller:topic", {
     init() {
       this._super(...arguments);
 
-      this.appEvents.on("shared-edit-on-post", post => {
+      this.appEvents.on("shared-edit-on-post", (post) => {
         const draftKey = post.get("topic.draft_key");
         const draftSequence = post.get("topic.draft_sequence");
 
@@ -133,7 +154,7 @@ function initWithApi(api) {
           post,
           action: SHARED_EDIT_ACTION,
           draftKey,
-          draftSequence
+          draftSequence,
         });
       });
     },
@@ -141,7 +162,7 @@ function initWithApi(api) {
     willDestroy() {
       this.appEvents.off("shared-edit-on-post", this);
       this._super(...arguments);
-    }
+    },
   });
 
   api.modifyClass("controller:composer", {
@@ -196,7 +217,7 @@ function initWithApi(api) {
         return;
       }
       return this._super();
-    }
+    },
   });
 }
 
@@ -204,5 +225,5 @@ export default {
   name: "discourse-shared-edits",
   initialize: () => {
     withPluginApi("0.8.6", initWithApi);
-  }
+  },
 };
diff --git a/assets/javascripts/lib/shared-edits.js.es6 b/assets/javascripts/lib/shared-edits.js.es6
index b7bf46c..f620f7d 100644
--- a/assets/javascripts/lib/shared-edits.js.es6
+++ b/assets/javascripts/lib/shared-edits.js.es6
@@ -11,7 +11,7 @@ export function setupSharedEdit(composer) {
   composer.set("sharedEditManager", manager);
 
   ajax(`/shared_edits/p/${composer.post.id}`)
-    .then(data => {
+    .then((data) => {
       manager.set("version", data.version);
       manager.set("raw", data.raw);
       manager.set("composer", composer);
@@ -83,7 +83,7 @@ const SharedEditManager = EmberObject.extend({
 
   commit() {
     ajax(`/shared_edits/p/${this.composer.post.id}/commit`, {
-      method: "PUT"
+      method: "PUT",
     }).catch(popupAjaxError);
   },
 
@@ -126,10 +126,10 @@ const SharedEditManager = EmberObject.extend({
         data: {
           revision: JSON.stringify(changes),
           version: this.version,
-          client_id: composer.messageBus.clientId
-        }
+          client_id: composer.messageBus.clientId,
+        },
       })
-        .then(result => {
+        .then((result) => {
           const inProgressChanges = diff(submittedRaw, composer.reply);
           this.applyRevisions(result.revisions, inProgressChanges);
         })
@@ -150,7 +150,7 @@ const SharedEditManager = EmberObject.extend({
 
     let newChanges = [];
 
-    revs.forEach(revision => {
+    revs.forEach((revision) => {
       if (revision.version === newVersion + 1) {
         let parsedRevision = JSON.parse(revision.revision);
         newRaw = otUnicode.apply(newRaw, parsedRevision);
@@ -187,9 +187,17 @@ const SharedEditManager = EmberObject.extend({
           newChanges
         );
 
+        // still need to compensate for scrollHeight changes
+        // but at least this is mostly stable
+        const scrollTop = input.scrollTop;
+
         input.value = newRaw;
         input.selectionStart = position;
         input.selectionEnd = position + selLength;
+
+        window.requestAnimationFrame(() => {
+          input.scrollTop = scrollTop;
+        });
       }
 
       this.composer.set("reply", newRaw);
@@ -200,7 +208,7 @@ const SharedEditManager = EmberObject.extend({
     const composer = this.composer;
     const post = composer.post;
 
-    composer.messageBus.subscribe(`/shared_edits/${post.id}`, message => {
+    composer.messageBus.subscribe(`/shared_edits/${post.id}`, (message) => {
       if (
         message.client_id !== composer.messageBus.clientId &&
         !this.ajaxInProgress
@@ -208,5 +216,5 @@ const SharedEditManager = EmberObject.extend({
         this.applyRevisions([message]);
       }
     });
-  }
+  },
 });

GitHub sha: 15b9ecbb

1 Like

This commit appears in #3 which was approved by jjaffeux. It was merged by jjaffeux.