]> git.ipfire.org Git - thirdparty/bugzilla.git/commitdiff
Bug 1563360 - End key does not scroll all the way to the bottom of BMO page
authorKohei Yoshino <kohei.yoshino@gmail.com>
Fri, 19 Jul 2019 19:49:27 +0000 (15:49 -0400)
committerGitHub <noreply@github.com>
Fri, 19 Jul 2019 19:49:27 +0000 (15:49 -0400)
extensions/BugModal/web/bug_modal.css
extensions/BugModal/web/comments.js
skins/standard/global.css

index f4a9710bcc32faeef8e7be40b94d57dfeacba9f0..2c8532cf45f530024c3a626d6149707a8952b09a 100644 (file)
@@ -839,8 +839,13 @@ h3.change-name a {
   text-decoration: none;
 }
 
+.change-set .attachment .outer:empty {
+  width: 426px;
+  height: 240px;
+}
+
 .change-set .attachment button.outer {
-  padding: 0;
+  padding: 0 !important;
   box-shadow: none;
   font-weight: normal;
   transition: none;
index b39f22efefb7f8c98b00af1400c76f2d56467b75..35f35e6e4cdafdd54838021b13e5bed858b4e183 100644 (file)
@@ -473,7 +473,7 @@ $(function() {
  * Reference or define the Bugzilla app namespace.
  * @namespace
  */
-var Bugzilla = Bugzilla || {};
+var Bugzilla = Bugzilla || {}; // eslint-disable-line no-var
 
 /**
  * Reference or define the Review namespace.
@@ -493,11 +493,7 @@ Bugzilla.BugModal.Comments = class Comments {
   }
 
   /**
-   * Prepare to show image, media and text attachments inline if possible. For a better performance, this functionality
-   * uses the Intersection Observer API to show attachments when the associated comment goes into the viewport, when the
-   * page is scrolled down or the collapsed comment is expanded. This also utilizes the Network Information API to save
-   * @see https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
-   * @see https://developer.mozilla.org/en-US/docs/Web/API/Network_Information_API
+   * Prepare to show an attachment inline if possible.
    */
   prepare_inline_attachments() {
     // Check the connectivity, API support, user setting, bug security and sensitive keywords
@@ -508,15 +504,6 @@ Bugzilla.BugModal.Comments = class Comments {
       return;
     }
 
-    const observer = new IntersectionObserver(entries => entries.forEach(entry => {
-      const $att = entry.target;
-
-      if (entry.intersectionRatio > 0) {
-        observer.unobserve($att);
-        this.show_attachment($att);
-      }
-    }), { root: document.querySelector('#bugzilla-body') });
-
     document.querySelectorAll('.change-set').forEach($set => {
       // Skip if the comment has the `hide-attachment` tag
       const $comment = $set.querySelector('.comment:not([data-tags~="hide-attachment"])');
@@ -524,80 +511,162 @@ Bugzilla.BugModal.Comments = class Comments {
       const $attachment = $set.querySelector('.attachment:not(.obsolete):not(.deleted)');
 
       if ($comment && $attachment) {
-        observer.observe($attachment);
+        this.attachment = new Bugzilla.InlineAttachment($attachment);
       }
     });
   }
+};
 
+/**
+ * Implement the inline attachment renderer that will be used for bug comments. For a better performance, this
+ * functionality uses the Intersection Observer API to show an attachment when the comment goes into the viewport.
+ */
+Bugzilla.InlineAttachment = class InlineAttachment {
   /**
-   * Load and show an image, audio, video or text attachment.
-   * @param {HTMLElement} $att An attachment wrapper element.
+   * Initiate a new InlineAttachment instance.
+   * @param {HTMLElement} $attachment Attachment container on each comment.
    */
-  async show_attachment($att) {
-    const id = Number($att.dataset.id);
-    const link = $att.querySelector('.link').href;
-    const name = $att.querySelector('[itemprop="name"]').content;
-    const type = $att.querySelector('[itemprop="encodingFormat"]').content;
-    const size = Number($att.querySelector('[itemprop="contentSize"]').content);
-
-    // Skip if the attachment is marked as binary
-    if (type.match(/^application\/(?:octet-stream|binary)$/)) {
-      return;
-    }
+  constructor($attachment) {
+    this.$attachment = $attachment;
+    this.id = Number(this.$attachment.dataset.id);
+    this.link = this.$attachment.querySelector('.link').href;
+    this.name = this.$attachment.querySelector('[itemprop="name"]').content;
+    this.size = Number(this.$attachment.querySelector('[itemprop="contentSize"]').content);
+    this.type = this.$attachment.querySelector('[itemprop="encodingFormat"]').content;
+    this.media = this.type.split('/').shift();
 
     // Show image smaller than 2 MB, excluding SVG and non-standard formats
-    if (type.match(/^image\/(?!vnd|svg).+$/) && size < 2000000) {
-      $att.insertAdjacentHTML('beforeend', `
-        <a href="${link}" class="outer lightbox"><img src="${link}" alt="${name.htmlEncode()}" itemprop="image"></a>`);
+    if (this.type.match(/^image\/(?!vnd|svg).+$/) && this.size < 2000000) {
+      this.show_image();
+    }
+
+    // Show audio and video
+    if (this.type.match(/^(?:audio|video)\/(?!vnd).+$/) && document.createElement(this.media).canPlayType(this.type)) {
+      this.show_media();
+    }
+
+    // Detect text (code from attachment.js)
+    this.is_patch = this.$attachment.matches('.patch');
+    this.is_markdown = !!this.name.match(/\.(?:md|mkdn?|mdown|markdown)$/);
+    this.is_source = !!this.name.match(/\.(?:cpp|es|h|js|json|rs|rst|sh|toml|ts|tsx|xml|yaml|yml)$/);
+    this.is_text = this.type.match(/^text\/(?!x-).+$/) || this.is_patch || this.is_markdown || this.is_source;
+
+    // Show text smaller than 50 KB
+    if (this.is_text && this.size < 50000) {
+      this.show_text();
+    }
+  }
+
+  /**
+   * Show an image attachment.
+   */
+  async show_image() {
+    // Insert a placeholder first
+    this.$attachment.insertAdjacentHTML('beforeend', `<a href="${this.link}" class="outer lightbox"></a>`);
+
+    // Wait until the container goes into the viewport
+    await this.watch_visibility();
+
+    const $image = new Image();
+
+    try {
+      await new Promise((resolve, reject) => {
+        $image.addEventListener('load', () => resolve(), { once: true });
+        $image.addEventListener('error', () => reject(), { once: true });
+        $image.src = this.link;
+      });
+
+      $image.setAttribute('itemprop', 'image');
+      $image.alt = this.name;
+      this.$outer.appendChild($image);
 
       // Add lightbox support
-      $att.querySelector('.outer.lightbox').addEventListener('click', event => {
-        if (event.metaKey || event.ctrlKey || event.altKey || event.shiftKey) {
-          return;
+      this.$outer.addEventListener('click', event => {
+        if (!event.metaKey && !event.ctrlKey && !event.altKey && !event.shiftKey) {
+          event.preventDefault();
+          lb_show(event.target);
         }
+      });
+    } catch (ex) {
+      this.$outer.remove();
+    }
+  }
 
-        event.preventDefault();
-        lb_show(event.target);
+  /**
+   * Show an audio or video attachment.
+   */
+  async show_media() {
+    // Insert a placeholder first
+    this.$attachment.insertAdjacentHTML('beforeend', '<span class="outer"></span>');
+
+    // Wait until the container goes into the viewport
+    await this.watch_visibility();
+
+    const $media = document.createElement(this.media);
+
+    try {
+      await new Promise((resolve, reject) => {
+        $media.addEventListener('loadedmetadata', () => resolve(), { once: true });
+        $media.addEventListener('error', () => reject(), { once: true });
+        $media.src = this.link;
       });
+
+      $media.setAttribute('itemprop', this.media);
+      $media.controls = true;
+      this.$outer.appendChild($media);
+    } catch (ex) {
+      this.$outer.remove();
     }
+  }
 
-    // Show audio and video
-    if (type.match(/^(?:audio|video)\/(?!vnd).+$/)) {
-      const media = type.split('/')[0];
+  /**
+   * Show a text attachment. Fetch the raw text via the API.
+   */
+  async show_text() {
+    // Insert a placeholder first
+    this.$attachment.insertAdjacentHTML('beforeend',
+      `<button type="button" role="link" title="${this.name.htmlEncode()}" class="outer"></button>`);
+
+    // Wait until the container goes into the viewport
+    await this.watch_visibility();
 
-      if (document.createElement(media).canPlayType(type)) {
-        $att.insertAdjacentHTML('beforeend', `
-          <span class="outer"><${media} src="${link}" controls itemprop="${media}"></span>`);
+    try {
+      const { attachments } = await Bugzilla.API.get(`bug/attachment/${this.id}`, { include_fields: 'data' });
+      const text = decodeURIComponent(escape(atob(attachments[this.id].data)));
+      const lang = this.is_patch ? 'diff' : this.type.match(/\w+$/)[0];
+
+      this.$outer.innerHTML = `<pre class="language-${lang}" role="img" itemprop="text">${text.htmlEncode()}</pre>`;
+
+      // Make the button work as a link. It cannot be `<a>` because Prism Autolinker plugin may add links to `<pre>`
+      this.$attachment.querySelector('[role="link"]').addEventListener('click', () => location.href = this.link);
+
+      if (Prism) {
+        Prism.highlightElement(this.$attachment.querySelector('pre'));
+        this.$attachment.querySelectorAll('pre a').forEach($a => $a.tabIndex = -1);
       }
+    } catch (ex) {
+      this.$outer.remove();
     }
+  }
 
-    // Detect text (code from attachment.js)
-    const is_patch = $att.matches('.patch');
-    const is_markdown = !!name.match(/\.(?:md|mkdn?|mdown|markdown)$/);
-    const is_source = !!name.match(/\.(?:cpp|es|h|js|json|rs|rst|sh|toml|ts|tsx|xml|yaml|yml)$/);
-    const is_text = type.match(/^text\/(?!x-).+$/) || is_patch || is_markdown || is_source;
-
-    // Show text smaller than 50 KB
-    if (is_text && size < 50000) {
-      // Load text body
-      try {
-        const { attachments } = await Bugzilla.API.get(`bug/attachment/${id}`, { include_fields: 'data' });
-        const text = decodeURIComponent(escape(atob(attachments[id].data)));
-        const lang = is_patch ? 'diff' : type.match(/\w+$/)[0];
-
-        $att.insertAdjacentHTML('beforeend', `
-          <button type="button" role="link" title="${name.htmlEncode()}" class="outer">
-          <pre class="language-${lang}" role="img" itemprop="text">${text.htmlEncode()}</pre></button>`);
-
-        // Make the button work as a link. It cannot be `<a>` because Prism Autolinker plugin may add links to `<pre>`
-        $att.querySelector('[role="link"]').addEventListener('click', () => location.href = link);
-
-        if (Prism) {
-          Prism.highlightElement($att.querySelector('pre'));
-          $att.querySelectorAll('pre a').forEach($a => $a.tabIndex = -1);
+  /**
+   * Use the Intersection Observer API to watch the visibility of the attachment container.
+   * @returns {Promise} Resolved once the container goes into the viewport.
+   * @see https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
+   */
+  async watch_visibility() {
+    this.$outer = this.$attachment.querySelector('.outer');
+
+    return new Promise(resolve => {
+      const observer = new IntersectionObserver(entries => entries.forEach(entry => {
+        if (entry.intersectionRatio > 0) {
+          observer.disconnect();
+          resolve();
         }
-      } catch (ex) {}
-    }
+      }), { root: document.querySelector('#bugzilla-body') });
+
+      observer.observe(this.$attachment);
+    });
   }
 };
 
index d909dd9c1b7f9db3357668831d43f90573194c3f..600b25c93a2eb81f54e51209131abe8717fee4dd 100644 (file)
@@ -991,7 +991,6 @@ input[type="radio"]:checked {
     overflow-x: auto;
     overflow-y: scroll;
     -webkit-overflow-scrolling: touch; /* Enable momentum scrolling on iOS */
-    scroll-behavior: smooth;
     will-change: transform; /* Enable smooth scrolling (Safari) */
   }
 }