UX: make composer resize work on touch devices (#7068)

UX: make composer resize work on touch devices (#7068)

  • UX: make composer resize work on touch devices

This also replaces a vendor dependency with a small built-in resize mechanism.

  • Make blue bar’s larger padding specific to touch devices
diff --git a/app/assets/javascripts/discourse/components/composer-body.js.es6 b/app/assets/javascripts/discourse/components/composer-body.js.es6
index 5d194eb..c536565 100644
--- a/app/assets/javascripts/discourse/components/composer-body.js.es6
+++ b/app/assets/javascripts/discourse/components/composer-body.js.es6
@@ -8,6 +8,17 @@ import positioningWorkaround from "discourse/lib/safari-hacks";
 import { headerHeight } from "discourse/components/site-header";
 import KeyEnterEscape from "discourse/mixins/key-enter-escape";
 
+const START_EVENTS = "touchstart mousedown";
+const DRAG_EVENTS = "touchmove mousemove";
+const END_EVENTS = "touchend mouseup";
+
+const MIN_COMPOSER_SIZE = 240;
+const THROTTLE_RATE = 20;
+
+function mouseYPos(e) {
+  return e.clientY || (e.touches && e.touches[0] && e.touches[0].clientY);
+}
+
 export default Ember.Component.extend(KeyEnterEscape, {
   elementId: "reply-control",
 
@@ -84,17 +95,53 @@ export default Ember.Component.extend(KeyEnterEscape, {
     }
   },
 
-  didInsertElement() {
-    this._super(...arguments);
-    const $replyControl = $("#reply-control");
-    const resize = () => Ember.run(() => this.resize());
+  setupComposerResizeEvents() {
+    const $composer = this.$();
+    const $grippie = this.$(".grippie");
+    const $document = Ember.$(document);
+    let origComposerSize = 0;
+    let lastMousePos = 0;
+
+    const performDrag = event => {
+      $composer.trigger("div-resizing");
+      $composer.addClass("clear-transitions");
+      const currentMousePos = mouseYPos(event);
+      let size = origComposerSize + (lastMousePos - currentMousePos);
+
+      const winHeight = Ember.$(window).height();
+      size = Math.min(size, winHeight - headerHeight());
+      size = Math.max(size, MIN_COMPOSER_SIZE);
+      const sizePx = `${size}px`;
+      this.movePanels(sizePx);
+      $composer.height(sizePx);
+    };
+
+    const throttledPerformDrag = (event => {
+      event.preventDefault();
+      Ember.run.throttle(this, performDrag, event, THROTTLE_RATE);
+    }).bind(this);
 
-    $replyControl.DivResizer({
-      resize,
-      maxHeight: winHeight => winHeight - headerHeight(),
-      onDrag: sizePx => this.movePanels(sizePx)
+    const endDrag = () => {
+      $document.off(DRAG_EVENTS, throttledPerformDrag);
+      $document.off(END_EVENTS, endDrag);
+      $composer.removeClass("clear-transitions");
+      $composer.focus();
+    };
+
+    $grippie.on(START_EVENTS, event => {
+      event.preventDefault();
+      origComposerSize = $composer.height();
+      lastMousePos = mouseYPos(event);
+      $document.on(DRAG_EVENTS, throttledPerformDrag);
+      $document.on(END_EVENTS, endDrag);
     });
+  },
+
+  didInsertElement() {
+    this._super(...arguments);
+    this.setupComposerResizeEvents();
 
+    const resize = () => Ember.run(() => this.resize());
     const triggerOpen = () => {
       if (this.get("composer.composeState") === Composer.OPEN) {
         this.appEvents.trigger("composer:opened");
@@ -102,13 +149,11 @@ export default Ember.Component.extend(KeyEnterEscape, {
     };
     triggerOpen();
 
-    afterTransition($replyControl, () => {
+    afterTransition(this.$(), () => {
       resize();
       triggerOpen();
     });
     positioningWorkaround(this.$());
-
-    this.appEvents.on("composer:resize", this, this.resize);
   },
 
   willDestroyElement() {
diff --git a/app/assets/javascripts/discourse/templates/composer.hbs b/app/assets/javascripts/discourse/templates/composer.hbs
index 9f512ba..834f65d 100644
--- a/app/assets/javascripts/discourse/templates/composer.hbs
+++ b/app/assets/javascripts/discourse/templates/composer.hbs
@@ -4,7 +4,7 @@
                  typed=(action "typed")
                  cancelled=(action "cancelled")
                  save=(action "save")}}
-
+  <div class="grippie"></div>
   {{#if visible}}
       {{composer-messages composer=model
                           messageCount=messageCount
diff --git a/app/assets/javascripts/vendor.js b/app/assets/javascripts/vendor.js
index 5279b2c..0503964 100644
--- a/app/assets/javascripts/vendor.js
+++ b/app/assets/javascripts/vendor.js
@@ -9,7 +9,6 @@
 //= require Markdown.Converter.js
 //= require bootbox.js
 //= require bootstrap-modal.js
-//= require div_resizer
 //= require caret_position
 //= require favcount.js
 //= require jquery.ba-resize.js
diff --git a/app/assets/stylesheets/desktop/compose.scss b/app/assets/stylesheets/desktop/compose.scss
index 21152aa..8fd3c36 100644
--- a/app/assets/stylesheets/desktop/compose.scss
+++ b/app/assets/stylesheets/desktop/compose.scss
@@ -67,6 +67,14 @@
   }
 }
 
+.discourse-touch {
+  .open {
+    .grippie {
+      padding: 7px 0;
+    }
+  }
+}
+
 .composer-popup-container {
   max-width: 1500px;
   margin-left: auto;
diff --git a/vendor/assets/javascripts/div_resizer.js b/vendor/assets/javascripts/div_resizer.js
deleted file mode 100644
index 9bb7b3c..0000000
--- a/vendor/assets/javascripts/div_resizer.js
+++ /dev/null
@@ -1,99 +0,0 @@
-/**
-  This is a jQuery plugin to support resizing text areas.
-
-  Originally based off text area resizer by Ryan O'Dell : http://plugins.jquery.com/misc/textarea.js
-
-  @module $.fn.DivResizer
-**/
-
-var div, endDrag, grip, lastMousePos, min, mousePosition, originalDivHeight, originalPos, performDrag, startDrag, wrappedEndDrag, wrappedPerformDrag;
-div = void 0;
-originalPos = void 0;
-originalDivHeight = void 0;
-lastMousePos = 0;
-min = 230;
-grip = void 0;
-wrappedEndDrag = void 0;
-wrappedPerformDrag = void 0;
-
-startDrag = function(e, opts) {
-  div = $(e.data.el);
-  div.addClass('clear-transitions');
-  div.blur();
-  lastMousePos = mousePosition(e).y;
-  originalPos = lastMousePos;
-  originalDivHeight = div.height();
-  wrappedPerformDrag = (function() {
-    return function(e) {
-      return performDrag(e, opts);
-    };
-  })();
-  wrappedEndDrag = (function() {
-    return function(e) {
-      return endDrag(e, opts);
-    };
-  })();
-  $(document).mousemove(wrappedPerformDrag).mouseup(wrappedEndDrag);
-  return false;
-};
-
-performDrag = function(e, opts) {
-  $(div).trigger("div-resizing");
-
-  var size, sizePx, thisMousePos;
-  thisMousePos = mousePosition(e).y;
-  size = originalDivHeight + (originalPos - thisMousePos);
-  lastMousePos = thisMousePos;
-
-  var maxHeight = $(window).height();
-  if (opts.maxHeight) {
-    maxHeight = opts.maxHeight(maxHeight);
-  }
-  size = Math.min(size, maxHeight);
-  size = Math.max(min, size);
-  sizePx = size + "px";
-  if (typeof opts.onDrag === "function") {
-    opts.onDrag(sizePx);
-  }
-  div.height(sizePx);
-  if (size < min) {
-    endDrag(e, opts);
-  }
-  return false;
-};
-
-endDrag = function(e, opts) {
-  $(document).unbind("mousemove", wrappedPerformDrag).unbind("mouseup", wrappedEndDrag);
-  div.removeClass('clear-transitions');
-  div.focus();
-  if (typeof opts.resize === "function") {
-    opts.resize();
-  }
-  $(div).trigger("div-resized");
-  div = null;
-};
-
-mousePosition = function(e) {
-  return {
-    x: e.clientX + document.documentElement.scrollLeft,
-    y: e.clientY + document.documentElement.scrollTop
-  };
-};
-
-$.fn.DivResizer = function(opts) {
-  return this.each(function() {
-    var grippie, start, staticOffset;
-    div = $(this);
-    if (div.hasClass("processed")) return;
-    div.addClass("processed");
-    staticOffset = null;
-    start = function() {
-      return function(e) {
-        return startDrag(e, opts);
-      };
-    };
-    grippie = div.prepend("<div class='grippie'></div>").find('.grippie').bind("mousedown", {
-      el: this
-    }, start());
-  });
-};

GitHub sha: d5efe2d7

3 Likes

This commit has been mentioned on Discourse Meta. There might be relevant details there:

this is so awesome, so happy you made this change.

2 Likes