mirror of
https://github.com/reddit-archive/reddit.git
synced 2026-04-27 03:00:12 -04:00
Add live-updating to timestamps.
This commit is contained in:
@@ -450,6 +450,8 @@ module["reddit"] = LocalizedModule("reddit.js",
|
||||
"lib/jquery.url.js",
|
||||
"lib/backbone-1.0.0.js",
|
||||
"templates.js",
|
||||
"scrollupdater.js",
|
||||
"timetext.js",
|
||||
"ui.js",
|
||||
"login.js",
|
||||
"flair.js",
|
||||
|
||||
@@ -618,26 +618,26 @@ class Link(Thing, Printable):
|
||||
if item.score_fmt == Score.points:
|
||||
taglinetext = ("<span>" +
|
||||
_("%(score)s submitted %(when)s "
|
||||
"ago%(lastedited)s") +
|
||||
"%(lastedited)s") +
|
||||
"</span>")
|
||||
taglinetext += author_text
|
||||
elif item.different_sr:
|
||||
taglinetext = _("submitted %(when)s ago%(lastedited)s "
|
||||
taglinetext = _("submitted %(when)s %(lastedited)s "
|
||||
"by %(author)s to %(reddit)s")
|
||||
else:
|
||||
taglinetext = _("submitted %(when)s ago%(lastedited)s "
|
||||
taglinetext = _("submitted %(when)s %(lastedited)s "
|
||||
"by %(author)s")
|
||||
else:
|
||||
if item.score_fmt == Score.points:
|
||||
taglinetext = ("<span>" +
|
||||
_("%(score)s submitted %(when)s ago") +
|
||||
_("%(score)s submitted %(when)s") +
|
||||
"</span>")
|
||||
taglinetext += author_text
|
||||
elif item.different_sr:
|
||||
taglinetext = _("submitted %(when)s ago by %(author)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")
|
||||
item.taglinetext = taglinetext
|
||||
|
||||
if item.is_author:
|
||||
@@ -1518,13 +1518,13 @@ class Message(Thing, Printable):
|
||||
item.body = _('[unblock user to see this message]')
|
||||
taglinetext = ''
|
||||
if item.hide_author:
|
||||
taglinetext = _("subreddit message %(author)s sent %(when)s ago")
|
||||
taglinetext = _("subreddit message %(author)s sent %(when)s")
|
||||
elif item.author_id == c.user._id:
|
||||
taglinetext = _("to %(dest)s sent %(when)s ago")
|
||||
taglinetext = _("to %(dest)s sent %(when)s")
|
||||
elif item.to_id == c.user._id or item.to_id is None:
|
||||
taglinetext = _("from %(author)s sent %(when)s ago")
|
||||
taglinetext = _("from %(author)s sent %(when)s")
|
||||
else:
|
||||
taglinetext = _("to %(dest)s from %(author)s sent %(when)s ago")
|
||||
taglinetext = _("to %(dest)s from %(author)s sent %(when)s")
|
||||
item.taglinetext = taglinetext
|
||||
if item.to:
|
||||
if item.to._deleted:
|
||||
|
||||
@@ -454,8 +454,8 @@ $.fn.replace_things = function(things, keep_children, reveal, stubs) {
|
||||
* case of a comment tree, flags whether or not the new thing has
|
||||
* the thread present) while "reveal" determines whether or not to
|
||||
* animate the transition from old to new. */
|
||||
var self = this;
|
||||
return $.map(things, function(thing) {
|
||||
var self = this,
|
||||
map = $.map(things, function(thing) {
|
||||
var data = thing.data;
|
||||
var existing = $(self).things(data.id);
|
||||
if(stubs)
|
||||
@@ -508,13 +508,15 @@ $.fn.replace_things = function(things, keep_children, reveal, stubs) {
|
||||
$(document).trigger('new_thing', new_thing)
|
||||
return new_thing;
|
||||
});
|
||||
|
||||
|
||||
$(document).trigger('new_things_inserted')
|
||||
return map
|
||||
};
|
||||
|
||||
|
||||
$.insert_things = function(things, append) {
|
||||
/* Insert new things into a listing.*/
|
||||
return $.map(things, function(thing) {
|
||||
var map = $.map(things, function(thing) {
|
||||
var data = thing.data;
|
||||
var s = $.listing(data.parent);
|
||||
if(append)
|
||||
@@ -525,7 +527,9 @@ $.insert_things = function(things, append) {
|
||||
thing_init_func(s.hide().show());
|
||||
$(document).trigger('new_thing', s)
|
||||
return s;
|
||||
});
|
||||
})
|
||||
$(document).trigger('new_things_inserted')
|
||||
return map
|
||||
};
|
||||
|
||||
$.fn.delete_table_row = function(callback) {
|
||||
|
||||
@@ -1,115 +1,129 @@
|
||||
r.ScrollUpdater = Backbone.View.extend({
|
||||
selector: null,
|
||||
update: function() {},
|
||||
!function(r, $){
|
||||
r.ScrollUpdater = Backbone.View.extend({
|
||||
selector: null,
|
||||
startUpdate: function () {},
|
||||
update: function ($el) {},
|
||||
endUpdate: function ($els) {},
|
||||
|
||||
start: function() {
|
||||
this._resetScrollState()
|
||||
this._listen()
|
||||
return this
|
||||
},
|
||||
start: function() {
|
||||
this._resetScrollState()
|
||||
this._listen()
|
||||
return this
|
||||
},
|
||||
|
||||
restart: function() {
|
||||
this._resetScrollState()
|
||||
return this
|
||||
},
|
||||
restart: function() {
|
||||
this._resetScrollState()
|
||||
return this
|
||||
},
|
||||
|
||||
_resetScrollState: function() {
|
||||
this._elements = $(this.selector)
|
||||
_.sortBy(this._elements, function(el) {
|
||||
return $(el).offset().top
|
||||
})
|
||||
_resetScrollState: function() {
|
||||
this._elements = this.$el.find(this.selector)
|
||||
_.sortBy(this._elements, function(el) {
|
||||
return $(el).offset().top
|
||||
})
|
||||
|
||||
this._curIndex = 0
|
||||
this._lastScroll = null
|
||||
this._toUpdate = []
|
||||
this._totalTime = 0
|
||||
this._curIndex = 0
|
||||
this._lastScroll = null
|
||||
this._toUpdate = []
|
||||
this._totalTime = 0
|
||||
|
||||
// Trigger once now to detect any elements currently in view.
|
||||
_.defer($.proxy(this, '_updateThings'))
|
||||
},
|
||||
// Trigger once now to detect any elements currently in view.
|
||||
_.defer($.proxy(this, '_updateThings'))
|
||||
},
|
||||
|
||||
_listen: function() {
|
||||
var throttledUpdate = _.throttle($.proxy(this, '_updateThings'), 20)
|
||||
$(window).on("scroll", throttledUpdate)
|
||||
},
|
||||
_listen: function() {
|
||||
var throttledUpdate = _.throttle($.proxy(this, '_updateThings'), 20)
|
||||
$(window).on('scroll', throttledUpdate)
|
||||
},
|
||||
|
||||
_updateThings: function(ev) {
|
||||
if (!this._elements.length) {
|
||||
return
|
||||
}
|
||||
_updateThings: function(ev) {
|
||||
if (!this._elements.length) {
|
||||
return
|
||||
}
|
||||
|
||||
var startTime = new Date()
|
||||
var startTime = new Date()
|
||||
|
||||
// update the current page of elements and half a page in the
|
||||
// direction of motion
|
||||
var $win = $(window),
|
||||
winHeight = $win.height(),
|
||||
scrollTop = $win.scrollTop(),
|
||||
ceiling = scrollTop,
|
||||
floor = scrollTop + winHeight
|
||||
// update the current page of elements and half a page in the
|
||||
// direction of motion
|
||||
var $win = $(window),
|
||||
winHeight = $win.height(),
|
||||
scrollTop = $win.scrollTop(),
|
||||
ceiling = scrollTop,
|
||||
floor = scrollTop + winHeight
|
||||
|
||||
if (scrollTop < this._lastScroll) {
|
||||
ceiling = Math.max(ceiling - Math.floor(winHeight / 2), 0)
|
||||
} else {
|
||||
floor += Math.ceil(winHeight / 2)
|
||||
}
|
||||
if (scrollTop < this._lastScroll) {
|
||||
ceiling = Math.max(ceiling - Math.floor(winHeight / 2), 0)
|
||||
} else {
|
||||
floor += Math.ceil(winHeight / 2)
|
||||
}
|
||||
|
||||
// scan to ceiling to set the cursor
|
||||
var idx = this._curIndex,
|
||||
$cur = $(this._elements[idx])
|
||||
if ($cur.offset().top < ceiling) {
|
||||
// forward
|
||||
while (idx < this._elements.length-1 && $cur.offset().top < ceiling) {
|
||||
// scan to ceiling to set the cursor
|
||||
var idx = this._curIndex,
|
||||
$cur = $(this._elements[idx])
|
||||
|
||||
if ($cur.offset().top < ceiling) {
|
||||
// forward
|
||||
while (idx < this._elements.length-1 && $cur.offset().top < ceiling) {
|
||||
$cur = $(this._elements[idx])
|
||||
idx++
|
||||
}
|
||||
} else {
|
||||
// backward
|
||||
while (idx > 0 && $cur.offset().top > ceiling) {
|
||||
$cur = $(this._elements[idx])
|
||||
idx--
|
||||
}
|
||||
}
|
||||
|
||||
// update forward to floor
|
||||
var count = 0
|
||||
do {
|
||||
$cur = $(this._elements[idx])
|
||||
this._toUpdate.push($cur)
|
||||
idx++
|
||||
count++
|
||||
} while (idx <= this._elements.length-1 && $cur.offset().top <= floor)
|
||||
|
||||
this._curIndex = idx - 1
|
||||
this._lastScroll = scrollTop
|
||||
|
||||
var endTime = new Date()
|
||||
this._totalTime += endTime - startTime
|
||||
|
||||
r.debug('scrollupdater queued', count, 'in', endTime - startTime, 'ms')
|
||||
|
||||
this._doUpdates()
|
||||
},
|
||||
|
||||
cutoff: 1000 / 60,
|
||||
_doUpdates: function() {
|
||||
this.startUpdate()
|
||||
|
||||
var startTime = new Date(),
|
||||
endTime = startTime,
|
||||
count = 0,
|
||||
els = []
|
||||
|
||||
while (endTime - startTime < this.cutoff) {
|
||||
if (!this._toUpdate.length) {
|
||||
break
|
||||
}
|
||||
var $el = this._toUpdate.shift()
|
||||
els.push($el)
|
||||
this.update($el)
|
||||
count++
|
||||
endTime = new Date()
|
||||
}
|
||||
} else {
|
||||
// backward
|
||||
while (idx > 0 && $cur.offset().top > ceiling) {
|
||||
$cur = $(this._elements[idx])
|
||||
idx--
|
||||
|
||||
this._totalTime += endTime - startTime
|
||||
r.debug('scrollupdater updated', count, 'in', endTime - startTime, 'ms')
|
||||
r.debug('scrollupdater total', this._totalTime, 'ms')
|
||||
|
||||
if (this._toUpdate.length) {
|
||||
_.defer($.proxy(this, '_doUpdates'))
|
||||
}
|
||||
|
||||
this.endUpdate($(els))
|
||||
}
|
||||
|
||||
// update forward to floor
|
||||
var count = 0
|
||||
do {
|
||||
$cur = $(this._elements[idx])
|
||||
this._toUpdate.push($cur)
|
||||
idx++
|
||||
count++
|
||||
} while (idx <= this._elements.length-1 && $cur.offset().top <= floor)
|
||||
|
||||
this._curIndex = idx - 1
|
||||
this._lastScroll = scrollTop
|
||||
|
||||
var endTime = new Date()
|
||||
this._totalTime += endTime - startTime
|
||||
|
||||
r.debug('scrollupdater queued', count, 'in', endTime - startTime, 'ms')
|
||||
|
||||
this._doUpdates()
|
||||
},
|
||||
|
||||
cutoff: 1000 / 60,
|
||||
_doUpdates: function() {
|
||||
var startTime = new Date(),
|
||||
endTime = startTime,
|
||||
count = 0
|
||||
while (endTime - startTime < this.cutoff) {
|
||||
if (!this._toUpdate.length) {
|
||||
break
|
||||
}
|
||||
var $el = this._toUpdate.shift()
|
||||
this.update($el)
|
||||
count++
|
||||
endTime = new Date()
|
||||
}
|
||||
this._totalTime += endTime - startTime
|
||||
r.debug('scrollupdater updated', count, 'in', endTime - startTime, 'ms')
|
||||
r.debug('scrollupdater total', this._totalTime, 'ms')
|
||||
if (this._toUpdate.length) {
|
||||
_.defer($.proxy(this, '_doUpdates'))
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}(r, jQuery)
|
||||
|
||||
@@ -1,58 +1,81 @@
|
||||
// polyfill, see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now
|
||||
if (!Date.now) {
|
||||
Date.now = function now() {
|
||||
return new Date().getTime()
|
||||
!function(r, $){
|
||||
if (!Date.now) {
|
||||
Date.now = function now() {
|
||||
return new Date().getTime()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
r.timetext = {
|
||||
_maxAge: 24 * 60 * 60,
|
||||
_chunks: [
|
||||
[60 * 60 * 24 * 365, r.NP_('a year ago', '%(num)s years ago')],
|
||||
[60 * 60 * 24 * 30, r.NP_('a month ago', '%(num)s months ago')],
|
||||
[60 * 60 * 24, r.NP_('a day ago', '%(num)s days ago')],
|
||||
// Start with smallest chunk, since we're probably looking at a recent
|
||||
// set of timestamps and probably don't need to check older dates.
|
||||
var CHUNKS = [
|
||||
[60, r.NP_('a minute ago', '%(num)s minutes ago')],
|
||||
[60 * 60, r.NP_('an hour ago', '%(num)s hours ago')],
|
||||
[60, r.NP_('a minute ago', '%(num)s minutes ago')]
|
||||
],
|
||||
[60 * 60 * 24, r.NP_('a day ago', '%(num)s days ago')],
|
||||
[60 * 60 * 24 * 30, r.NP_('a month ago', '%(num)s months ago')],
|
||||
[60 * 60 * 24 * 365, r.NP_('a year ago', '%(num)s years ago')]
|
||||
]
|
||||
|
||||
init: function () {
|
||||
var defaults = {
|
||||
maxage: 24 * 60 * 60
|
||||
}
|
||||
|
||||
function TimeText(selector, opts) {
|
||||
this.opts = _.defaults(opts || {}, defaults)
|
||||
|
||||
this.elCache = selector ? $(selector) : $([])
|
||||
|
||||
this.refresh = _.throttle(this._refresh, 1000)
|
||||
|
||||
setInterval($.proxy(this.refresh, this), 20 * 1000)
|
||||
this.refresh()
|
||||
setInterval(this.refresh, 20 * 1000)
|
||||
},
|
||||
}
|
||||
|
||||
refresh: function () {
|
||||
TimeText.prototype._refresh = function(){
|
||||
var now = Date.now()
|
||||
|
||||
$('time.live').each(function () {
|
||||
r.timetext.refreshOne(this, now)
|
||||
})
|
||||
},
|
||||
this.elCache.each($.proxy(function (i, el) {
|
||||
this.refreshOne(el, now)
|
||||
}, this))
|
||||
}
|
||||
|
||||
refreshOne: function (el, now) {
|
||||
if (!now)
|
||||
now = Date.now()
|
||||
TimeText.prototype.updateCache = function(elCache) {
|
||||
this.elCache = elCache
|
||||
this.refresh()
|
||||
}
|
||||
|
||||
TimeText.prototype.refreshOne = function (el, now) {
|
||||
if (!now){
|
||||
now = Date.now()
|
||||
}
|
||||
|
||||
var $el = $(el)
|
||||
var timestamp = $el.data('timestamp')
|
||||
var isoTimestamp
|
||||
var age
|
||||
var count
|
||||
var keys
|
||||
var text
|
||||
|
||||
if (!timestamp) {
|
||||
var isoTimestamp = $el.attr('datetime')
|
||||
isoTimestamp = $el.attr('datetime')
|
||||
timestamp = Date.parse(isoTimestamp)
|
||||
$el.data('timestamp', timestamp)
|
||||
}
|
||||
|
||||
var age = (now - timestamp) / 1000
|
||||
if (age > this._maxAge) {
|
||||
age = (now - timestamp) / 1000
|
||||
|
||||
if (age > this.opts.maxage) {
|
||||
$el.removeClass('live-timestamp')
|
||||
return
|
||||
}
|
||||
|
||||
var chunks = r.timetext._chunks
|
||||
var text = r._('just now')
|
||||
text = r._('just now')
|
||||
|
||||
$.each(r.timetext._chunks, function (ix, chunk) {
|
||||
var count = Math.floor(age / chunk[0])
|
||||
if (count > 0) {
|
||||
var keys = chunk[1]
|
||||
$.each(CHUNKS, function (ix, chunk) {
|
||||
count = Math.floor(age / chunk[0])
|
||||
|
||||
if (count < chunk[0] && count > 0) {
|
||||
keys = chunk[1]
|
||||
text = r.P_(keys[0], keys[1], count).format({num: count})
|
||||
return false
|
||||
}
|
||||
@@ -60,8 +83,6 @@ r.timetext = {
|
||||
|
||||
$el.text(text)
|
||||
}
|
||||
}
|
||||
|
||||
$(function () {
|
||||
r.timetext.init()
|
||||
})
|
||||
r.TimeText = TimeText
|
||||
}(r, jQuery)
|
||||
|
||||
@@ -42,6 +42,34 @@ r.ui.init = function() {
|
||||
}
|
||||
|
||||
r.ui.PermissionEditor.init()
|
||||
|
||||
r.ui.initLiveTimestamps()
|
||||
}
|
||||
|
||||
r.ui.TimeTextScrollListener = r.ScrollUpdater.extend({
|
||||
initialize: function() {
|
||||
this.timeText = new r.TimeText(this.selector)
|
||||
},
|
||||
selector: '.live-timestamp:visible',
|
||||
endUpdate: function($els) {
|
||||
this.timeText.updateCache($els)
|
||||
}
|
||||
})
|
||||
|
||||
r.ui.initLiveTimestamps = function() {
|
||||
// We only want a global timestamp scroll listener to instantiate on
|
||||
// pages with `thing`s. Since we don't have a router yet, we'll scope
|
||||
// the element to `.sitetable`s, which will contain it. This is kind of a
|
||||
// dirty hack and should be obsoleted by a router + view system.
|
||||
if ($('.sitetable').length) {
|
||||
var listener = new r.ui.TimeTextScrollListener({ el: '.sitetable' })
|
||||
listener.start()
|
||||
|
||||
// Every time we add a new `thing`, we'll need to re-grab our element caches.
|
||||
$(document).on('new_things_inserted', function() {
|
||||
listener.restart()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
r.ui.showWorkingDeferred = function(el, deferred) {
|
||||
|
||||
@@ -120,7 +120,7 @@ ${parent.collapsed()}
|
||||
${unsafe(self.score(thing, likes = thing.likes))} 
|
||||
%endif
|
||||
%endif
|
||||
${thing_timestamp(thing, thing.timesince)} ${_("ago")}
|
||||
${thing_timestamp(thing, thing.timesince, live=True, include_tense=True)}
|
||||
${edited(thing, thing.lastedited)}
|
||||
%endif
|
||||
|
||||
|
||||
@@ -209,7 +209,7 @@ ${parent.thing_data_attributes(what)} data-ups="${what.upvotes}" data-downs="${w
|
||||
%>
|
||||
${unsafe(taglinetext % dict(reddit=self.subreddit(),
|
||||
score=capture(self.score, thing, thing.likes, tag='span'),
|
||||
when=capture(thing_timestamp, thing, thing.timesince),
|
||||
when=capture(thing_timestamp, thing, thing.timesince, live=True, include_tense=True),
|
||||
author=WrappedUser(thing.author, thing.attribs, thing).render(),
|
||||
lastedited=capture(edited, thing, thing.lastedited)
|
||||
))}
|
||||
|
||||
@@ -64,7 +64,7 @@ ${parent.thing_css_class(what)} ${"new" if thing.new else ""} ${"was-comment" if
|
||||
subreddit=subreddit)
|
||||
|
||||
taglinetext = thing.taglinetext.replace(' ', ' ') % dict(
|
||||
when=capture(thing_timestamp, thing, thing.timesince),
|
||||
when=capture(thing_timestamp, thing, thing.timesince, include_tense=True),
|
||||
author=u"<b>%s</b>" % author,
|
||||
dest=u"<b>%s</b>" % thing.dest)
|
||||
%>
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
%>
|
||||
|
||||
<tr class="modactions" style="background-color: ${bgcolor}" data-fullname="${thing.fullname}">
|
||||
<td class="timestamp whitespace:nowrap">${timestamp(thing.date)} ago</td>
|
||||
<td class="timestamp whitespace:nowrap">${timestamp(thing.date, live=True, include_tense=True)}</td>
|
||||
%if is_multi:
|
||||
<td class="subreddit">${plain_link('/r/' + thing.sr_name, thing.sr_path + 'about/log', title=thing.sr_name)}</td>
|
||||
%endif
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
<tr>
|
||||
<td>${ip}</td>
|
||||
<td>${location.get('country_name', '')}</td>
|
||||
<td>${timestamp(last_visit)} ${_('ago')}</td>
|
||||
<td>${timestamp(last_visit, live=True, include_tense=True)}</td>
|
||||
</tr>
|
||||
% endfor
|
||||
</tbody>
|
||||
|
||||
@@ -546,18 +546,29 @@ ${unsafe(txt)}
|
||||
</label>
|
||||
</%def>
|
||||
|
||||
<%def name="timestamp(date, since=None)">
|
||||
<%def name="timestamp(date, since=None, live=False, include_tense=False)">
|
||||
## 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="${html_datetime(date)}">
|
||||
<% now = date.now(g.tz) %>
|
||||
|
||||
<time title="${long_datetime(date)}" datetime="${html_datetime(date)}"
|
||||
%if live:
|
||||
class="live-timestamp"
|
||||
%endif
|
||||
>
|
||||
${unsafe(since or timesince(date))}
|
||||
%if include_tense and date < now:
|
||||
${_("ago")}
|
||||
%elif date > now:
|
||||
${_("from now")}
|
||||
%endif
|
||||
</time>
|
||||
</%def>
|
||||
|
||||
<%def name="thing_timestamp(thing, since=None)">
|
||||
<%def name="thing_timestamp(thing, since=None, live=False, include_tense=False)">
|
||||
## todo: use pubdate attribute once things are <article> tags.
|
||||
## note: comment and link templates will pass a CachedVariable stub as since.
|
||||
${timestamp(thing._date, since=since)}
|
||||
${timestamp(thing._date, since=since, live=live, include_tense=include_tense)}
|
||||
</%def>
|
||||
|
||||
<%def name="percentage(slice, total)">
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
%endif
|
||||
|
||||
<td style="white-space: nowrap;">
|
||||
${timestamp(thing.date)} ago
|
||||
${timestamp(thing.date, live=True, include_tense=True)}
|
||||
</td>
|
||||
|
||||
%if not thing.show_extended:
|
||||
|
||||
Reference in New Issue
Block a user