Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Fuzzy time #341

Closed
wants to merge 20 commits into from

5 participants

@andrew-smith

Feature request #141 - Live update fuzzy time.

Things to note:

  • Server side processing writes out "37 seconds" (example) and client side will change it to "about a minute ago". If user has a slow loading time then they will notice a jump when the plugin runs
  • The messaging pages don't have the dates in elements
r2/example.ini
@@ -14,7 +14,7 @@ template_debug = false
reload_templates = true
# use uncompressed static files (out of /static/js and /static/css)
# rather than compressed files out of /static (for development if true)
-uncompressedJS = true
+uncompressedJS = false
@kemitche Collaborator
kemitche added a note

Be sure to undo this! :)

Should there be some checks in place before accessing the r.strings array then? In dev mode it will throw a few errors because r.strings would not have been defined.

Running with uncompressedJS == True worked for me. Perhaps you forgot to run make?

Reverted back to true

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@andrew-smith andrew-smith commented on the diff
r2/r2/templates/utils.html
@@ -516,7 +516,7 @@
<%def name="timestamp(date, since=None)">
## todo: use pubdate attribute once things are <article> tags.
## note: comment and link templates will pass a CachedVariable stub as since.
- <time title="${long_datetime(date)}" datetime="${date.isoformat()}">
+ <time class="fuzzy-time" title="${long_datetime(date)}" datetime="${date.isoformat()}">
${unsafe(since or timesince(date))}

Could place the "ago" string here so that pages are rendered with "posted 37 seconds ago" and therefore will be a less noticeable jump when the plugin changes it to "about a minute ago"

It looks like timesince itself has support for adding an "ago" suffix if you pass bare=False. It might make sense to add support for a language-specific prefix there to mirror timeago. Then you could just pass bare=False in this function.

What do you think @kemitche? Is a language-specific prefix and suffix sufficient for translators to handle fuzzy times, taking them out of the containing translatable string? (see the strings changes above)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@andrew-smith andrew-smith commented on the diff
r2/r2/templates/utils.html
@@ -516,7 +516,7 @@
<%def name="timestamp(date, since=None)">
## todo: use pubdate attribute once things are <article> tags.
## note: comment and link templates will pass a CachedVariable stub as since.
- <time title="${long_datetime(date)}" datetime="${date.isoformat()}">
+ <time class="fuzzy-time" title="${long_datetime(date)}" datetime="${date.isoformat()}">

Now that I think about it... because all the elements will have a datetime field - does it need class="fuzzy-time" ?

I think it is sensible to include a class to avoid any other time elements being unintentionally affected someday.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
r2/r2/public/static/js/reddit.js
@@ -1235,6 +1235,28 @@ $(function() {
$("#shortlink-text").click(function() {
$(this).select();
});
+
+ /* Load fuzzy time i18n strings */
+ $.timeago.settings.strings = {
+ prefixAgo: null,

Can you add the prefix strings to strings.py so that they can be used by localizations?

Yep! :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
r2/r2/public/static/js/reddit.js
((10 lines not shown))
+ suffixFromNow: r.strings.ta_suffixFromNow,
+ seconds: r.strings.ta_seconds,
+ minute: r.strings.ta_minute,
+ minutes: r.strings.ta_minutes,
+ hour: r.strings.ta_hour,
+ hours: r.strings.ta_hours,
+ day: r.strings.ta_day,
+ days: r.strings.ta_days,
+ month: r.strings.ta_month,
+ months: r.strings.ta_months,
+ year: r.strings.ta_year,
+ years: r.strings.ta_years
+ };
+
+ /* Init fuzzy time on time elements */
+ $("time.fuzzy-time").timeago();

I've been working on overhauling the JS code organization, and the mishmash of initialization code in reddit.js is best considered legacy. Could you please move this initialization code to its own file/object like r.timeago (see r.analytics as an example) and init it at the bottom of base.js?

Would you prefer the file name to be r.timeago.js or just timeago.js?
The code for timeago is in lib/jquery.timeago.js - should I move that into the same file as the init function or leave it as it is?

I'd suggest defining an object named r.timeago in a file named something like fuzzytime.js (to match the class name). Since the fuzzy time behavior is being provided by a library, I'd leave it as is in the js/lib dir.

Cool - that's done now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@chromakode

Looks good, thanks!

@andrew-smith

Interesting new library: http://momentjs.com/ (Found it on reddit!)

@xPaw

Looks good :thumbsup:

@spladug
Owner

I'm going to close this pull request as I think it'll work better if we build full-site live timestamps off of https://github.com/reddit/reddit-plugin-liveupdate/blob/master/reddit_liveupdate/public/static/js/timetext.js. Thank you a tonne for making this pull request as it pointed out many of the i18n-related issues we needed to take into account for such a system.

@spladug spladug closed this
@andrew-smith

Cheers for that. I had fun checking it out so that's what counts, right? ;)

I'll look into something else that I'll contribute to reddit!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Feb 9, 2012
  1. @andrew-smith
  2. @andrew-smith
  3. @andrew-smith
  4. @andrew-smith
  5. @andrew-smith
  6. @andrew-smith
  7. @andrew-smith
  8. @andrew-smith
  9. @andrew-smith
  10. @andrew-smith
  11. @andrew-smith
  12. @andrew-smith
  13. @andrew-smith
  14. @andrew-smith
  15. @andrew-smith
Commits on Feb 22, 2012
  1. @andrew-smith
Commits on Feb 23, 2012
  1. @andrew-smith
  2. @andrew-smith
  3. @andrew-smith
  4. @andrew-smith
This page is out of date. Refresh to see the latest.
View
2  r2/r2/lib/js.py
@@ -237,11 +237,13 @@ def outputs(self):
"lib/json2.js",
"lib/jquery.cookie.js",
"lib/jquery.url.js",
+ "lib/jquery.timeago.js",
"jquery.reddit.js",
"base.js",
"ui.js",
"login.js",
"analytics.js",
+ "timeago.js",
"flair.js",
"reddit.js",
"utils.js",
View
17 r2/r2/lib/strings.py
@@ -40,6 +40,23 @@
# dictionary is not used directly but rather is managed by the single
# StringHandler instance strings
string_dict = dict(
+
+
+ # time ago values (jquery.timeago.js)
+ ta_ago = "ago",
+ ta_suffixFromNow = "from now",
+ ta_seconds = "less than a minute",
+ ta_minute = "about a minute",
+ ta_minutes = "%d minutes",
+ ta_hour = "about an hour",
+ ta_hours = "about %d hours",
+ ta_day = "a day",
+ ta_days = "%d days",
+ ta_month = "about a month",
+ ta_months = "%d months",
+ ta_year = "about a year",
+ ta_years = "%d years",
+
banned_by = "removed by %s",
banned = "removed",
View
1  r2/r2/public/static/js/base.js
@@ -9,4 +9,5 @@ r.setup = function(config) {
$(function() {
r.login.ui.init()
r.analytics.init()
+ r.timeago.init()
})
View
150 r2/r2/public/static/js/lib/jquery.timeago.js
@@ -0,0 +1,150 @@
+/**
+ * Timeago is a jQuery plugin that makes it easy to support automatically
+ * updating fuzzy timestamps (e.g. "4 minutes ago" or "about 1 day ago").
+ *
+ * @name timeago
+ * @version 0.10.0
+ * @requires jQuery v1.2.3+
+ * @author Ryan McGeary
+ * @license MIT License - http://www.opensource.org/licenses/mit-license.php
+ *
+ * For usage and examples, visit:
+ * http://timeago.yarp.com/
+ *
+ * Copyright (c) 2008-2011, Ryan McGeary (ryanonjavascript -[at]- mcgeary [*dot*] org)
+ */
+(function($) {
+ $.timeago = function(timestamp) {
+ if (timestamp instanceof Date) {
+ return inWords(timestamp);
+ } else if (typeof timestamp === "string") {
+ return inWords($.timeago.parse(timestamp));
+ } else {
+ return inWords($.timeago.datetime(timestamp));
+ }
+ };
+ var $t = $.timeago;
+
+ $.extend($.timeago, {
+ //these will be overwritten in ../reddit.js (jquery document ready function)
+ //based on i18n settings
+ settings: {
+ refreshMillis: 60000,
+ allowFuture: false,
+ strings: {
+ prefixAgo: null,
+ prefixFromNow: null,
+ suffixAgo: "ago",
+ suffixFromNow: "from now",
+ seconds: "less than a minute",
+ minute: "about a minute",
+ minutes: "%d minutes",
+ hour: "about an hour",
+ hours: "about %d hours",
+ day: "a day",
+ days: "%d days",
+ month: "about a month",
+ months: "%d months",
+ year: "about a year",
+ years: "%d years",
+ numbers: []
+ }
+ },
+ inWords: function(distanceMillis) {
+ var $l = this.settings.strings;
+ var prefix = $l.prefixAgo;
+ var suffix = $l.suffixAgo;
+ if (this.settings.allowFuture) {
+ if (distanceMillis < 0) {
+ prefix = $l.prefixFromNow;
+ suffix = $l.suffixFromNow;
+ }
+ }
+
+ var seconds = Math.abs(distanceMillis) / 1000;
+ var minutes = seconds / 60;
+ var hours = minutes / 60;
+ var days = hours / 24;
+ var years = days / 365;
+
+ function substitute(stringOrFunction, number) {
+ var string = $.isFunction(stringOrFunction) ? stringOrFunction(number, distanceMillis) : stringOrFunction;
+ var value = ($l.numbers && $l.numbers[number]) || number;
+ return string.replace(/%d/i, value);
+ }
+
+ var words = seconds < 45 && substitute($l.seconds, Math.round(seconds)) ||
+ seconds < 90 && substitute($l.minute, 1) ||
+ minutes < 45 && substitute($l.minutes, Math.round(minutes)) ||
+ minutes < 90 && substitute($l.hour, 1) ||
+ hours < 24 && substitute($l.hours, Math.round(hours)) ||
+ hours < 48 && substitute($l.day, 1) ||
+ days < 30 && substitute($l.days, Math.floor(days)) ||
+ days < 60 && substitute($l.month, 1) ||
+ days < 365 && substitute($l.months, Math.floor(days / 30)) ||
+ years < 2 && substitute($l.year, 1) ||
+ substitute($l.years, Math.floor(years));
+
+ return $.trim([prefix, words, suffix].join(" "));
+ },
+ parse: function(iso8601) {
+ var s = $.trim(iso8601);
+ s = s.replace(/\.\d\d\d+/,""); // remove milliseconds
+ s = s.replace(/-/,"/").replace(/-/,"/");
+ s = s.replace(/T/," ").replace(/Z/," UTC");
+ s = s.replace(/([\+\-]\d\d)\:?(\d\d)/," $1$2"); // -04:00 -> -0400
+ return new Date(s);
+ },
+ datetime: function(elem) {
+ // jQuery's `is()` doesn't play well with HTML5 in IE
+ var isTime = $(elem).get(0).tagName.toLowerCase() === "time"; // $(elem).is("time");
+ var iso8601 = isTime ? $(elem).attr("datetime") : $(elem).attr("title");
+ return $t.parse(iso8601);
+ }
+ });
+
+ $.fn.timeago = function() {
+ var self = this;
+ self.each(refresh);
+
+ var $s = $t.settings;
+ if ($s.refreshMillis > 0) {
+ setInterval(function() { self.each(refresh); }, $s.refreshMillis);
+ }
+ return self;
+ };
+
+ function refresh() {
+ var data = prepareData(this);
+ if (!isNaN(data.datetime)) {
+ $(this).text(inWords(data.datetime));
+ }
+ return this;
+ }
+
+ function prepareData(element) {
+ element = $(element);
+ if (!element.data("timeago")) {
+ element.data("timeago", { datetime: $t.datetime(element) });
+ /* Stops the title being overridden
+ var text = $.trim(element.text());
+ if (text.length > 0) {
+ element.attr("title", text);
+ }
+ */
+ }
+ return element.data("timeago");
+ }
+
+ function inWords(date) {
+ return $t.inWords(distance(date));
+ }
+
+ function distance(date) {
+ return (new Date().getTime() - date.getTime());
+ }
+
+ // fix for IE6 suckage
+ document.createElement("abbr");
+ document.createElement("time");
+}(jQuery));
View
1  r2/r2/public/static/js/reddit.js
@@ -1316,3 +1316,4 @@ function parse_domain(url) {
}
return domain;
}
+
View
27 r2/r2/public/static/js/timeago.js
@@ -0,0 +1,27 @@
+r.timeago = {
+
+ init: function() {
+
+ /* Load fuzzy time i18n strings */
+ $.timeago.settings.strings = {
+ prefixAgo: null,
+ prefixFromNow: null,
+ suffixAgo: r.strings.ta_ago,
+ suffixFromNow: r.strings.ta_suffixFromNow,
+ seconds: r.strings.ta_seconds,
+ minute: r.strings.ta_minute,
+ minutes: r.strings.ta_minutes,
+ hour: r.strings.ta_hour,
+ hours: r.strings.ta_hours,
+ day: r.strings.ta_day,
+ days: r.strings.ta_days,
+ month: r.strings.ta_month,
+ months: r.strings.ta_months,
+ year: r.strings.ta_year,
+ years: r.strings.ta_years
+ };
+
+ /* Init fuzzy time on time elements */
+ $("time.fuzzy-time").timeago();
+ }
+}
View
2  r2/r2/templates/comment.html
@@ -104,7 +104,7 @@
%if show:
${unsafe(self.score(thing, likes = thing.likes))}&#32;
%endif
- ${thing_timestamp(thing, thing.timesince)}&#32;${_("ago")}
+ ${thing_timestamp(thing, thing.timesince)}&#32;
%if thing.editted:
<em>*</em>&nbsp;
%endif
View
6 r2/r2/templates/link.html
@@ -202,15 +202,15 @@
<%def name="tagline()">
<%
if thing.score_fmt == Score.points:
- taglinetext = "<span>" + _("%(score)s submitted %(when)s ago") + "</span>"
+ taglinetext = "<span>" + _("%(score)s submitted %(when)s") + "</span>"
if thing.different_sr:
taglinetext += " <span>" + _("by %(author)s to %(reddit)s") + "</span>"
else:
taglinetext += " <span>" + _("by %(author)s") + "</span>"
elif thing.different_sr:
- taglinetext = _("submitted %(when)s ago by %(author)s to %(reddit)s")
+ taglinetext = _("submitted %(when)s by %(author)s to %(reddit)s")
else:
- taglinetext = _("submitted %(when)s ago by %(author)s")
+ taglinetext = _("submitted %(when)s by %(author)s")
taglinetext = taglinetext.replace(" ", "&#32;")
%>
View
2  r2/r2/templates/utils.html
@@ -516,7 +516,7 @@
<%def name="timestamp(date, since=None)">
## todo: use pubdate attribute once things are <article> tags.
## note: comment and link templates will pass a CachedVariable stub as since.
- <time title="${long_datetime(date)}" datetime="${date.isoformat()}">
+ <time class="fuzzy-time" title="${long_datetime(date)}" datetime="${date.isoformat()}">

Now that I think about it... because all the elements will have a datetime field - does it need class="fuzzy-time" ?

I think it is sensible to include a class to avoid any other time elements being unintentionally affected someday.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
${unsafe(since or timesince(date))}

Could place the "ago" string here so that pages are rendered with "posted 37 seconds ago" and therefore will be a less noticeable jump when the plugin changes it to "about a minute ago"

It looks like timesince itself has support for adding an "ago" suffix if you pass bare=False. It might make sense to add support for a language-specific prefix there to mirror timeago. Then you could just pass bare=False in this function.

What do you think @kemitche? Is a language-specific prefix and suffix sufficient for translators to handle fuzzy times, taking them out of the containing translatable string? (see the strings changes above)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
</time>
</%def>
Something went wrong with that request. Please try again.