diff options
-rw-r--r-- | extensions/BugModal/template/en/default/bug_modal/header.html.tmpl | 8 | ||||
-rw-r--r-- | extensions/BugModal/web/bug_modal.css | 37 | ||||
-rw-r--r-- | extensions/BugModal/web/bug_modal.js | 87 |
3 files changed, 126 insertions, 6 deletions
diff --git a/extensions/BugModal/template/en/default/bug_modal/header.html.tmpl b/extensions/BugModal/template/en/default/bug_modal/header.html.tmpl index a21e9c268..c5ae78af3 100644 --- a/extensions/BugModal/template/en/default/bug_modal/header.html.tmpl +++ b/extensions/BugModal/template/en/default/bug_modal/header.html.tmpl @@ -83,12 +83,7 @@ [%# update last-visited %] [% IF user.id && user.is_involved_in_bug(bug) %] - $(function() { - bugzilla_ajax({ - url: 'rest/bug_user_last_visit/[% bug.id FILTER none %]', - type: 'POST' - }); - }); + document.addEventListener('DOMContentLoaded', () => show_new_changes_indicator(), { once: true }); [% END %] [%# expose useful data to js %] @@ -101,6 +96,7 @@ is_insider: [% user.is_insider ? "true" : "false" %], is_timetracker: [% user.is_timetracker ? "true" : "false" %], can_tag: [% user.can_tag_comments ? "true" : "false" %], + timezone: '[% user.timezone.name FILTER js %]', settings: { quote_replies: '[% user.settings.quote_replies.value FILTER js %]', zoom_textareas: [% user.settings.zoom_textareas.value == "on" ? "true" : "false" %], diff --git a/extensions/BugModal/web/bug_modal.css b/extensions/BugModal/web/bug_modal.css index eeba78d74..9a3978b14 100644 --- a/extensions/BugModal/web/bug_modal.css +++ b/extensions/BugModal/web/bug_modal.css @@ -532,6 +532,43 @@ td.flag-requestee { text-align: right; } +.new-changes-link { + margin: 8px 0; + border-radius: 4px; + padding: 4px; + font-size: 12px; + text-align: center; + color: #FFF; + background: #277AC1; + cursor: pointer; +} + +.new-changes-separator { + position: relative; + margin: 16px -8px; + height: 0; + border-top: 1px solid #C00; + -moz-user-select: none; + -webkit-user-select: none; + user-select: none; +} + +.new-changes-separator span { + display: inline-block; + position: absolute; + top: -10px; + right: 16px; + border: 1px solid #CCC; + border-radius: 4px; + padding: 0 4px; + height: 16px; + font-size: 10px; + line-height: 16px; + text-transform: uppercase; + color: #C00; + background-color: #FFF; +} + .change-set { clear: both; -webkit-box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.1); diff --git a/extensions/BugModal/web/bug_modal.js b/extensions/BugModal/web/bug_modal.js index 300ebd3ae..ef015d3f9 100644 --- a/extensions/BugModal/web/bug_modal.js +++ b/extensions/BugModal/web/bug_modal.js @@ -1346,6 +1346,93 @@ function confirmUnsafeURL(url) { 'The full URL is:\n\n' + url + '\n\nContinue?'); } +function show_new_changes_indicator() { + const url = `rest/bug_user_last_visit/${BUGZILLA.bug_id}`; + + // Get the last visited timestamp + bugzilla_ajax({ url }, data => { + // Save the current timestamp + bugzilla_ajax({ url, type: 'POST' }); + + if (!data[0] || !data[0].last_visit_ts) { + return; + } + + const last_visit_ts = new Date(data[0].last_visit_ts); + const new_changes = [...document.querySelectorAll('main .change-set')].filter($change => { + // Exclude hidden CC changes + return $change.clientHeight > 0 && + new Date($change.querySelector('[data-time]').getAttribute('data-time') * 1000) > last_visit_ts; + }); + + if (new_changes.length === 0) { + return; + } + + const now = new Date(); + const date_locale = document.querySelector('html').lang; + const date_options = { + year: 'numeric', + month: 'long', + day: 'numeric', + hour: 'numeric', + minute: 'numeric', + hour12: false, + timeZone: BUGZILLA.user.timezone, + timeZoneName: 'short', + }; + + if (last_visit_ts.getFullYear() === now.getFullYear()) { + delete date_options.year; + + if (last_visit_ts.getMonth() === now.getMonth() && last_visit_ts.getDate() === now.getDate()) { + delete date_options.month; + delete date_options.day; + } + } + + const $link = document.createElement('div'); + const $separator = document.createElement('div'); + const comments_count = new_changes.filter($change => !!$change.querySelector('.comment')).length; + const changes_count = new_changes.length - comments_count; + const date_attr = last_visit_ts.toISOString(); + const date_label = last_visit_ts.toLocaleString(date_locale, date_options); + + // Insert a link + $link.className = 'new-changes-link'; + $link.innerHTML = + (c => c === 0 ? '' : (c === 1 ? `${c} new comment` : `${c} new comments`))(comments_count) + + (comments_count > 0 && changes_count > 0 ? ', ' : '') + + (c => c === 0 ? '' : (c === 1 ? `${c} new change` : `${c} new changes`))(changes_count) + + ` since <time datetime="${date_attr}">${date_label}</time>`; + $link.addEventListener('click', () => { + $link.remove(); + scroll_element_into_view($separator); + }, { once: true }); + document.querySelector('#changeform').insertAdjacentElement('beforebegin', $link); + + // Insert a separator + $separator.className = 'new-changes-separator'; + $separator.innerHTML = '<span>New</span>'; + new_changes[0].insertAdjacentElement('beforebegin', $separator); + + // Remove the link once the separator goes into the viewport + if ('IntersectionObserver' in window) { + const observer = new IntersectionObserver(entries => entries.forEach(entry => { + if (entry.intersectionRatio > 0) { + observer.unobserve($separator); + $link.remove(); + } + }), { root: $separator.offsetParent }); + + observer.observe($separator); + } + + // TODO: Enable auto-scroll once the modal page layout is optimized + // scroll_element_into_view($separator); + }); +} + // fix url after bug creation/update if (history && history.replaceState) { let bug_id = BUGZILLA.bug_id; |