Skip to content
This repository

Fuzzy time #341

Closed
wants to merge 20 commits into from

5 participants

Andrew Smith Pavel Neil Williams Keith Mitchell Max Goodman
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
14 14
 reload_templates = true
15 15
 # use uncompressed static files (out of /static/js and /static/css)
16 16
 # rather than compressed files out of /static (for development if true)
17  
-uncompressedJS = true
  17
+uncompressedJS = false
4
Keith Mitchell Collaborator

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.

Max Goodman Owner

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 February 09, 2012
r2/r2/templates/utils.html
@@ -516,7 +516,7 @@
516 516
 <%def name="timestamp(date, since=None)">
517 517
   ## todo: use pubdate attribute once things are <article> tags.
518 518
   ## note: comment and link templates will pass a CachedVariable stub as since.
519  
-  <time title="${long_datetime(date)}" datetime="${date.isoformat()}">
  519
+  <time class="fuzzy-time" title="${long_datetime(date)}" datetime="${date.isoformat()}">
520 520
     ${unsafe(since or timesince(date))}
2

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"

Max Goodman Owner

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 February 09, 2012
r2/r2/templates/utils.html
@@ -516,7 +516,7 @@
516 516
 <%def name="timestamp(date, since=None)">
517 517
   ## todo: use pubdate attribute once things are <article> tags.
518 518
   ## note: comment and link templates will pass a CachedVariable stub as since.
519  
-  <time title="${long_datetime(date)}" datetime="${date.isoformat()}">
  519
+  <time class="fuzzy-time" title="${long_datetime(date)}" datetime="${date.isoformat()}">
2

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

Max Goodman Owner

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() {
1235 1235
         $("#shortlink-text").click(function() {
1236 1236
             $(this).select();
1237 1237
         });
  1238
+        
  1239
+        /* Load fuzzy time i18n strings */
  1240
+        $.timeago.settings.strings = {
  1241
+            prefixAgo: null,
3
Max Goodman Owner

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

Max Goodman Owner

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))
  1244
+            suffixFromNow: r.strings.ta_suffixFromNow,
  1245
+            seconds: r.strings.ta_seconds,
  1246
+            minute: r.strings.ta_minute,
  1247
+            minutes: r.strings.ta_minutes,
  1248
+            hour: r.strings.ta_hour,
  1249
+            hours: r.strings.ta_hours,
  1250
+            day: r.strings.ta_day,
  1251
+            days: r.strings.ta_days,
  1252
+            month: r.strings.ta_month,
  1253
+            months: r.strings.ta_months,
  1254
+            year: r.strings.ta_year,
  1255
+            years: r.strings.ta_years
  1256
+        };
  1257
+        
  1258
+        /* Init fuzzy time on time elements */
  1259
+        $("time.fuzzy-time").timeago();
4
Max Goodman Owner

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?

Max Goodman Owner

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
Max Goodman
Owner

Looks good, thanks!

Andrew Smith

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

Pavel

Looks good :thumbsup:

Neil Williams
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.

Neil Williams spladug closed this August 30, 2013
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
This page is out of date. Refresh to see the latest.
2  r2/r2/lib/js.py
@@ -237,11 +237,13 @@ def outputs(self):
237 237
     "lib/json2.js",
238 238
     "lib/jquery.cookie.js",
239 239
     "lib/jquery.url.js",
  240
+    "lib/jquery.timeago.js",    
240 241
     "jquery.reddit.js",
241 242
     "base.js",
242 243
     "ui.js",
243 244
     "login.js",
244 245
     "analytics.js",
  246
+    "timeago.js",
245 247
     "flair.js",
246 248
     "reddit.js",
247 249
     "utils.js",
17  r2/r2/lib/strings.py
@@ -40,6 +40,23 @@
40 40
 # dictionary is not used directly but rather is managed by the single
41 41
 # StringHandler instance strings
42 42
 string_dict = dict(
  43
+           
  44
+
  45
+    # time ago values (jquery.timeago.js)
  46
+    ta_ago = "ago",
  47
+    ta_suffixFromNow = "from now",
  48
+    ta_seconds =  "less than a minute",
  49
+    ta_minute =  "about a minute",
  50
+    ta_minutes =  "%d minutes",
  51
+    ta_hour =  "about an hour",
  52
+    ta_hours =  "about %d hours",
  53
+    ta_day = "a day",
  54
+    ta_days =  "%d days",
  55
+    ta_month =  "about a month",
  56
+    ta_months =  "%d months",
  57
+    ta_year =  "about a year",
  58
+    ta_years =  "%d years",
  59
+
43 60
 
44 61
     banned_by = "removed by %s",
45 62
     banned    = "removed",
1  r2/r2/public/static/js/base.js
@@ -9,4 +9,5 @@ r.setup = function(config) {
9 9
 $(function() {
10 10
     r.login.ui.init()
11 11
     r.analytics.init()
  12
+    r.timeago.init()
12 13
 })
150  r2/r2/public/static/js/lib/jquery.timeago.js
... ...
@@ -0,0 +1,150 @@
  1
+/**
  2
+ * Timeago is a jQuery plugin that makes it easy to support automatically
  3
+ * updating fuzzy timestamps (e.g. "4 minutes ago" or "about 1 day ago").
  4
+ *
  5
+ * @name timeago
  6
+ * @version 0.10.0
  7
+ * @requires jQuery v1.2.3+
  8
+ * @author Ryan McGeary
  9
+ * @license MIT License - http://www.opensource.org/licenses/mit-license.php
  10
+ *
  11
+ * For usage and examples, visit:
  12
+ * http://timeago.yarp.com/
  13
+ *
  14
+ * Copyright (c) 2008-2011, Ryan McGeary (ryanonjavascript -[at]- mcgeary [*dot*] org)
  15
+ */
  16
+(function($) {
  17
+  $.timeago = function(timestamp) {
  18
+    if (timestamp instanceof Date) {
  19
+      return inWords(timestamp);
  20
+    } else if (typeof timestamp === "string") {
  21
+      return inWords($.timeago.parse(timestamp));
  22
+    } else {
  23
+      return inWords($.timeago.datetime(timestamp));
  24
+    }
  25
+  };
  26
+  var $t = $.timeago;
  27
+
  28
+  $.extend($.timeago, {
  29
+    //these will be overwritten in ../reddit.js (jquery document ready function)
  30
+    //based on i18n settings
  31
+    settings: {
  32
+      refreshMillis: 60000,
  33
+      allowFuture: false,
  34
+      strings: {
  35
+        prefixAgo: null,
  36
+        prefixFromNow: null,
  37
+        suffixAgo: "ago",
  38
+        suffixFromNow: "from now",
  39
+        seconds: "less than a minute",
  40
+        minute: "about a minute",
  41
+        minutes: "%d minutes",
  42
+        hour: "about an hour",
  43
+        hours: "about %d hours",
  44
+        day: "a day",
  45
+        days: "%d days",
  46
+        month: "about a month",
  47
+        months: "%d months",
  48
+        year: "about a year",
  49
+        years: "%d years",
  50
+        numbers: []
  51
+      }
  52
+    },
  53
+    inWords: function(distanceMillis) {
  54
+      var $l = this.settings.strings;
  55
+      var prefix = $l.prefixAgo;
  56
+      var suffix = $l.suffixAgo;
  57
+      if (this.settings.allowFuture) {
  58
+        if (distanceMillis < 0) {
  59
+          prefix = $l.prefixFromNow;
  60
+          suffix = $l.suffixFromNow;
  61
+        }
  62
+      }
  63
+
  64
+      var seconds = Math.abs(distanceMillis) / 1000;
  65
+      var minutes = seconds / 60;
  66
+      var hours = minutes / 60;
  67
+      var days = hours / 24;
  68
+      var years = days / 365;
  69
+
  70
+      function substitute(stringOrFunction, number) {
  71
+        var string = $.isFunction(stringOrFunction) ? stringOrFunction(number, distanceMillis) : stringOrFunction;
  72
+        var value = ($l.numbers && $l.numbers[number]) || number;
  73
+        return string.replace(/%d/i, value);
  74
+      }
  75
+
  76
+      var words = seconds < 45 && substitute($l.seconds, Math.round(seconds)) ||
  77
+        seconds < 90 && substitute($l.minute, 1) ||
  78
+        minutes < 45 && substitute($l.minutes, Math.round(minutes)) ||
  79
+        minutes < 90 && substitute($l.hour, 1) ||
  80
+        hours < 24 && substitute($l.hours, Math.round(hours)) ||
  81
+        hours < 48 && substitute($l.day, 1) ||
  82
+        days < 30 && substitute($l.days, Math.floor(days)) ||
  83
+        days < 60 && substitute($l.month, 1) ||
  84
+        days < 365 && substitute($l.months, Math.floor(days / 30)) ||
  85
+        years < 2 && substitute($l.year, 1) ||
  86
+        substitute($l.years, Math.floor(years));
  87
+
  88
+      return $.trim([prefix, words, suffix].join(" "));
  89
+    },
  90
+    parse: function(iso8601) {
  91
+      var s = $.trim(iso8601);
  92
+      s = s.replace(/\.\d\d\d+/,""); // remove milliseconds
  93
+      s = s.replace(/-/,"/").replace(/-/,"/");
  94
+      s = s.replace(/T/," ").replace(/Z/," UTC");
  95
+      s = s.replace(/([\+\-]\d\d)\:?(\d\d)/," $1$2"); // -04:00 -> -0400
  96
+      return new Date(s);
  97
+    },
  98
+    datetime: function(elem) {
  99
+      // jQuery's `is()` doesn't play well with HTML5 in IE
  100
+      var isTime = $(elem).get(0).tagName.toLowerCase() === "time"; // $(elem).is("time");
  101
+      var iso8601 = isTime ? $(elem).attr("datetime") : $(elem).attr("title");
  102
+      return $t.parse(iso8601);
  103
+    }
  104
+  });
  105
+
  106
+  $.fn.timeago = function() {
  107
+    var self = this;
  108
+    self.each(refresh);
  109
+
  110
+    var $s = $t.settings;
  111
+    if ($s.refreshMillis > 0) {
  112
+      setInterval(function() { self.each(refresh); }, $s.refreshMillis);
  113
+    }
  114
+    return self;
  115
+  };
  116
+
  117
+  function refresh() {
  118
+    var data = prepareData(this);
  119
+    if (!isNaN(data.datetime)) {
  120
+      $(this).text(inWords(data.datetime));
  121
+    }
  122
+    return this;
  123
+  }
  124
+
  125
+  function prepareData(element) {
  126
+    element = $(element);
  127
+    if (!element.data("timeago")) {
  128
+      element.data("timeago", { datetime: $t.datetime(element) });
  129
+      /* Stops the title being overridden 
  130
+      var text = $.trim(element.text());
  131
+      if (text.length > 0) {
  132
+        element.attr("title", text);
  133
+      }
  134
+      */
  135
+    }
  136
+    return element.data("timeago");
  137
+  }
  138
+
  139
+  function inWords(date) {
  140
+    return $t.inWords(distance(date));
  141
+  }
  142
+
  143
+  function distance(date) {
  144
+    return (new Date().getTime() - date.getTime());
  145
+  }
  146
+
  147
+  // fix for IE6 suckage
  148
+  document.createElement("abbr");
  149
+  document.createElement("time");
  150
+}(jQuery));
1  r2/r2/public/static/js/reddit.js
@@ -1316,3 +1316,4 @@ function parse_domain(url) {
1316 1316
     }
1317 1317
     return domain;
1318 1318
 }
  1319
+
27  r2/r2/public/static/js/timeago.js
... ...
@@ -0,0 +1,27 @@
  1
+r.timeago = {
  2
+
  3
+    init: function() {
  4
+
  5
+        /* Load fuzzy time i18n strings */
  6
+        $.timeago.settings.strings = {
  7
+            prefixAgo: null,
  8
+            prefixFromNow: null,
  9
+            suffixAgo: r.strings.ta_ago,
  10
+            suffixFromNow: r.strings.ta_suffixFromNow,
  11
+            seconds: r.strings.ta_seconds,
  12
+            minute: r.strings.ta_minute,
  13
+            minutes: r.strings.ta_minutes,
  14
+            hour: r.strings.ta_hour,
  15
+            hours: r.strings.ta_hours,
  16
+            day: r.strings.ta_day,
  17
+            days: r.strings.ta_days,
  18
+            month: r.strings.ta_month,
  19
+            months: r.strings.ta_months,
  20
+            year: r.strings.ta_year,
  21
+            years: r.strings.ta_years
  22
+        };
  23
+        
  24
+        /* Init fuzzy time on time elements */
  25
+        $("time.fuzzy-time").timeago();
  26
+    }
  27
+}
2  r2/r2/templates/comment.html
@@ -104,7 +104,7 @@
104 104
     %if show:
105 105
        ${unsafe(self.score(thing, likes = thing.likes))}&#32;
106 106
     %endif
107  
-    ${thing_timestamp(thing, thing.timesince)}&#32;${_("ago")}
  107
+    ${thing_timestamp(thing, thing.timesince)}&#32;
108 108
     %if thing.editted:
109 109
       <em>*</em>&nbsp;
110 110
     %endif
6  r2/r2/templates/link.html
@@ -202,15 +202,15 @@
2  r2/r2/templates/utils.html
@@ -516,7 +516,7 @@
516 516
 <%def name="timestamp(date, since=None)">
517 517
   ## todo: use pubdate attribute once things are <article> tags.
518 518
   ## note: comment and link templates will pass a CachedVariable stub as since.
519  
-  <time title="${long_datetime(date)}" datetime="${date.isoformat()}">
  519
+  <time class="fuzzy-time" title="${long_datetime(date)}" datetime="${date.isoformat()}">
520 520
     ${unsafe(since or timesince(date))}
521 521
   </time>
522 522
 </%def>
Commit_comment_tip

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.