Commit e5da3786 authored by Nick Stenning's avatar Nick Stenning

Merge pull request #1638 from hypothesis/1615-update-timestamps

Update annotation card timestamps regulary
parents 4cd394a7 ca928da6
......@@ -249,58 +249,73 @@ AnnotationController = [
# value is used to signal whether the annotation is being displayed inside
# an embedded widget.
###
annotation = ['annotator', 'documentHelpers', (annotator, documentHelpers) ->
linkFn = (scope, elem, attrs, [ctrl, thread, threadFilter, counter]) ->
# Helper function to remove the temporary thread created for a new reply.
prune = (message) ->
return if message.id? # threading plugin will take care of it
return unless thread.container.message is message
thread.container.parent?.removeChild(thread.container)
if thread?
annotator.subscribe 'annotationDeleted', prune
scope.$on '$destroy', ->
annotator.unsubscribe 'annotationDeleted', prune
# Observe the embedded attribute
attrs.$observe 'annotationEmbedded', (value) ->
ctrl.embedded = value? and value != 'false'
# Save on Meta + Enter or Ctrl + Enter.
elem.on 'keydown', (event) ->
if event.keyCode == 13 and (event.metaKey or event.ctrlKey)
event.preventDefault()
scope.$evalAsync ->
ctrl.save()
# Keep track of edits going on in the thread.
if counter?
# Expand the thread if descendants are editing.
scope.$watch (-> counter.count 'edit'), (count) ->
if count and not ctrl.editing and thread.collapsed
thread.toggleCollapsed()
# Propagate changes through the counters.
scope.$watch (-> ctrl.editing), (editing, old) ->
if editing
counter.count 'edit', 1
# Disable the filter and freeze it to always match while editing.
threadFilter?.freeze()
else if old
counter.count 'edit', -1
threadFilter?.freeze(false)
# Clean up when the thread is destroyed
scope.$on '$destroy', ->
if ctrl.editing then counter?.count 'edit', -1
controller: 'AnnotationController'
controllerAs: 'vm'
link: linkFn
require: ['annotation', '?^thread', '?^threadFilter', '?^deepCount']
scope:
annotationGet: '&annotation'
templateUrl: 'annotation.html'
annotation = [
'annotator', 'documentHelpers', 'render', 'timeHelpers', '$timeout',
(annotator, documentHelpers, render, timeHelpers, $timeout) ->
linkFn = (scope, elem, attrs, [ctrl, thread, threadFilter, counter]) ->
# Helper function to remove the temporary thread created for a new reply.
prune = (message) ->
return if message.id? # threading plugin will take care of it
return unless thread.container.message is message
thread.container.parent?.removeChild(thread.container)
if thread?
annotator.subscribe 'annotationDeleted', prune
scope.$on '$destroy', ->
annotator.unsubscribe 'annotationDeleted', prune
# Observe the embedded attribute
attrs.$observe 'annotationEmbedded', (value) ->
ctrl.embedded = value? and value != 'false'
# Save on Meta + Enter or Ctrl + Enter.
elem.on 'keydown', (event) ->
if event.keyCode == 13 and (event.metaKey or event.ctrlKey)
event.preventDefault()
scope.$evalAsync ->
ctrl.save()
# Keep track of edits going on in the thread.
if counter?
# Expand the thread if descendants are editing.
scope.$watch (-> counter.count 'edit'), (count) ->
if count and not ctrl.editing and thread.collapsed
thread.toggleCollapsed()
# Propagate changes through the counters.
scope.$watch (-> ctrl.editing), (editing, old) ->
if editing
counter.count 'edit', 1
# Disable the filter and freeze it to always match while editing.
threadFilter?.freeze()
else if old
counter.count 'edit', -1
threadFilter?.freeze(false)
# Clean up when the thread is destroyed
scope.$on '$destroy', ->
if ctrl.editing then counter?.count 'edit', -1
updateTimeStamp = ->
stamp = ctrl.annotation.updated
scope.timestamp = timeHelpers.toFuzzyString stamp
fuzzyUpdate = timeHelpers.nextFuzzyUpdate(stamp)
# Handle null value, give default 5 sec
fuzzyUpdate ?=5
nextUpdate = 1000*fuzzyUpdate+500
$timeout -> render updateTimeStamp, nextUpdate, false
render updateTimeStamp
controller: 'AnnotationController'
controllerAs: 'vm'
link: linkFn
require: ['annotation', '?^thread', '?^threadFilter', '?^deepCount']
scope:
annotationGet: '&annotation'
templateUrl: 'annotation.html'
]
......
......@@ -7,40 +7,6 @@ class Converter extends Markdown.Converter
text.replace /<a href=/g, "<a target=\"_blank\" href="
fuzzyTime = (date) ->
return '' if not date
delta = Math.round((+new Date - new Date(date)) / 1000)
minute = 60
hour = minute * 60
day = hour * 24
week = day * 7
month = day * 30
year = day * 365
if (delta < 30)
fuzzy = 'moments ago'
else if (delta < minute)
fuzzy = delta + ' seconds ago'
else if (delta < 2 * minute)
fuzzy = 'a minute ago'
else if (delta < hour)
fuzzy = Math.floor(delta / minute) + ' minutes ago'
else if (Math.floor(delta / hour) == 1)
fuzzy = '1 hour ago'
else if (delta < day)
fuzzy = Math.floor(delta / hour) + ' hours ago'
else if (delta < day * 2)
fuzzy = 'yesterday'
else if (delta < month)
fuzzy = Math.round(delta / day) + ' days ago'
else if (delta < year)
fuzzy = Math.round(delta / month) + ' months ago'
else
fuzzy = Math.round(delta / year) + ' years ago'
fuzzy
momentFilter = ->
(value, format) ->
# Determine the timezone name and browser language.
......@@ -66,6 +32,5 @@ persona = (user, part='username') ->
angular.module('h')
.filter('converter', -> (new Converter()).makeHtml)
.filter('fuzzyTime', -> fuzzyTime)
.filter('moment', momentFilter)
.filter('persona', -> persona)
minute = 60
hour = minute * 60
day = hour * 24
month = day * 30
year = day * 365
BREAKPOINTS = [
[30, 'moments ago', 1 ]
[minute, '{} seconds ago', 1 ]
[2 * minute, 'a minute ago', minute]
[hour, '{} minutes ago', minute]
[2 * hour, 'an hour ago', hour ]
[day, '{} hours ago', hour ]
[2 * day, 'yesterday', day ]
[month, '{} days ago', day ]
[year, '{} months ago', month ]
[Infinity, '{} years ago', year ]
]
arrayFind = (array, predicate) ->
if not array?
throw new TypeError('arrayFindIndex called on null or undefined')
if typeof predicate != 'function'
throw new TypeError('predicate must be a function')
for value, i in array
if predicate(value, i, array)
return value
return null
getBreakpoint = (date) ->
delta = Math.round((new Date() - new Date(date)) / 1000)
delta: delta
breakpoint: arrayFind(BREAKPOINTS, (x) -> x[0] > delta)
createTimeHelpers = ->
toFuzzyString: (date) ->
return '' unless date
{delta, breakpoint} = getBreakpoint(date)
return '' unless breakpoint
template = breakpoint[1]
resolution = breakpoint[2]
return template.replace('{}', String(Math.floor(delta / resolution)))
nextFuzzyUpdate: (date) ->
return null if not date
{_, breakpoint} = getBreakpoint(date)
return null unless breakpoint
return breakpoint[2]
angular.module('h.helpers')
.factory('timeHelpers', createTimeHelpers)
......@@ -37,7 +37,7 @@
title="{{vm.annotation.update | moment:'LLLL'}}"
ng-if="!vm.editing && vm.annotation.updated"
ng-href="{{baseURI}}a/{{vm.annotation.id}}"
>{{vm.annotation.updated | fuzzyTime | date : mediumDate}}</a>
>{{timestamp}}</a>
</header>
<!-- Excerpts -->
......
assert = chai.assert
minute = 60
hour = minute * 60
day = hour * 24
month = day * 30
year = day * 365
FIXTURES_TO_FUZZY_STRING = [
[10, 'moments ago']
[29, 'moments ago']
[49, '49 seconds ago']
[minute + 5, 'a minute ago']
[3 * minute + 5, '3 minutes ago']
[4 * hour, '4 hours ago']
[27 * hour, 'yesterday']
[3 * day + 30 * minute, '3 days ago']
[6 * month + 2 * day, '6 months ago']
[8 * year, '8 years ago']
]
FIXTURES_NEXT_FUZZY_UPDATE = [
[10, 1]
[29, 1]
[49, 1]
[minute + 5, minute]
[3 * minute + 5, minute]
[4 * hour, hour]
[27 * hour, day]
[3 * day + 30 * minute, day]
[6 * month + 2 * day, month]
[8 * year, year]
]
describe 'timeHelpers', ->
beforeEach module('h.helpers')
timeHelpers = null
sandbox = null
beforeEach inject (_timeHelpers_) ->
timeHelpers = _timeHelpers_
sandbox = sinon.sandbox.create()
sandbox.useFakeTimers()
afterEach ->
sandbox.restore()
describe '.toFuzzyString', ->
it 'Handles empty dates', ->
time = null
expect = ''
assert.equal(timeHelpers.toFuzzyString(time), expect)
testFixture = (f) ->
->
time = new Date()
expect = f[1]
sandbox.clock.tick(f[0]*1000)
assert.equal(timeHelpers.toFuzzyString(time), expect)
for f, i in FIXTURES_TO_FUZZY_STRING
it "creates correct fuzzy string for fixture #{i}", testFixture(f)
describe '.nextFuzzyUpdate', ->
it 'Handles empty dates', ->
time = null
expect = null
assert.equal(timeHelpers.nextFuzzyUpdate(time), expect)
testFixture = (f) ->
->
time = new Date()
expect = f[1]
sandbox.clock.tick(f[0]*1000)
assert.equal(timeHelpers.nextFuzzyUpdate(time), expect)
for f, i in FIXTURES_NEXT_FUZZY_UPDATE
it "gives correct next fuzzy update time for fixture #{i}", testFixture(f)
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment