From e5131c86c22c5577c0e27db04481084caf635dc5 Mon Sep 17 00:00:00 2001 From: Byron Jones Date: Tue, 14 Apr 2015 13:20:00 +0800 Subject: Bug 1146767: update relative dates without refreshing the page --- extensions/BugModal/Extension.pm | 16 ++- extensions/BugModal/lib/ActivityStream.pm | 15 +-- extensions/BugModal/lib/Util.pm | 31 +++++ .../template/en/default/bug_modal/flags.html.tmpl | 3 +- .../template/en/default/bug_modal/header.html.tmpl | 1 + .../en/default/bug_modal/rel_time.html.tmpl | 2 +- extensions/BugModal/web/bug_modal.js | 2 +- extensions/BugModal/web/time_ago.js | 142 +++++++++++++++++++++ 8 files changed, 196 insertions(+), 16 deletions(-) create mode 100644 extensions/BugModal/lib/Util.pm create mode 100644 extensions/BugModal/web/time_ago.js (limited to 'extensions/BugModal') diff --git a/extensions/BugModal/Extension.pm b/extensions/BugModal/Extension.pm index 04d7cb567..0bc96e730 100644 --- a/extensions/BugModal/Extension.pm +++ b/extensions/BugModal/Extension.pm @@ -14,12 +14,13 @@ use base qw(Bugzilla::Extension); use Bugzilla::Extension::BugModal::ActivityStream; use Bugzilla::Extension::BugModal::MonkeyPatches; +use Bugzilla::Extension::BugModal::Util qw(date_str_to_time); use Bugzilla::Constants; use Bugzilla::User::Setting; use Bugzilla::Util qw(trick_taint datetime_from html_quote); use List::MoreUtils qw(any); use Template::Stash; -use Time::Duration; +use Time::Duration qw(ago); our $VERSION = '1'; @@ -62,7 +63,7 @@ sub template_after_create { my ($timestamp) = @_; my $datetime = datetime_from($timestamp) // return $timestamp; - return ago(time() - $datetime->epoch); + return ago(abs(time() - $datetime->epoch)); }; }, 1 ); @@ -85,6 +86,17 @@ sub template_after_create { }, 1 ); + # parse date string and output epoch + $context->define_filter( + epoch => sub { + my ($context) = @_; + return sub { + my ($date_str) = @_; + return date_str_to_time($date_str); + }; + }, 1 + ); + # flatten a list of hashrefs to a list of values # eg. logins = users.pluck("login") $context->define_vmethod( diff --git a/extensions/BugModal/lib/ActivityStream.pm b/extensions/BugModal/lib/ActivityStream.pm index dae6b8ba2..97edf2ee6 100644 --- a/extensions/BugModal/lib/ActivityStream.pm +++ b/extensions/BugModal/lib/ActivityStream.pm @@ -12,9 +12,9 @@ package Bugzilla::Bug; use strict; use warnings; +use Bugzilla::Extension::BugModal::Util qw(date_str_to_time); use Bugzilla::User; use Bugzilla::Constants; -use Time::Local; # returns an arrayref containing all changes to the bug - comments, field # changes, and duplicates @@ -51,7 +51,7 @@ sub activity_stream { _add_activities_to_stream($self, $stream); _add_duplicates_to_stream($self, $stream); - my $base_time = _sql_date_to_time($self->creation_ts); + my $base_time = date_str_to_time($self->creation_ts); foreach my $change_set (@$stream) { $change_set->{id} = $change_set->{comment} ? 'c' . $change_set->{comment}->count @@ -101,7 +101,7 @@ sub _add_comments_to_stream { next if $comment->type == CMT_HAS_DUPE; next if $comment->is_private && !($user->is_insider || $user->id == $comment->author->id); next if $comment->body eq '' && ($comment->work_time - 0) != 0 && !$user->is_timetracker; - _add_comment_to_stream($stream, _sql_date_to_time($comment->creation_ts), $comment->author->id, $comment); + _add_comment_to_stream($stream, date_str_to_time($comment->creation_ts), $comment->author->id, $comment); } } @@ -237,7 +237,7 @@ sub _add_activities_to_stream { } } - _add_activity_to_stream($stream, _sql_date_to_time($operation->{when}), $operation->{who}->id, $operation); + _add_activity_to_stream($stream, date_str_to_time($operation->{when}), $operation->{who}->id, $operation); } # prime the visible-bugs cache @@ -274,11 +274,4 @@ sub _add_duplicates_to_stream { } } -sub _sql_date_to_time { - my ($date) = @_; - $date =~ /^(\d{4})[\.\-](\d{2})[\.\-](\d{2}) (\d{2}):(\d{2}):(\d{2})$/ - or die "internal error: invalid date '$date'"; - return timelocal($6, $5, $4, $3, $2 - 1, $1 - 1900); -} - 1; diff --git a/extensions/BugModal/lib/Util.pm b/extensions/BugModal/lib/Util.pm new file mode 100644 index 000000000..daca86518 --- /dev/null +++ b/extensions/BugModal/lib/Util.pm @@ -0,0 +1,31 @@ +# 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. + +package Bugzilla::Extension::BugModal::Util; +use strict; +use warnings; + +use base qw(Exporter); +our @EXPORT_OK = qw(date_str_to_time); + +use feature 'state'; + +use Bugzilla::Util qw(datetime_from); +use DateTime::TimeZone; +use Time::Local qw(timelocal); + +sub date_str_to_time { + my ($date) = @_; + # avoid creating a DateTime object + if ($date =~ /^(\d{4})[\.\-](\d{2})[\.\-](\d{2}) (\d{2}):(\d{2}):(\d{2})$/) { + return timelocal($6, $5, $4, $3, $2 - 1, $1 - 1900); + } + state $tz //= DateTime::TimeZone->new( name => 'local' ); + return datetime_from($date, $tz)->epoch; +} + +1; diff --git a/extensions/BugModal/template/en/default/bug_modal/flags.html.tmpl b/extensions/BugModal/template/en/default/bug_modal/flags.html.tmpl index 70e7e5c3d..77e04b882 100644 --- a/extensions/BugModal/template/en/default/bug_modal/flags.html.tmpl +++ b/extensions/BugModal/template/en/default/bug_modal/flags.html.tmpl @@ -74,7 +74,8 @@ - + [% f.type.name FILTER html %] 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 70c98641f..9e3742ef8 100644 --- a/extensions/BugModal/template/en/default/bug_modal/header.html.tmpl +++ b/extensions/BugModal/template/en/default/bug_modal/header.html.tmpl @@ -49,6 +49,7 @@ # assets javascript_urls.push( + "extensions/BugModal/web/time_ago.js", "extensions/BugModal/web/bug_modal.js", "extensions/BugModal/web/ZeroClipboard/ZeroClipboard.min.js", "js/field.js", diff --git a/extensions/BugModal/template/en/default/bug_modal/rel_time.html.tmpl b/extensions/BugModal/template/en/default/bug_modal/rel_time.html.tmpl index 3b31dedeb..26a1b709b 100644 --- a/extensions/BugModal/template/en/default/bug_modal/rel_time.html.tmpl +++ b/extensions/BugModal/template/en/default/bug_modal/rel_time.html.tmpl @@ -10,7 +10,7 @@ # ts: timestamp #%] - + [%~ IF content.defined; content; diff --git a/extensions/BugModal/web/bug_modal.js b/extensions/BugModal/web/bug_modal.js index bd529fab1..87d173680 100644 --- a/extensions/BugModal/web/bug_modal.js +++ b/extensions/BugModal/web/bug_modal.js @@ -134,7 +134,7 @@ $(function() { }); // use non-native tooltips for relative times and bug summaries - $('.rel-time, .bz_bug_link').tooltip({ + $('.rel-time, .rel-time-title, .bz_bug_link').tooltip({ position: { my: "left top+8", at: "left bottom", collision: "flipfit" }, show: { effect: 'none' }, hide: { effect: 'none' } diff --git a/extensions/BugModal/web/time_ago.js b/extensions/BugModal/web/time_ago.js new file mode 100644 index 000000000..59f12692a --- /dev/null +++ b/extensions/BugModal/web/time_ago.js @@ -0,0 +1,142 @@ +/* + * this is a port of perl's Time::Duration module, which has the following + * license: + * + * Copyright 2006, Sean M. Burke C, all rights reserved. This + * program is free software; you can redistribute it and/or modify it under the + * same terms as Perl itself. + * + * This program is distributed in the hope that it will be useful, but without + * any warranty; without even the implied warranty of merchantability or + * fitness for a particular purpose. + */ + +$(function() { + 'use strict'; + + function separate(seconds) { + // breakdown of seconds into units, starting with the most significant + + var remainder = seconds; + var tmp; + var wheel = []; + + // years + tmp = Math.floor(remainder / (365 * 24 * 60 * 60)); + wheel.push([ 'year', tmp, 1000000000 ]); + remainder -= tmp * (365 * 24 * 60 * 60); + + // days + tmp = Math.floor(remainder / (24 * 60 * 60)); + wheel.push([ 'day', tmp, 365 ]); + remainder -= tmp * (24 * 60 * 60); + + // hours + tmp = Math.floor(remainder / (60 * 60)); + wheel.push([ 'hour', tmp, 24 ]); + remainder -= tmp * (60 * 60); + + // minutes + tmp = Math.floor(remainder / 60); + wheel.push([ 'minute', tmp, 60 ]); + remainder -= tmp * 60; + + // seconds + wheel.push([ 'second', Math.floor(remainder), 60 ]); + return wheel; + } + + function approximate(precision, wheel) { + // now nudge the wheels into an acceptably (im)precise configuration + FIX: do { + // constraints for leaving this block: + // 1) number of nonzero wheels must be <= precision + // 2) no wheels can be improperly expressed (like having "60" for mins) + + var nonzero_count = 0; + var improperly_expressed = -1; + + for (var i = 0; i < wheel.length; i++) { + var tmp = wheel[i]; + if (tmp[1] == 0) { + continue; + } + ++nonzero_count; + if (i == 0) { + // the years wheel is never improper or over any limit; skip + continue; + } + + if (nonzero_count > precision) { + // this is one nonzero wheel too many + + // incr previous wheel if we're big enough + if (tmp[1] >= (tmp[tmp.length - 1] / 2)) { + ++wheel[i - 1][1]; + } + + // reset this and subsequent wheels to 0 + for (var j = i; j < wheel.length; j++) { + wheel[j][1] = 0; + } + + // start over + continue FIX; + + } else if (tmp[1] >= tmp[tmp.length - 1]) { + // it's an improperly expressed wheel (like "60" on the mins wheel) + improperly_expressed = i; + } + } + + if (improperly_expressed != -1) { + // only fix the least-significant improperly expressed wheel (at a time) + ++wheel[improperly_expressed - 1][1]; + wheel[improperly_expressed][1] = 0; + + // start over + continue FIX; + } + + // otherwise there's not too many nonzero wheels, and there's no + //improperly expressed wheels, so fall thru... + } while(0); + + return wheel; + } + + function render(wheel) { + var parts = []; + wheel.forEach(function(element, index) { + if (element[1] > 0) { + parts.push(element[1] + ' ' + element[0]); + if (element[1] != 1) { + parts[parts.length - 1] += 's'; + } + } + }); + + if (parts.length == 0) { + return "just now"; + } + parts[parts.length - 1] += ' ago'; + if (parts.length == 1) { + return parts[0]; + } + if (parts.length == 2) { + return parts[0] + ' and ' + parts[1]; + } + parts[parts.length - 1] = 'and ' + parts[parts.length - 1]; + return parts.join(', '); + } + + window.setInterval(function() { + var now = Math.floor(new Date().getTime() / 1000); + $('.rel-time').each(function() { + $(this).text(render(approximate(2, separate(now - $(this).data('time'))))); + }); + $('.rel-time-title').each(function() { + $(this).attr('title', render(approximate(2, separate(now - $(this).data('time'))))); + }); + }, 60000); +}); -- cgit v1.2.3-24-g4f1b