summaryrefslogtreecommitdiffstats
path: root/extensions
diff options
context:
space:
mode:
Diffstat (limited to 'extensions')
-rw-r--r--extensions/Review/template/en/default/hook/global/header-badge.html.tmpl47
-rw-r--r--extensions/Review/template/en/default/hook/global/header-start.html.tmpl8
-rw-r--r--extensions/Review/web/js/badge.js102
-rw-r--r--extensions/Review/web/styles/badge.css27
4 files changed, 162 insertions, 22 deletions
diff --git a/extensions/Review/template/en/default/hook/global/header-badge.html.tmpl b/extensions/Review/template/en/default/hook/global/header-badge.html.tmpl
index aca61561e..df3dd82be 100644
--- a/extensions/Review/template/en/default/hook/global/header-badge.html.tmpl
+++ b/extensions/Review/template/en/default/hook/global/header-badge.html.tmpl
@@ -6,18 +6,35 @@
# defined by the Mozilla Public License, v. 2.0.
#%]
-[% RETURN UNLESS
- user.review_request_count
- || user.feedback_request_count
- || user.needinfo_request_count
-%]
-
-<a id="header-flags" class="badge"
- href="request.cgi?action=queue&amp;requestee=[% user.login FILTER uri %]&amp;group=type"
- title="Flags requested of you:
- [%- " review (" _ user.review_request_count _ ")" IF user.review_request_count -%]
- [%- " feedback (" _ user.feedback_request_count _ ")" IF user.feedback_request_count -%]
- [%- " needinfo (" _ user.needinfo_request_count _ ")" IF user.needinfo_request_count -%]
-">
- [%- user.review_request_count + user.feedback_request_count + user.needinfo_request_count ~%]
-</a>
+[% IF user.id %]
+ [% request_count = user.review_request_count + user.feedback_request_count + user.needinfo_request_count %]
+ <div id="header-requests" class="dropdown">
+ <button type="button" id="header-requests-menu-button" class="dropdown-button minor"
+ title="Requests for you[%- IF request_count -%]:
+ [%- " review (" _ user.review_request_count _ ")" IF user.review_request_count -%]
+ [%- " feedback (" _ user.feedback_request_count _ ")" IF user.feedback_request_count -%]
+ [%- " needinfo (" _ user.needinfo_request_count _ ")" IF user.needinfo_request_count -%][%- END -%]"
+ aria-label="Requests for you" aria-expanded="false" aria-haspopup="true" aria-controls="header-requests-menu">
+ [%- IF request_count -%]
+ <span class="badge">[% request_count FILTER html %]</span>
+ [%- ELSE -%]
+ <span class="icon" aria-hidden="true"></span>
+ [%- END -%]
+ </button>
+ <section class="dropdown-content dropdown-panel left" id="header-requests-menu" role="menu" style="display:none;">
+ <header>
+ <h2>Requests</h2>
+ </header>
+ [%- IF request_count -%]
+ <div class="loading">Loading…</div>
+ <ul class="notifications" role="none" hidden></ul>
+ [%- ELSE -%]
+ <div class="empty">You’re all caught up!</div>
+ [%- END -%]
+ <footer>
+ <div><a href="request.cgi?action=queue&amp;requestee=[% user.login FILTER uri %]&amp;group=requestee"
+ role="menuitem" tabindex="-1">See All</a></div>
+ </footer>
+ </section>
+ </div>
+[% END %]
diff --git a/extensions/Review/template/en/default/hook/global/header-start.html.tmpl b/extensions/Review/template/en/default/hook/global/header-start.html.tmpl
index 3da136f41..5441ea270 100644
--- a/extensions/Review/template/en/default/hook/global/header-start.html.tmpl
+++ b/extensions/Review/template/en/default/hook/global/header-start.html.tmpl
@@ -6,12 +6,8 @@
# defined by the Mozilla Public License, v. 2.0.
#%]
-[% IF user.review_request_count
- || user.feedback_request_count
- || user.needinfo_request_count
-%]
- [% style_urls.push('extensions/Review/web/styles/badge.css') %]
-[% END %]
+[% style_urls.push('extensions/Review/web/styles/badge.css') %]
+[% javascript_urls.push('js/util.js', 'js/lib/md5.min.js', 'extensions/Review/web/js/badge.js') %]
[% RETURN UNLESS template.name == 'attachment/edit.html.tmpl'
|| template.name == 'attachment/create.html.tmpl'
diff --git a/extensions/Review/web/js/badge.js b/extensions/Review/web/js/badge.js
new file mode 100644
index 000000000..cff22dc40
--- /dev/null
+++ b/extensions/Review/web/js/badge.js
@@ -0,0 +1,102 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This Source Code Form is "Incompatible With Secondary Licenses", as
+ * defined by the Mozilla Public License, v. 2.0. */
+
+/**
+ * Reference or define the Bugzilla app namespace.
+ * @namespace
+ */
+var Bugzilla = Bugzilla || {};
+
+/**
+ * Reference or define the Review namespace.
+ * @namespace
+ */
+Bugzilla.Review = Bugzilla.Review || {};
+
+/**
+ * Provide the Badge functionality that shows the current review summary in the dropdown.
+ */
+Bugzilla.Review.Badge = class Badge {
+ /**
+ * Get a new Badge instance.
+ * @returns {Badge} New Badge instance.
+ */
+ constructor() {
+ this.$button = document.querySelector('#header-requests-menu-button');
+ this.$panel = document.querySelector('#header-requests .dropdown-panel');
+ this.$loading = document.querySelector('#header-requests .dropdown-panel .loading');
+
+ if (this.$loading) {
+ this.$button.addEventListener('click', () => this.init(), { once: true });
+ }
+ }
+
+ /**
+ * Initialize the Reviews dropdown menu.
+ */
+ async init() {
+ const url = this.$panel.querySelector('footer a').href + '&ctype=json';
+ const response = await fetch(url, { credentials: 'same-origin' });
+ const _requests = response.ok ? await response.json() : [];
+
+ if (!response.ok) {
+ this.$loading.innerHTML = 'Couldn’t load requests for you.<br>Please try again later.';
+
+ return;
+ }
+
+ if (!_requests.length) {
+ this.$loading.className = 'empty';
+ this.$loading.innerHTML = 'You’re all caught up!';
+
+ return;
+ }
+
+ const requests = [];
+ const $ul = this.$panel.querySelector('ul');
+ const $fragment = document.createDocumentFragment();
+
+ // Sort requests from new to old, then group reviews/feedbacks asked by the same person in the same bug
+ _requests.reverse().forEach(_req => {
+ const dup_index = requests.findIndex(req => req.requester === _req.requester
+ && req.bug_id === _req.bug_id && req.type === _req.type && req.attach_id && _req.attach_id);
+
+ if (dup_index > -1) {
+ requests[dup_index].dup_count++;
+ } else {
+ _req.dup_count = 1;
+ requests.push(_req);
+ }
+ });
+
+ // Show up to 20 newest requests
+ requests.slice(0, 20).forEach(req => {
+ const $li = document.createElement('li');
+ const [, name, email] = req.requester.match(/^(.*)\s<(.*)>$/);
+ const pretty_name = name.replace(/([\[\(<‹].*?[›>\)\]]|\:[\w\-]+|\s+\-\s+.*)/g, '').trim();
+ const link = req.attach_id && req.dup_count === 1
+ ? `attachment.cgi?id=${req.attach_id}&amp;action=edit` : `show_bug.cgi?id=${req.bug_id}`;
+
+ $li.setAttribute('role', 'none');
+ $li.innerHTML = `<a href="${link}" role="menuitem" tabindex="-1" data-type="${req.type}">
+ <img src="https://secure.gravatar.com/avatar/${md5(email)}?d=mm&amp;size=64" alt="">
+ <label><strong>${pretty_name.htmlEncode()}</strong> asked for your
+ ${(req.type === 'needinfo' ? 'info' : req.type)} ${(req.attach_id ? 'on' : '')}
+ ${(req.attach_id && req.ispatch ? (req.dup_count > 1 ? `${req.dup_count} patches` : 'a patch') : '')}
+ ${(req.attach_id && !req.ispatch ? (req.dup_count > 1 ? `${req.dup_count} files` : 'a file') : '')}
+ in <strong>Bug ${req.bug_id} &ndash; ${req.bug_summary.htmlEncode()}</strong>.</label>
+ <time datetime="${req.created}">${timeAgo(new Date(req.created))}</time></a>`;
+ $fragment.appendChild($li);
+ });
+
+ this.$loading.remove();
+ $ul.appendChild($fragment);
+ $ul.hidden = false;
+ }
+}
+
+window.addEventListener('DOMContentLoaded', () => new Bugzilla.Review.Badge(), { once: true });
diff --git a/extensions/Review/web/styles/badge.css b/extensions/Review/web/styles/badge.css
index 53f539df8..9a6e14a8c 100644
--- a/extensions/Review/web/styles/badge.css
+++ b/extensions/Review/web/styles/badge.css
@@ -5,7 +5,32 @@
* This Source Code Form is "Incompatible With Secondary Licenses", as
* defined by the Mozilla Public License, v. 2.0. */
-#header .badge {
+#header-requests {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ margin: 0 4px !important;
+ width: 32px;
+ height: 32px;
+}
+
+#header-requests-menu-button .icon {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 24px;
+ height: 24px;
+ background: #BBB;
+ color: #FFF;
+ border-radius: 50%;
+}
+
+#header-requests-menu-button .icon::before {
+ font-size: 16px;
+ content: '\E7F4';
+}
+
+#header-requests-menu-button .badge {
display: flex;
align-items: center;
justify-content: center;