Commit 43f1c4f9 authored by Kevin Grandon's avatar Kevin Grandon Committed by Andrew Sutherland
Browse files

Bug 837251 - Compose textarea doesn't occupy available screen space or show bounding box. r=asuth

Includes some minor review fixes by asuth.
parent 33fe27aa
Loading
Loading
Loading
Loading
+1 −1
Changes for apps/email/js/cards/compose.html: 1 added line, 1 removed line.
Original line number Diff line number Diff line
@@ -64,7 +64,7 @@
      <ul class="cmp-attachment-container">
      </ul>
    </div>
    <textarea class="cmp-body-text" rows="1"></textarea>
    <div class="cmp-body-text" contenteditable="true"></div>
    <div class="cmp-body-html">
    </div>
  </div>
+99 −26
Changes for apps/email/js/cards/compose.js: 99 added lines, 26 removed lines.
Original line number Diff line number Diff line
@@ -44,6 +44,8 @@ function focusInputAndPositionCursorFromContainerClick(event, input) {
  // because under Gecko originalTarget may contain anonymous content.
  if (event.explicitOriginalTarget === input)
    return;
  // Stop bubbling to avoid our other focus-handlers!
  event.stopPropagation();

  // coordinates are relative to the viewport origin
  var bounds = input.getBoundingClientRect();
@@ -81,10 +83,6 @@ function ComposeCard(domNode, mode, args) {
  this.bccNode = domNode.getElementsByClassName('cmp-bcc-text')[0];
  this.subjectNode = domNode.getElementsByClassName('cmp-subject-text')[0];
  this.textBodyNode = domNode.getElementsByClassName('cmp-body-text')[0];
  this.textBodyNode.addEventListener('input',
                                     this.onTextBodyDelta.bind(this));
  this.textBodyNode.addEventListener('change',
                                     this.onTextBodyDelta.bind(this));
  this.htmlBodyContainer = domNode.getElementsByClassName('cmp-body-html')[0];
  this.htmlIframeNode = null;

@@ -124,6 +122,21 @@ function ComposeCard(domNode, mode, args) {
      evt, subjectContainer.querySelector('input'));
  });

  // Likewise, clicking on the empty space below our contenteditable region
  // or on our immutable HTML quoting box should result in us positioning
  // the cursor in our contenteditable region.
  this.scrollContainer.addEventListener(
    'click',
    function(event) {
      // Only do this if the click is BELOW the text area.
      var bounds = this.textBodyNode.getBoundingClientRect();
      if (event.clientY > bounds.bottom) {
        this._focusEditorWithCursorAtEnd(event);
      }
    }.bind(this));
  this.htmlBodyContainer.addEventListener(
    'click', this._focusEditorWithCursorAtEnd.bind(this));

  // Sent sound init
  this.sentAudioKey = 'mail.sent-sound.enabled';
  this.sentAudio = new Audio('/sounds/sent.opus');
@@ -142,6 +155,85 @@ function ComposeCard(domNode, mode, args) {
  }
}
ComposeCard.prototype = {

  /**
   * Focus our contenteditable region and position the cursor at the last
   * valid editing cursor position.
   *
   * The intent is so that if the user taps below our editing region that we
   * still correctly position the cursor.  We previously relied on min-height
   * to do this for us, but that results in ugly problems when we have quoted
   * HTML that follows and our editable region is not big enough to satisfy
   * the height.
   *
   * Note: When we are quoting HTML, the "Bob wrote:" stuff does go in the
   * contenteditable text area, so we may actually want to get smarter and
   * position the cursor before that node instead.
   */
  _focusEditorWithCursorAtEnd: function(event) {
    if (event)
      event.stopPropagation();

    // Selection/range manipulation is the easiest way to force the cursor
    // to a specific location.
    //
    // Note: Once the user has pressed return once, the editor will create a
    // bogus <br type="_moz"> that is always the last element.  Even though this
    // bogus node will be the last child, nothing tremendously bad happens.
    //
    // Note: This technique does result in our new text existing in its own,
    // new text node.  So don't make any assumptions about how text nodes are
    // arranged.
    var insertAfter = this.textBodyNode.lastChild;
    var range = document.createRange();
    range.setStartAfter(insertAfter);
    range.setEndAfter(insertAfter);

    this.textBodyNode.focus();
    var selection = window.getSelection();
    selection.removeAllRanges();
    selection.addRange(range);
  },

  /**
   * Inserts an email into the contenteditable element
   */
  populateEditor: function(value) {
    var lines = value.split('\n');
    var frag = document.createDocumentFragment();
    for (var i = 0, len = lines.length; i < len; i++) {
      if (i) {
        frag.appendChild(document.createElement('br'));
      }
      frag.appendChild(document.createTextNode(lines[i]));
    }
    this.textBodyNode.appendChild(frag);
  },

  /**
   * Gets the raw value from a contenteditable div
   */
  fromEditor: function(value) {
    var content = '';
    var len = this.textBodyNode.childNodes.length;
    for (var i = 0; i < len; i++) {
      var node = this.textBodyNode.childNodes[i];
      if (node.nodeName === 'BR' &&
          // Gecko's contenteditable implementation likes to create a synthetic
          // trailing BR with type="_moz".  We do not like/need this synthetic
          // BR, so we filter it out.  Check out
          // nsTextEditRules::CreateTrailingBRIfNeeded to find out where it
          // comes from.
          node.getAttribute('type') !== '_moz') {
        content += '\n';
      } else {
        content += node.textContent;
      }
    }

    return content;
  },

  postInsert: function() {
    // the HTML bit needs us linked into the DOM so the iframe can be
    // linked in, hence this happens in postInsert.
@@ -197,9 +289,7 @@ ComposeCard.prototype = {
    this.insertAttachments();

    this.subjectNode.value = this.composer.subject;
    this.textBodyNode.value = this.composer.body.text;
    // force the textarea to be sized.
    this.onTextBodyDelta();
    this.populateEditor(this.composer.body.text);

    if (this.composer.body.html) {
      // Although (still) sanitized, this is still HTML we did not create and so
@@ -234,7 +324,7 @@ ComposeCard.prototype = {
    this.composer.cc = frobAddressNode(this.ccNode);
    this.composer.bcc = frobAddressNode(this.bccNode);
    this.composer.subject = this.subjectNode.value;
    this.composer.body.text = this.textBodyNode.value;
    this.composer.body.text = this.fromEditor();
    // The HTML representation cannot currently change in our UI, so no
    // need to save it.  However, what we send to the back-end is what gets
    // sent, so if you want to implement editing UI and change this here,
@@ -261,7 +351,7 @@ ComposeCard.prototype = {
    // body, there are attachments, or we already created a draft for this
    // guy in which case we really want to provide the option to delete the
    // draft.
    return (this.subjectNode.value || this.textBodyNode.value ||
    return (this.subjectNode.value || this.textBodyNode.textContent ||
        !checkAddressEmpty() || this.composer.attachments.length ||
        this.composer.hasDraft);
  },
@@ -442,23 +532,6 @@ ComposeCard.prototype = {
    focusInputAndPositionCursorFromContainerClick(evt, input);
  },

  /**
   * Make our textarea grow as new lines are added...
   */
  onTextBodyDelta: function() {
    var value = this.textBodyNode.value, newlines = 0, idx = -1;
    while (true) {
      idx = value.indexOf('\n', idx + 1);
      if (idx === -1)
        break;
      newlines++;
    }
    // the last line won't have a newline
    var neededRows = newlines + 1;
    if (this.textBodyNode.rows !== neededRows)
      this.textBodyNode.rows = neededRows;
  },

  insertAttachments: function() {
    var attachmentsContainer =
      this.domNode.getElementsByClassName('cmp-attachment-container')[0];
+4 −7
Changes for apps/email/js/cards/message_reader.js: 4 added lines, 7 removed lines.
Original line number Diff line number Diff line
@@ -299,11 +299,13 @@ MessageReaderCard.prototype = {
    });

    var otherAddresses = (this.header.to || []).concat(this.header.cc || []);
    if (this.header.replyTo) {
    if (this.header.replyTo && this.header.replyTo.author) {
      otherAddresses.push(this.header.replyTo.author);
    }
    for (var i = 0; i < otherAddresses.length; i++) {
      if (myAddresses.indexOf(otherAddresses[i].address) == -1) {
      var otherAddress = otherAddresses[i];
      if (otherAddress.address &&
          myAddresses.indexOf(otherAddress.address) == -1) {
        return true;
      }
    }
@@ -335,11 +337,6 @@ MessageReaderCard.prototype = {
    }).bind(this);
    contents.addEventListener('submit', formSubmit);

    if (!this.canForward()) {
      contents.querySelector('.msg-reply-menu-forward')
        .classList.add('collapsed');
    }

    if (!this.canReplyAll()) {
      contents.querySelector('.msg-reply-menu-reply-all')
        .classList.add('collapsed');
+8 −3
Changes for apps/email/js/iframe_shims.js: 8 added lines, 3 removed lines.
Original line number Diff line number Diff line
@@ -185,9 +185,6 @@ function createAndInsertIframeForContent(htmlStr, scrollContainer,

  var viewportWidth = parentNode.offsetWidth - scrollPad;
  var viewport = document.createElement('div');
  var title = document.getElementsByClassName('msg-reader-header')[0];
  var header = document.getElementsByClassName('msg-envelope-bar')[0];
  var extraHeight = title.clientHeight + header.clientHeight;
  viewport.setAttribute(
    'style',
    'overflow: hidden; position: relative; ' +
@@ -329,6 +326,14 @@ function createAndInsertIframeForContent(htmlStr, scrollContainer,
  if (!newsletterMode || interactiveMode !== 'interactive') {
    return iframeShims;
  }
  // XXX these live down here now because these hardcoded hacks will
  // explode when we use this logic in the composer (when there isn't
  // a message reader on the stack).  This is okay / not any more horrible
  // because the compose iframe is entirely noninteractive so these values
  // aren't used anyways.
  var title = document.getElementsByClassName('msg-reader-header')[0];
  var header = document.getElementsByClassName('msg-envelope-bar')[0];
  var extraHeight = title.clientHeight + header.clientHeight;
  detectorTarget.addEventListener('dbltap', function(e) {
    var newScale = scale;
    if (lastScale === scale) {
+0 −1
Changes for apps/email/style/common.css: 0 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -141,7 +141,6 @@ body {
  overflow-x: hidden;
}
.scrollregion-below-header {
  overflow-y: auto;
  overflow-x: hidden;
  height: calc(100% - 5.8rem + 0.5rem);
  will-change: scroll-position;
Loading