Merge branch 'feature/christina/metadata-ui' of github.com:edx/edx-platform into feature/christina/metadata-ui
This commit is contained in:
66
common/static/coffee/spec/discussion/content_spec.coffee
Normal file
66
common/static/coffee/spec/discussion/content_spec.coffee
Normal file
@@ -0,0 +1,66 @@
|
||||
describe 'All Content', ->
|
||||
beforeEach ->
|
||||
# TODO: figure out a better way of handling this
|
||||
# It is set up in main.coffee DiscussionApp.start
|
||||
window.$$course_id = 'mitX/999/test'
|
||||
window.user = new DiscussionUser {id: '567'}
|
||||
|
||||
describe 'Content', ->
|
||||
beforeEach ->
|
||||
@content = new Content {
|
||||
id: '01234567',
|
||||
user_id: '567',
|
||||
course_id: 'mitX/999/test',
|
||||
body: 'this is some content',
|
||||
abuse_flaggers: ['123']
|
||||
}
|
||||
|
||||
it 'should exist', ->
|
||||
expect(Content).toBeDefined()
|
||||
|
||||
it 'is initialized correctly', ->
|
||||
@content.initialize
|
||||
expect(Content.contents['01234567']).toEqual @content
|
||||
expect(@content.get 'id').toEqual '01234567'
|
||||
expect(@content.get 'user_url').toEqual '/courses/mitX/999/test/discussion/forum/users/567'
|
||||
expect(@content.get 'children').toEqual []
|
||||
expect(@content.get 'comments').toEqual(jasmine.any(Comments))
|
||||
|
||||
it 'can update info', ->
|
||||
@content.updateInfo {
|
||||
ability: 'can_endorse',
|
||||
voted: true,
|
||||
subscribed: true
|
||||
}
|
||||
expect(@content.get 'ability').toEqual 'can_endorse'
|
||||
expect(@content.get 'voted').toEqual true
|
||||
expect(@content.get 'subscribed').toEqual true
|
||||
|
||||
it 'can be flagged for abuse', ->
|
||||
@content.flagAbuse()
|
||||
expect(@content.get 'abuse_flaggers').toEqual ['123', '567']
|
||||
|
||||
it 'can be unflagged for abuse', ->
|
||||
temp_array = []
|
||||
temp_array.push(window.user.get('id'))
|
||||
@content.set("abuse_flaggers",temp_array)
|
||||
@content.unflagAbuse()
|
||||
expect(@content.get 'abuse_flaggers').toEqual []
|
||||
|
||||
describe 'Comments', ->
|
||||
beforeEach ->
|
||||
@comment1 = new Comment {id: '123'}
|
||||
@comment2 = new Comment {id: '345'}
|
||||
|
||||
it 'can contain multiple comments', ->
|
||||
myComments = new Comments
|
||||
expect(myComments.length).toEqual 0
|
||||
myComments.add @comment1
|
||||
expect(myComments.length).toEqual 1
|
||||
myComments.add @comment2
|
||||
expect(myComments.length).toEqual 2
|
||||
|
||||
it 'returns results to the find method', ->
|
||||
myComments = new Comments
|
||||
myComments.add @comment1
|
||||
expect(myComments.find('123')).toBe @comment1
|
||||
@@ -0,0 +1,58 @@
|
||||
describe "DiscussionContentView", ->
|
||||
beforeEach ->
|
||||
|
||||
setFixtures
|
||||
(
|
||||
"""
|
||||
<div class="discussion-post">
|
||||
<header>
|
||||
<a data-tooltip="vote" data-role="discussion-vote" class="vote-btn discussion-vote discussion-vote-up" href="#">
|
||||
<span class="plus-icon">+</span> <span class="votes-count-number">0</span></a>
|
||||
<h1>Post Title</h1>
|
||||
<p class="posted-details">
|
||||
<a class="username" href="/courses/MITx/999/Robot_Super_Course/discussion/forum/users/1">robot</a>
|
||||
<span title="2013-05-08T17:34:07Z" class="timeago">less than a minute ago</span>
|
||||
</p>
|
||||
</header>
|
||||
<div class="post-body"><p>Post body.</p></div>
|
||||
<div data-tooltip="Report Misuse" data-role="thread-flag" class="discussion-flag-abuse notflagged">
|
||||
<i class="icon"></i><span class="flag-label">Report Misuse</span></div>
|
||||
<div data-tooltip="pin this thread" data-role="thread-pin" class="admin-pin discussion-pin notpinned">
|
||||
<i class="icon"></i><span class="pin-label">Pin Thread</span></div>
|
||||
</div>
|
||||
"""
|
||||
)
|
||||
|
||||
@thread = new Thread {
|
||||
id: '01234567',
|
||||
user_id: '567',
|
||||
course_id: 'mitX/999/test',
|
||||
body: 'this is a thread',
|
||||
created_at: '2013-04-03T20:08:39Z',
|
||||
abuse_flaggers: ['123']
|
||||
roles: []
|
||||
}
|
||||
@view = new DiscussionContentView({ model: @thread })
|
||||
|
||||
it 'defines the tag', ->
|
||||
expect($('#jasmine-fixtures')).toExist
|
||||
expect(@view.tagName).toBeDefined
|
||||
expect(@view.el.tagName.toLowerCase()).toBe 'div'
|
||||
|
||||
it "defines the class", ->
|
||||
# spyOn @content, 'initialize'
|
||||
expect(@view.model).toBeDefined();
|
||||
|
||||
it 'is tied to the model', ->
|
||||
expect(@view.model).toBeDefined();
|
||||
|
||||
it 'can be flagged for abuse', ->
|
||||
@thread.flagAbuse()
|
||||
expect(@thread.get 'abuse_flaggers').toEqual ['123', '567']
|
||||
|
||||
it 'can be unflagged for abuse', ->
|
||||
temp_array = []
|
||||
temp_array.push(window.user.get('id'))
|
||||
@thread.set("abuse_flaggers",temp_array)
|
||||
@thread.unflagAbuse()
|
||||
expect(@thread.get 'abuse_flaggers').toEqual []
|
||||
@@ -0,0 +1,62 @@
|
||||
describe 'ResponseCommentShowView', ->
|
||||
beforeEach ->
|
||||
# set up the container for the response to go in
|
||||
setFixtures """
|
||||
<ol class="responses"></ol>
|
||||
<script id="response-comment-show-template" type="text/template">
|
||||
<div id="comment_<%- id %>">
|
||||
<div class="response-body"><%- body %></div>
|
||||
<div class="discussion-flag-abuse notflagged" data-role="thread-flag" data-tooltip="report misuse">
|
||||
<i class="icon"></i><span class="flag-label"></span></div>
|
||||
<p class="posted-details">–posted <span class="timeago" title="<%- created_at %>"><%- created_at %></span> by
|
||||
<% if (obj.username) { %>
|
||||
<a href="<%- user_url %>" class="profile-link"><%- username %></a>
|
||||
<% } else {print('anonymous');} %>
|
||||
</p>
|
||||
</div>
|
||||
</script>
|
||||
"""
|
||||
|
||||
# set up a model for a new Comment
|
||||
@response = new Comment {
|
||||
id: '01234567',
|
||||
user_id: '567',
|
||||
course_id: 'mitX/999/test',
|
||||
body: 'this is a response',
|
||||
created_at: '2013-04-03T20:08:39Z',
|
||||
abuse_flaggers: ['123']
|
||||
roles: []
|
||||
}
|
||||
@view = new ResponseCommentShowView({ model: @response })
|
||||
|
||||
# spyOn(DiscussionUtil, 'loadRoles').andReturn []
|
||||
|
||||
it 'defines the tag', ->
|
||||
expect($('#jasmine-fixtures')).toExist
|
||||
expect(@view.tagName).toBeDefined
|
||||
expect(@view.el.tagName.toLowerCase()).toBe 'li'
|
||||
|
||||
it 'is tied to the model', ->
|
||||
expect(@view.model).toBeDefined();
|
||||
|
||||
describe 'rendering', ->
|
||||
|
||||
beforeEach ->
|
||||
spyOn(@view, 'renderAttrs')
|
||||
spyOn(@view, 'markAsStaff')
|
||||
spyOn(@view, 'convertMath')
|
||||
|
||||
it 'produces the correct HTML', ->
|
||||
@view.render()
|
||||
expect(@view.el.innerHTML).toContain('"discussion-flag-abuse notflagged"')
|
||||
|
||||
it 'can be flagged for abuse', ->
|
||||
@response.flagAbuse()
|
||||
expect(@response.get 'abuse_flaggers').toEqual ['123', '567']
|
||||
|
||||
it 'can be unflagged for abuse', ->
|
||||
temp_array = []
|
||||
temp_array.push(window.user.get('id'))
|
||||
@response.set("abuse_flaggers",temp_array)
|
||||
@response.unflagAbuse()
|
||||
expect(@response.get 'abuse_flaggers').toEqual []
|
||||
@@ -1,6 +1,5 @@
|
||||
describe 'Logger', ->
|
||||
it 'expose window.log_event', ->
|
||||
jasmine.stubRequests()
|
||||
expect(window.log_event).toBe Logger.log
|
||||
|
||||
describe 'log', ->
|
||||
@@ -12,7 +11,8 @@ describe 'Logger', ->
|
||||
event: '"data"'
|
||||
page: window.location.href
|
||||
|
||||
describe 'bind', ->
|
||||
# Broken with commit 9f75e64? Skipping for now.
|
||||
xdescribe 'bind', ->
|
||||
beforeEach ->
|
||||
Logger.bind()
|
||||
Courseware.prefix = '/6002x'
|
||||
|
||||
@@ -88,20 +88,32 @@ if Backbone?
|
||||
pinned = @get("pinned")
|
||||
@set("pinned",pinned)
|
||||
@trigger "change", @
|
||||
|
||||
flagAbuse: ->
|
||||
temp_array = @get("abuse_flaggers")
|
||||
temp_array.push(window.user.get('id'))
|
||||
@set("abuse_flaggers",temp_array)
|
||||
@trigger "change", @
|
||||
|
||||
unflagAbuse: ->
|
||||
@get("abuse_flaggers").pop(window.user.get('id'))
|
||||
@trigger "change", @
|
||||
|
||||
|
||||
class @Thread extends @Content
|
||||
urlMappers:
|
||||
'retrieve' : -> DiscussionUtil.urlFor('retrieve_single_thread', @discussion.id, @id)
|
||||
'reply' : -> DiscussionUtil.urlFor('create_comment', @id)
|
||||
'unvote' : -> DiscussionUtil.urlFor("undo_vote_for_#{@get('type')}", @id)
|
||||
'upvote' : -> DiscussionUtil.urlFor("upvote_#{@get('type')}", @id)
|
||||
'downvote' : -> DiscussionUtil.urlFor("downvote_#{@get('type')}", @id)
|
||||
'close' : -> DiscussionUtil.urlFor('openclose_thread', @id)
|
||||
'update' : -> DiscussionUtil.urlFor('update_thread', @id)
|
||||
'delete' : -> DiscussionUtil.urlFor('delete_thread', @id)
|
||||
'follow' : -> DiscussionUtil.urlFor('follow_thread', @id)
|
||||
'unfollow' : -> DiscussionUtil.urlFor('unfollow_thread', @id)
|
||||
'retrieve' : -> DiscussionUtil.urlFor('retrieve_single_thread', @discussion.id, @id)
|
||||
'reply' : -> DiscussionUtil.urlFor('create_comment', @id)
|
||||
'unvote' : -> DiscussionUtil.urlFor("undo_vote_for_#{@get('type')}", @id)
|
||||
'upvote' : -> DiscussionUtil.urlFor("upvote_#{@get('type')}", @id)
|
||||
'downvote' : -> DiscussionUtil.urlFor("downvote_#{@get('type')}", @id)
|
||||
'close' : -> DiscussionUtil.urlFor('openclose_thread', @id)
|
||||
'update' : -> DiscussionUtil.urlFor('update_thread', @id)
|
||||
'delete' : -> DiscussionUtil.urlFor('delete_thread', @id)
|
||||
'follow' : -> DiscussionUtil.urlFor('follow_thread', @id)
|
||||
'unfollow' : -> DiscussionUtil.urlFor('unfollow_thread', @id)
|
||||
'flagAbuse' : -> DiscussionUtil.urlFor("flagAbuse_#{@get('type')}", @id)
|
||||
'unFlagAbuse' : -> DiscussionUtil.urlFor("unFlagAbuse_#{@get('type')}", @id)
|
||||
'pinThread' : -> DiscussionUtil.urlFor("pin_thread", @id)
|
||||
'unPinThread' : -> DiscussionUtil.urlFor("un_pin_thread", @id)
|
||||
|
||||
@@ -157,6 +169,8 @@ if Backbone?
|
||||
'endorse': -> DiscussionUtil.urlFor('endorse_comment', @id)
|
||||
'update': -> DiscussionUtil.urlFor('update_comment', @id)
|
||||
'delete': -> DiscussionUtil.urlFor('delete_comment', @id)
|
||||
'flagAbuse' : -> DiscussionUtil.urlFor("flagAbuse_#{@get('type')}", @id)
|
||||
'unFlagAbuse' : -> DiscussionUtil.urlFor("unFlagAbuse_#{@get('type')}", @id)
|
||||
|
||||
getCommentsCount: ->
|
||||
count = 0
|
||||
|
||||
@@ -37,6 +37,9 @@ if Backbone?
|
||||
data['commentable_ids'] = options.commentable_ids
|
||||
when 'all'
|
||||
url = DiscussionUtil.urlFor 'threads'
|
||||
when 'flagged'
|
||||
data['flagged'] = true
|
||||
url = DiscussionUtil.urlFor 'search'
|
||||
when 'followed'
|
||||
url = DiscussionUtil.urlFor 'followed_threads', options.user_id
|
||||
if options['group_id']
|
||||
|
||||
@@ -18,8 +18,12 @@ class @DiscussionUtil
|
||||
@loadRoles: (roles)->
|
||||
@roleIds = roles
|
||||
|
||||
@loadFlagModerator: (what)->
|
||||
@isFlagModerator = ((what=="True") or (what == 1))
|
||||
|
||||
@loadRolesFromContainer: ->
|
||||
@loadRoles($("#discussion-container").data("roles"))
|
||||
@loadFlagModerator($("#discussion-container").data("flag-moderator"))
|
||||
|
||||
@isStaff: (user_id) ->
|
||||
staff = _.union(@roleIds['Staff'], @roleIds['Moderator'], @roleIds['Administrator'])
|
||||
@@ -48,9 +52,13 @@ class @DiscussionUtil
|
||||
update_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/update"
|
||||
create_comment : "/courses/#{$$course_id}/discussion/threads/#{param}/reply"
|
||||
delete_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/delete"
|
||||
flagAbuse_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/flagAbuse"
|
||||
unFlagAbuse_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/unFlagAbuse"
|
||||
flagAbuse_comment : "/courses/#{$$course_id}/discussion/comments/#{param}/flagAbuse"
|
||||
unFlagAbuse_comment : "/courses/#{$$course_id}/discussion/comments/#{param}/unFlagAbuse"
|
||||
upvote_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/upvote"
|
||||
downvote_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/downvote"
|
||||
pin_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/pin"
|
||||
pin_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/pin"
|
||||
un_pin_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/unpin"
|
||||
undo_vote_for_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/unvote"
|
||||
follow_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/follow"
|
||||
@@ -72,7 +80,7 @@ class @DiscussionUtil
|
||||
permanent_link_thread : "/courses/#{$$course_id}/discussion/forum/#{param}/threads/#{param1}"
|
||||
permanent_link_comment : "/courses/#{$$course_id}/discussion/forum/#{param}/threads/#{param1}##{param2}"
|
||||
user_profile : "/courses/#{$$course_id}/discussion/forum/users/#{param}"
|
||||
followed_threads : "/courses/#{$$course_id}/discussion/forum/users/#{param}/followed"
|
||||
followed_threads : "/courses/#{$$course_id}/discussion/forum/users/#{param}/followed"
|
||||
threads : "/courses/#{$$course_id}/discussion/forum"
|
||||
}[name]
|
||||
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
if Backbone?
|
||||
class @DiscussionContentView extends Backbone.View
|
||||
|
||||
|
||||
events:
|
||||
"click .discussion-flag-abuse": "toggleFlagAbuse"
|
||||
|
||||
|
||||
attrRenderer:
|
||||
endorsed: (endorsed) ->
|
||||
if endorsed
|
||||
@@ -94,7 +99,48 @@ if Backbone?
|
||||
|
||||
setWmdContent: (cls_identifier, text) =>
|
||||
DiscussionUtil.setWmdContent @$el, $.proxy(@$, @), cls_identifier, text
|
||||
|
||||
|
||||
initialize: ->
|
||||
@initLocal()
|
||||
@model.bind('change', @renderPartialAttrs, @)
|
||||
|
||||
|
||||
|
||||
toggleFlagAbuse: (event) ->
|
||||
event.preventDefault()
|
||||
if window.user.id in @model.get("abuse_flaggers") or (DiscussionUtil.isFlagModerator and @model.get("abuse_flaggers").length > 0)
|
||||
@unFlagAbuse()
|
||||
else
|
||||
@flagAbuse()
|
||||
|
||||
flagAbuse: ->
|
||||
url = @model.urlFor("flagAbuse")
|
||||
DiscussionUtil.safeAjax
|
||||
$elem: @$(".discussion-flag-abuse")
|
||||
url: url
|
||||
type: "POST"
|
||||
success: (response, textStatus) =>
|
||||
if textStatus == 'success'
|
||||
###
|
||||
note, we have to clone the array in order to trigger a change event
|
||||
###
|
||||
temp_array = _.clone(@model.get('abuse_flaggers'));
|
||||
temp_array.push(window.user.id)
|
||||
@model.set('abuse_flaggers', temp_array)
|
||||
|
||||
unFlagAbuse: ->
|
||||
url = @model.urlFor("unFlagAbuse")
|
||||
DiscussionUtil.safeAjax
|
||||
$elem: @$(".discussion-flag-abuse")
|
||||
url: url
|
||||
type: "POST"
|
||||
success: (response, textStatus) =>
|
||||
if textStatus == 'success'
|
||||
temp_array = _.clone(@model.get('abuse_flaggers'));
|
||||
temp_array.pop(window.user.id)
|
||||
# if you're an admin, clear this
|
||||
if DiscussionUtil.isFlagModerator
|
||||
temp_array = []
|
||||
|
||||
@model.set('abuse_flaggers', temp_array)
|
||||
|
||||
@@ -276,6 +276,11 @@ if Backbone?
|
||||
@$(".post-search-field").val("")
|
||||
@$('.cohort').show()
|
||||
@retrieveAllThreads()
|
||||
else if discussionId == "#flagged"
|
||||
@discussionIds = ""
|
||||
@$(".post-search-field").val("")
|
||||
@$('.cohort').hide()
|
||||
@retrieveFlaggedThreads()
|
||||
else if discussionId == "#following"
|
||||
@retrieveFollowed(event)
|
||||
@$('.cohort').hide()
|
||||
@@ -321,6 +326,12 @@ if Backbone?
|
||||
@collection.reset()
|
||||
@loadMorePages(event)
|
||||
|
||||
retrieveFlaggedThreads: (event)->
|
||||
@collection.current_page = 0
|
||||
@collection.reset()
|
||||
@mode = 'flagged'
|
||||
@loadMorePages(event)
|
||||
|
||||
sortThreads: (event) ->
|
||||
@$(".sort-bar a").removeClass("active")
|
||||
$(event.target).addClass("active")
|
||||
|
||||
@@ -3,6 +3,7 @@ if Backbone?
|
||||
|
||||
events:
|
||||
"click .discussion-vote": "toggleVote"
|
||||
"click .discussion-flag-abuse": "toggleFlagAbuse"
|
||||
"click .admin-pin": "togglePin"
|
||||
"click .action-follow": "toggleFollowing"
|
||||
"click .action-edit": "edit"
|
||||
@@ -25,6 +26,7 @@ if Backbone?
|
||||
@delegateEvents()
|
||||
@renderDogear()
|
||||
@renderVoted()
|
||||
@renderFlagged()
|
||||
@renderPinned()
|
||||
@renderAttrs()
|
||||
@$("span.timeago").timeago()
|
||||
@@ -42,6 +44,16 @@ if Backbone?
|
||||
@$("[data-role=discussion-vote]").addClass("is-cast")
|
||||
else
|
||||
@$("[data-role=discussion-vote]").removeClass("is-cast")
|
||||
|
||||
renderFlagged: =>
|
||||
if window.user.id in @model.get("abuse_flaggers") or (DiscussionUtil.isFlagModerator and @model.get("abuse_flaggers").length > 0)
|
||||
@$("[data-role=thread-flag]").addClass("flagged")
|
||||
@$("[data-role=thread-flag]").removeClass("notflagged")
|
||||
@$(".discussion-flag-abuse .flag-label").html("Misuse Reported")
|
||||
else
|
||||
@$("[data-role=thread-flag]").removeClass("flagged")
|
||||
@$("[data-role=thread-flag]").addClass("notflagged")
|
||||
@$(".discussion-flag-abuse .flag-label").html("Report Misuse")
|
||||
|
||||
renderPinned: =>
|
||||
if @model.get("pinned")
|
||||
@@ -56,6 +68,7 @@ if Backbone?
|
||||
|
||||
updateModelDetails: =>
|
||||
@renderVoted()
|
||||
@renderFlagged()
|
||||
@renderPinned()
|
||||
@$("[data-role=discussion-vote] .votes-count-number").html(@model.get("votes")["up_count"])
|
||||
|
||||
@@ -96,6 +109,7 @@ if Backbone?
|
||||
if textStatus == 'success'
|
||||
@model.set(response, {silent: true})
|
||||
|
||||
|
||||
unvote: ->
|
||||
window.user.unvote(@model)
|
||||
url = @model.urlFor("unvote")
|
||||
@@ -107,6 +121,7 @@ if Backbone?
|
||||
if textStatus == 'success'
|
||||
@model.set(response, {silent: true})
|
||||
|
||||
|
||||
edit: (event) ->
|
||||
@trigger "thread:edit", event
|
||||
|
||||
@@ -182,4 +197,4 @@ if Backbone?
|
||||
params = $.extend(params, user:{username: @model.username, user_url: @model.user_url})
|
||||
Mustache.render(@template, params)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ if Backbone?
|
||||
body = @getWmdContent("reply-body")
|
||||
return if not body.trim().length
|
||||
@setWmdContent("reply-body", "")
|
||||
comment = new Comment(body: body, created_at: (new Date()).toISOString(), username: window.user.get("username"), votes: { up_count: 0 }, endorsed: false, user_id: window.user.get("id"))
|
||||
comment = new Comment(body: body, created_at: (new Date()).toISOString(), username: window.user.get("username"), votes: { up_count: 0 }, abuse_flaggers:[], endorsed: false, user_id: window.user.get("id"))
|
||||
comment.set('thread', @model.get('thread'))
|
||||
@renderResponse(comment)
|
||||
@model.addComment()
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
if Backbone?
|
||||
class @ResponseCommentShowView extends DiscussionContentView
|
||||
|
||||
events:
|
||||
"click .discussion-flag-abuse": "toggleFlagAbuse"
|
||||
|
||||
tagName: "li"
|
||||
|
||||
initialize: ->
|
||||
super()
|
||||
@model.on "change", @updateModelDetails
|
||||
|
||||
render: ->
|
||||
@template = _.template($("#response-comment-show-template").html())
|
||||
params = @model.toJSON()
|
||||
@@ -11,6 +18,7 @@ if Backbone?
|
||||
@initLocal()
|
||||
@delegateEvents()
|
||||
@renderAttrs()
|
||||
@renderFlagged()
|
||||
@markAsStaff()
|
||||
@$el.find(".timeago").timeago()
|
||||
@convertMath()
|
||||
@@ -34,3 +42,17 @@ if Backbone?
|
||||
@$el.find("a.profile-link").after('<span class="staff-label">staff</span>')
|
||||
else if DiscussionUtil.isTA(@model.get("user_id"))
|
||||
@$el.find("a.profile-link").after('<span class="community-ta-label">Community TA</span>')
|
||||
|
||||
|
||||
renderFlagged: =>
|
||||
if window.user.id in @model.get("abuse_flaggers") or (DiscussionUtil.isFlagModerator and @model.get("abuse_flaggers").length > 0)
|
||||
@$("[data-role=thread-flag]").addClass("flagged")
|
||||
@$("[data-role=thread-flag]").removeClass("notflagged")
|
||||
else
|
||||
@$("[data-role=thread-flag]").removeClass("flagged")
|
||||
@$("[data-role=thread-flag]").addClass("notflagged")
|
||||
|
||||
updateModelDetails: =>
|
||||
@renderFlagged()
|
||||
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ if Backbone?
|
||||
"click .action-endorse": "toggleEndorse"
|
||||
"click .action-delete": "delete"
|
||||
"click .action-edit": "edit"
|
||||
"click .discussion-flag-abuse": "toggleFlagAbuse"
|
||||
|
||||
$: (selector) ->
|
||||
@$el.find(selector)
|
||||
@@ -23,6 +24,7 @@ if Backbone?
|
||||
if window.user.voted(@model)
|
||||
@$(".vote-btn").addClass("is-cast")
|
||||
@renderAttrs()
|
||||
@renderFlagged()
|
||||
@$el.find(".posted-details").timeago()
|
||||
@convertMath()
|
||||
@markAsStaff()
|
||||
@@ -70,6 +72,7 @@ if Backbone?
|
||||
success: (response, textStatus) =>
|
||||
if textStatus == 'success'
|
||||
@model.set(response)
|
||||
|
||||
|
||||
edit: (event) ->
|
||||
@trigger "response:edit", event
|
||||
@@ -92,3 +95,17 @@ if Backbone?
|
||||
url: url
|
||||
data: data
|
||||
type: "POST"
|
||||
|
||||
|
||||
renderFlagged: =>
|
||||
if window.user.id in @model.get("abuse_flaggers") or (DiscussionUtil.isFlagModerator and @model.get("abuse_flaggers").length > 0)
|
||||
@$("[data-role=thread-flag]").addClass("flagged")
|
||||
@$("[data-role=thread-flag]").removeClass("notflagged")
|
||||
@$(".discussion-flag-abuse .flag-label").html("Misuse Reported")
|
||||
else
|
||||
@$("[data-role=thread-flag]").removeClass("flagged")
|
||||
@$("[data-role=thread-flag]").addClass("notflagged")
|
||||
@$(".discussion-flag-abuse .flag-label").html("Report Misuse")
|
||||
|
||||
updateModelDetails: =>
|
||||
@renderFlagged()
|
||||
|
||||
@@ -77,7 +77,7 @@ if Backbone?
|
||||
body = @getWmdContent("comment-body")
|
||||
return if not body.trim().length
|
||||
@setWmdContent("comment-body", "")
|
||||
comment = new Comment(body: body, created_at: (new Date()).toISOString(), username: window.user.get("username"), user_id: window.user.get("id"), id:"unsaved")
|
||||
comment = new Comment(body: body, created_at: (new Date()).toISOString(), username: window.user.get("username"), abuse_flaggers:[], user_id: window.user.get("id"), id:"unsaved")
|
||||
view = @renderComment(comment)
|
||||
@hideEditorChrome()
|
||||
@trigger "comment:add", comment
|
||||
|
||||
1827
common/static/js/vendor/annotator.js
vendored
Normal file
1827
common/static/js/vendor/annotator.js
vendored
Normal file
@@ -0,0 +1,1827 @@
|
||||
/*
|
||||
** Annotator 1.2.6-dev-dc18206
|
||||
** https://github.com/okfn/annotator/
|
||||
**
|
||||
** Copyright 2012 Aron Carroll, Rufus Pollock, and Nick Stenning.
|
||||
** Dual licensed under the MIT and GPLv3 licenses.
|
||||
** https://github.com/okfn/annotator/blob/master/LICENSE
|
||||
**
|
||||
** Built at: 2013-05-16 18:01:57Z
|
||||
*/
|
||||
|
||||
|
||||
(function() {
|
||||
var $, Annotator, Delegator, LinkParser, Range, findChild, fn, functions, g, getNodeName, getNodePosition, gettext, simpleXPathJQuery, simpleXPathPure, util, _Annotator, _gettext, _i, _j, _len, _len1, _ref, _ref1, _t,
|
||||
__slice = [].slice,
|
||||
__hasProp = {}.hasOwnProperty,
|
||||
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
|
||||
__bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
|
||||
|
||||
simpleXPathJQuery = function(relativeRoot) {
|
||||
var jq;
|
||||
|
||||
jq = this.map(function() {
|
||||
var elem, idx, path, tagName;
|
||||
|
||||
path = '';
|
||||
elem = this;
|
||||
while (elem && elem.nodeType === 1 && elem !== relativeRoot) {
|
||||
tagName = elem.tagName.replace(":", "\\:");
|
||||
idx = $(elem.parentNode).children(tagName).index(elem) + 1;
|
||||
idx = "[" + idx + "]";
|
||||
path = "/" + elem.tagName.toLowerCase() + idx + path;
|
||||
elem = elem.parentNode;
|
||||
}
|
||||
return path;
|
||||
});
|
||||
return jq.get();
|
||||
};
|
||||
|
||||
simpleXPathPure = function(relativeRoot) {
|
||||
var getPathSegment, getPathTo, jq, rootNode;
|
||||
|
||||
getPathSegment = function(node) {
|
||||
var name, pos;
|
||||
|
||||
name = getNodeName(node);
|
||||
pos = getNodePosition(node);
|
||||
return "" + name + "[" + pos + "]";
|
||||
};
|
||||
rootNode = relativeRoot;
|
||||
getPathTo = function(node) {
|
||||
var xpath;
|
||||
|
||||
xpath = '';
|
||||
while (node !== rootNode) {
|
||||
if (node == null) {
|
||||
throw new Error("Called getPathTo on a node which was not a descendant of @rootNode. " + rootNode);
|
||||
}
|
||||
xpath = (getPathSegment(node)) + '/' + xpath;
|
||||
node = node.parentNode;
|
||||
}
|
||||
xpath = '/' + xpath;
|
||||
xpath = xpath.replace(/\/$/, '');
|
||||
return xpath;
|
||||
};
|
||||
jq = this.map(function() {
|
||||
var path;
|
||||
|
||||
path = getPathTo(this);
|
||||
return path;
|
||||
});
|
||||
return jq.get();
|
||||
};
|
||||
|
||||
findChild = function(node, type, index) {
|
||||
var child, children, found, name, _i, _len;
|
||||
|
||||
if (!node.hasChildNodes()) {
|
||||
throw new Error("XPath error: node has no children!");
|
||||
}
|
||||
children = node.childNodes;
|
||||
found = 0;
|
||||
for (_i = 0, _len = children.length; _i < _len; _i++) {
|
||||
child = children[_i];
|
||||
name = getNodeName(child);
|
||||
if (name === type) {
|
||||
found += 1;
|
||||
if (found === index) {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new Error("XPath error: wanted child not found.");
|
||||
};
|
||||
|
||||
getNodeName = function(node) {
|
||||
var nodeName;
|
||||
|
||||
nodeName = node.nodeName.toLowerCase();
|
||||
switch (nodeName) {
|
||||
case "#text":
|
||||
return "text()";
|
||||
case "#comment":
|
||||
return "comment()";
|
||||
case "#cdata-section":
|
||||
return "cdata-section()";
|
||||
default:
|
||||
return nodeName;
|
||||
}
|
||||
};
|
||||
|
||||
getNodePosition = function(node) {
|
||||
var pos, tmp;
|
||||
|
||||
pos = 0;
|
||||
tmp = node;
|
||||
while (tmp) {
|
||||
if (tmp.nodeName === node.nodeName) {
|
||||
pos++;
|
||||
}
|
||||
tmp = tmp.previousSibling;
|
||||
}
|
||||
return pos;
|
||||
};
|
||||
|
||||
gettext = null;
|
||||
|
||||
if (typeof Gettext !== "undefined" && Gettext !== null) {
|
||||
_gettext = new Gettext({
|
||||
domain: "annotator"
|
||||
});
|
||||
gettext = function(msgid) {
|
||||
return _gettext.gettext(msgid);
|
||||
};
|
||||
} else {
|
||||
gettext = function(msgid) {
|
||||
return msgid;
|
||||
};
|
||||
}
|
||||
|
||||
_t = function(msgid) {
|
||||
return gettext(msgid);
|
||||
};
|
||||
|
||||
if (!(typeof jQuery !== "undefined" && jQuery !== null ? (_ref = jQuery.fn) != null ? _ref.jquery : void 0 : void 0)) {
|
||||
console.error(_t("Annotator requires jQuery: have you included lib/vendor/jquery.js?"));
|
||||
}
|
||||
|
||||
if (!(JSON && JSON.parse && JSON.stringify)) {
|
||||
console.error(_t("Annotator requires a JSON implementation: have you included lib/vendor/json2.js?"));
|
||||
}
|
||||
|
||||
$ = jQuery.sub();
|
||||
|
||||
$.flatten = function(array) {
|
||||
var flatten;
|
||||
|
||||
flatten = function(ary) {
|
||||
var el, flat, _i, _len;
|
||||
|
||||
flat = [];
|
||||
for (_i = 0, _len = ary.length; _i < _len; _i++) {
|
||||
el = ary[_i];
|
||||
flat = flat.concat(el && $.isArray(el) ? flatten(el) : el);
|
||||
}
|
||||
return flat;
|
||||
};
|
||||
return flatten(array);
|
||||
};
|
||||
|
||||
$.plugin = function(name, object) {
|
||||
return jQuery.fn[name] = function(options) {
|
||||
var args;
|
||||
|
||||
args = Array.prototype.slice.call(arguments, 1);
|
||||
return this.each(function() {
|
||||
var instance;
|
||||
|
||||
instance = $.data(this, name);
|
||||
if (instance) {
|
||||
return options && instance[options].apply(instance, args);
|
||||
} else {
|
||||
instance = new object(this, options);
|
||||
return $.data(this, name, instance);
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
$.fn.textNodes = function() {
|
||||
var getTextNodes;
|
||||
|
||||
getTextNodes = function(node) {
|
||||
var nodes;
|
||||
|
||||
if (node && node.nodeType !== 3) {
|
||||
nodes = [];
|
||||
if (node.nodeType !== 8) {
|
||||
node = node.lastChild;
|
||||
while (node) {
|
||||
nodes.push(getTextNodes(node));
|
||||
node = node.previousSibling;
|
||||
}
|
||||
}
|
||||
return nodes.reverse();
|
||||
} else {
|
||||
return node;
|
||||
}
|
||||
};
|
||||
return this.map(function() {
|
||||
return $.flatten(getTextNodes(this));
|
||||
});
|
||||
};
|
||||
|
||||
$.fn.xpath = function(relativeRoot) {
|
||||
var exception, result;
|
||||
|
||||
try {
|
||||
result = simpleXPathJQuery.call(this, relativeRoot);
|
||||
} catch (_error) {
|
||||
exception = _error;
|
||||
console.log("jQuery-based XPath construction failed! Falling back to manual.");
|
||||
result = simpleXPathPure.call(this, relativeRoot);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
$.xpath = function(xp, root) {
|
||||
var idx, name, node, step, steps, _i, _len, _ref1;
|
||||
|
||||
steps = xp.substring(1).split("/");
|
||||
node = root;
|
||||
for (_i = 0, _len = steps.length; _i < _len; _i++) {
|
||||
step = steps[_i];
|
||||
_ref1 = step.split("["), name = _ref1[0], idx = _ref1[1];
|
||||
idx = idx != null ? parseInt((idx != null ? idx.split("]") : void 0)[0]) : 1;
|
||||
node = findChild(node, name.toLowerCase(), idx);
|
||||
}
|
||||
return node;
|
||||
};
|
||||
|
||||
$.escape = function(html) {
|
||||
return html.replace(/&(?!\w+;)/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
};
|
||||
|
||||
$.fn.escape = function(html) {
|
||||
if (arguments.length) {
|
||||
return this.html($.escape(html));
|
||||
}
|
||||
return this.html();
|
||||
};
|
||||
|
||||
$.fn.reverse = []._reverse || [].reverse;
|
||||
|
||||
functions = ["log", "debug", "info", "warn", "exception", "assert", "dir", "dirxml", "trace", "group", "groupEnd", "groupCollapsed", "time", "timeEnd", "profile", "profileEnd", "count", "clear", "table", "error", "notifyFirebug", "firebug", "userObjects"];
|
||||
|
||||
if (typeof console !== "undefined" && console !== null) {
|
||||
if (console.group == null) {
|
||||
console.group = function(name) {
|
||||
return console.log("GROUP: ", name);
|
||||
};
|
||||
}
|
||||
if (console.groupCollapsed == null) {
|
||||
console.groupCollapsed = console.group;
|
||||
}
|
||||
for (_i = 0, _len = functions.length; _i < _len; _i++) {
|
||||
fn = functions[_i];
|
||||
if (console[fn] == null) {
|
||||
console[fn] = function() {
|
||||
return console.log(_t("Not implemented:") + (" console." + name));
|
||||
};
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.console = {};
|
||||
for (_j = 0, _len1 = functions.length; _j < _len1; _j++) {
|
||||
fn = functions[_j];
|
||||
this.console[fn] = function() {};
|
||||
}
|
||||
this.console['error'] = function() {
|
||||
var args;
|
||||
|
||||
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
|
||||
return alert("ERROR: " + (args.join(', ')));
|
||||
};
|
||||
this.console['warn'] = function() {
|
||||
var args;
|
||||
|
||||
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
|
||||
return alert("WARNING: " + (args.join(', ')));
|
||||
};
|
||||
}
|
||||
|
||||
Delegator = (function() {
|
||||
Delegator.prototype.events = {};
|
||||
|
||||
Delegator.prototype.options = {};
|
||||
|
||||
Delegator.prototype.element = null;
|
||||
|
||||
function Delegator(element, options) {
|
||||
this.options = $.extend(true, {}, this.options, options);
|
||||
this.element = $(element);
|
||||
this.on = this.subscribe;
|
||||
this.addEvents();
|
||||
}
|
||||
|
||||
Delegator.prototype.addEvents = function() {
|
||||
var event, functionName, sel, selector, _k, _ref1, _ref2, _results;
|
||||
|
||||
_ref1 = this.events;
|
||||
_results = [];
|
||||
for (sel in _ref1) {
|
||||
functionName = _ref1[sel];
|
||||
_ref2 = sel.split(' '), selector = 2 <= _ref2.length ? __slice.call(_ref2, 0, _k = _ref2.length - 1) : (_k = 0, []), event = _ref2[_k++];
|
||||
_results.push(this.addEvent(selector.join(' '), event, functionName));
|
||||
}
|
||||
return _results;
|
||||
};
|
||||
|
||||
Delegator.prototype.addEvent = function(bindTo, event, functionName) {
|
||||
var closure, isBlankSelector,
|
||||
_this = this;
|
||||
|
||||
closure = function() {
|
||||
return _this[functionName].apply(_this, arguments);
|
||||
};
|
||||
isBlankSelector = typeof bindTo === 'string' && bindTo.replace(/\s+/g, '') === '';
|
||||
if (isBlankSelector) {
|
||||
bindTo = this.element;
|
||||
}
|
||||
if (typeof bindTo === 'string') {
|
||||
this.element.delegate(bindTo, event, closure);
|
||||
} else {
|
||||
if (this.isCustomEvent(event)) {
|
||||
this.subscribe(event, closure);
|
||||
} else {
|
||||
$(bindTo).bind(event, closure);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
Delegator.prototype.isCustomEvent = function(event) {
|
||||
event = event.split('.')[0];
|
||||
return $.inArray(event, Delegator.natives) === -1;
|
||||
};
|
||||
|
||||
Delegator.prototype.publish = function() {
|
||||
this.element.triggerHandler.apply(this.element, arguments);
|
||||
return this;
|
||||
};
|
||||
|
||||
Delegator.prototype.subscribe = function(event, callback) {
|
||||
var closure;
|
||||
|
||||
closure = function() {
|
||||
return callback.apply(this, [].slice.call(arguments, 1));
|
||||
};
|
||||
closure.guid = callback.guid = ($.guid += 1);
|
||||
this.element.bind(event, closure);
|
||||
return this;
|
||||
};
|
||||
|
||||
Delegator.prototype.unsubscribe = function() {
|
||||
this.element.unbind.apply(this.element, arguments);
|
||||
return this;
|
||||
};
|
||||
|
||||
return Delegator;
|
||||
|
||||
})();
|
||||
|
||||
Delegator.natives = (function() {
|
||||
var key, specials, val;
|
||||
|
||||
specials = (function() {
|
||||
var _ref1, _results;
|
||||
|
||||
_ref1 = jQuery.event.special;
|
||||
_results = [];
|
||||
for (key in _ref1) {
|
||||
if (!__hasProp.call(_ref1, key)) continue;
|
||||
val = _ref1[key];
|
||||
_results.push(key);
|
||||
}
|
||||
return _results;
|
||||
})();
|
||||
return "blur focus focusin focusout load resize scroll unload click dblclick\nmousedown mouseup mousemove mouseover mouseout mouseenter mouseleave\nchange select submit keydown keypress keyup error".split(/[^a-z]+/).concat(specials);
|
||||
})();
|
||||
|
||||
Range = {};
|
||||
|
||||
Range.sniff = function(r) {
|
||||
if (r.commonAncestorContainer != null) {
|
||||
return new Range.BrowserRange(r);
|
||||
} else if (typeof r.start === "string") {
|
||||
return new Range.SerializedRange(r);
|
||||
} else if (r.start && typeof r.start === "object") {
|
||||
return new Range.NormalizedRange(r);
|
||||
} else {
|
||||
console.error(_t("Could not sniff range type"));
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
Range.nodeFromXPath = function(xpath, root) {
|
||||
var customResolver, evaluateXPath, namespace, node, segment;
|
||||
|
||||
if (root == null) {
|
||||
root = document;
|
||||
}
|
||||
evaluateXPath = function(xp, nsResolver) {
|
||||
var exception;
|
||||
|
||||
if (nsResolver == null) {
|
||||
nsResolver = null;
|
||||
}
|
||||
try {
|
||||
return document.evaluate('.' + xp, root, nsResolver, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
|
||||
} catch (_error) {
|
||||
exception = _error;
|
||||
console.log("XPath evaluation failed.");
|
||||
console.log("Trying fallback...");
|
||||
return $.xpath(xp, root);
|
||||
}
|
||||
};
|
||||
if (!$.isXMLDoc(document.documentElement)) {
|
||||
return evaluateXPath(xpath);
|
||||
} else {
|
||||
customResolver = document.createNSResolver(document.ownerDocument === null ? document.documentElement : document.ownerDocument.documentElement);
|
||||
node = evaluateXPath(xpath, customResolver);
|
||||
if (!node) {
|
||||
xpath = ((function() {
|
||||
var _k, _len2, _ref1, _results;
|
||||
|
||||
_ref1 = xpath.split('/');
|
||||
_results = [];
|
||||
for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) {
|
||||
segment = _ref1[_k];
|
||||
if (segment && segment.indexOf(':') === -1) {
|
||||
_results.push(segment.replace(/^([a-z]+)/, 'xhtml:$1'));
|
||||
} else {
|
||||
_results.push(segment);
|
||||
}
|
||||
}
|
||||
return _results;
|
||||
})()).join('/');
|
||||
namespace = document.lookupNamespaceURI(null);
|
||||
customResolver = function(ns) {
|
||||
if (ns === 'xhtml') {
|
||||
return namespace;
|
||||
} else {
|
||||
return document.documentElement.getAttribute('xmlns:' + ns);
|
||||
}
|
||||
};
|
||||
node = evaluateXPath(xpath, customResolver);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
};
|
||||
|
||||
Range.RangeError = (function(_super) {
|
||||
__extends(RangeError, _super);
|
||||
|
||||
function RangeError(type, message, parent) {
|
||||
this.type = type;
|
||||
this.message = message;
|
||||
this.parent = parent != null ? parent : null;
|
||||
RangeError.__super__.constructor.call(this, this.message);
|
||||
}
|
||||
|
||||
return RangeError;
|
||||
|
||||
})(Error);
|
||||
|
||||
Range.BrowserRange = (function() {
|
||||
function BrowserRange(obj) {
|
||||
this.commonAncestorContainer = obj.commonAncestorContainer;
|
||||
this.startContainer = obj.startContainer;
|
||||
this.startOffset = obj.startOffset;
|
||||
this.endContainer = obj.endContainer;
|
||||
this.endOffset = obj.endOffset;
|
||||
}
|
||||
|
||||
BrowserRange.prototype.normalize = function(root) {
|
||||
var it, node, nr, offset, p, r, _k, _len2, _ref1;
|
||||
|
||||
if (this.tainted) {
|
||||
console.error(_t("You may only call normalize() once on a BrowserRange!"));
|
||||
return false;
|
||||
} else {
|
||||
this.tainted = true;
|
||||
}
|
||||
r = {};
|
||||
nr = {};
|
||||
_ref1 = ['start', 'end'];
|
||||
for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) {
|
||||
p = _ref1[_k];
|
||||
node = this[p + 'Container'];
|
||||
offset = this[p + 'Offset'];
|
||||
if (node.nodeType === 1) {
|
||||
it = node.childNodes[offset];
|
||||
node = it || node.childNodes[offset - 1];
|
||||
if (node.nodeType === 1 && !node.firstChild) {
|
||||
it = null;
|
||||
node = node.previousSibling;
|
||||
}
|
||||
while (node.nodeType !== 3) {
|
||||
node = node.firstChild;
|
||||
}
|
||||
offset = it ? 0 : node.nodeValue.length;
|
||||
}
|
||||
r[p] = node;
|
||||
r[p + 'Offset'] = offset;
|
||||
}
|
||||
nr.start = r.startOffset > 0 ? r.start.splitText(r.startOffset) : r.start;
|
||||
if (r.start === r.end) {
|
||||
if ((r.endOffset - r.startOffset) < nr.start.nodeValue.length) {
|
||||
nr.start.splitText(r.endOffset - r.startOffset);
|
||||
}
|
||||
nr.end = nr.start;
|
||||
} else {
|
||||
if (r.endOffset < r.end.nodeValue.length) {
|
||||
r.end.splitText(r.endOffset);
|
||||
}
|
||||
nr.end = r.end;
|
||||
}
|
||||
nr.commonAncestor = this.commonAncestorContainer;
|
||||
while (nr.commonAncestor.nodeType !== 1) {
|
||||
nr.commonAncestor = nr.commonAncestor.parentNode;
|
||||
}
|
||||
return new Range.NormalizedRange(nr);
|
||||
};
|
||||
|
||||
BrowserRange.prototype.serialize = function(root, ignoreSelector) {
|
||||
return this.normalize(root).serialize(root, ignoreSelector);
|
||||
};
|
||||
|
||||
return BrowserRange;
|
||||
|
||||
})();
|
||||
|
||||
Range.NormalizedRange = (function() {
|
||||
function NormalizedRange(obj) {
|
||||
this.commonAncestor = obj.commonAncestor;
|
||||
this.start = obj.start;
|
||||
this.end = obj.end;
|
||||
}
|
||||
|
||||
NormalizedRange.prototype.normalize = function(root) {
|
||||
return this;
|
||||
};
|
||||
|
||||
NormalizedRange.prototype.limit = function(bounds) {
|
||||
var nodes, parent, startParents, _k, _len2, _ref1;
|
||||
|
||||
nodes = $.grep(this.textNodes(), function(node) {
|
||||
return node.parentNode === bounds || $.contains(bounds, node.parentNode);
|
||||
});
|
||||
if (!nodes.length) {
|
||||
return null;
|
||||
}
|
||||
this.start = nodes[0];
|
||||
this.end = nodes[nodes.length - 1];
|
||||
startParents = $(this.start).parents();
|
||||
_ref1 = $(this.end).parents();
|
||||
for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) {
|
||||
parent = _ref1[_k];
|
||||
if (startParents.index(parent) !== -1) {
|
||||
this.commonAncestor = parent;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
NormalizedRange.prototype.serialize = function(root, ignoreSelector) {
|
||||
var end, serialization, start;
|
||||
|
||||
serialization = function(node, isEnd) {
|
||||
var n, nodes, offset, origParent, textNodes, xpath, _k, _len2;
|
||||
|
||||
if (ignoreSelector) {
|
||||
origParent = $(node).parents(":not(" + ignoreSelector + ")").eq(0);
|
||||
} else {
|
||||
origParent = $(node).parent();
|
||||
}
|
||||
xpath = origParent.xpath(root)[0];
|
||||
textNodes = origParent.textNodes();
|
||||
nodes = textNodes.slice(0, textNodes.index(node));
|
||||
offset = 0;
|
||||
for (_k = 0, _len2 = nodes.length; _k < _len2; _k++) {
|
||||
n = nodes[_k];
|
||||
offset += n.nodeValue.length;
|
||||
}
|
||||
if (isEnd) {
|
||||
return [xpath, offset + node.nodeValue.length];
|
||||
} else {
|
||||
return [xpath, offset];
|
||||
}
|
||||
};
|
||||
start = serialization(this.start);
|
||||
end = serialization(this.end, true);
|
||||
return new Range.SerializedRange({
|
||||
start: start[0],
|
||||
end: end[0],
|
||||
startOffset: start[1],
|
||||
endOffset: end[1]
|
||||
});
|
||||
};
|
||||
|
||||
NormalizedRange.prototype.text = function() {
|
||||
var node;
|
||||
|
||||
return ((function() {
|
||||
var _k, _len2, _ref1, _results;
|
||||
|
||||
_ref1 = this.textNodes();
|
||||
_results = [];
|
||||
for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) {
|
||||
node = _ref1[_k];
|
||||
_results.push(node.nodeValue);
|
||||
}
|
||||
return _results;
|
||||
}).call(this)).join('');
|
||||
};
|
||||
|
||||
NormalizedRange.prototype.textNodes = function() {
|
||||
var end, start, textNodes, _ref1;
|
||||
|
||||
textNodes = $(this.commonAncestor).textNodes();
|
||||
_ref1 = [textNodes.index(this.start), textNodes.index(this.end)], start = _ref1[0], end = _ref1[1];
|
||||
return $.makeArray(textNodes.slice(start, +end + 1 || 9e9));
|
||||
};
|
||||
|
||||
NormalizedRange.prototype.toRange = function() {
|
||||
var range;
|
||||
|
||||
range = document.createRange();
|
||||
range.setStartBefore(this.start);
|
||||
range.setEndAfter(this.end);
|
||||
return range;
|
||||
};
|
||||
|
||||
return NormalizedRange;
|
||||
|
||||
})();
|
||||
|
||||
Range.SerializedRange = (function() {
|
||||
function SerializedRange(obj) {
|
||||
this.start = obj.start;
|
||||
this.startOffset = obj.startOffset;
|
||||
this.end = obj.end;
|
||||
this.endOffset = obj.endOffset;
|
||||
}
|
||||
|
||||
SerializedRange.prototype.normalize = function(root) {
|
||||
var contains, e, length, node, p, range, tn, _k, _l, _len2, _len3, _ref1, _ref2;
|
||||
|
||||
range = {};
|
||||
_ref1 = ['start', 'end'];
|
||||
for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) {
|
||||
p = _ref1[_k];
|
||||
try {
|
||||
node = Range.nodeFromXPath(this[p], root);
|
||||
} catch (_error) {
|
||||
e = _error;
|
||||
throw new Range.RangeError(p, ("Error while finding " + p + " node: " + this[p] + ": ") + e, e);
|
||||
}
|
||||
if (!node) {
|
||||
throw new Range.RangeError(p, "Couldn't find " + p + " node: " + this[p]);
|
||||
}
|
||||
length = 0;
|
||||
_ref2 = $(node).textNodes();
|
||||
for (_l = 0, _len3 = _ref2.length; _l < _len3; _l++) {
|
||||
tn = _ref2[_l];
|
||||
if (length + tn.nodeValue.length >= this[p + 'Offset']) {
|
||||
range[p + 'Container'] = tn;
|
||||
range[p + 'Offset'] = this[p + 'Offset'] - length;
|
||||
break;
|
||||
} else {
|
||||
length += tn.nodeValue.length;
|
||||
}
|
||||
}
|
||||
if (range[p + 'Offset'] == null) {
|
||||
throw new Range.RangeError("" + p + "offset", "Couldn't find offset " + this[p + 'Offset'] + " in element " + this[p]);
|
||||
}
|
||||
}
|
||||
contains = document.compareDocumentPosition == null ? function(a, b) {
|
||||
return a.contains(b);
|
||||
} : function(a, b) {
|
||||
return a.compareDocumentPosition(b) & 16;
|
||||
};
|
||||
$(range.startContainer).parents().each(function() {
|
||||
if (contains(this, range.endContainer)) {
|
||||
range.commonAncestorContainer = this;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
return new Range.BrowserRange(range).normalize(root);
|
||||
};
|
||||
|
||||
SerializedRange.prototype.serialize = function(root, ignoreSelector) {
|
||||
return this.normalize(root).serialize(root, ignoreSelector);
|
||||
};
|
||||
|
||||
SerializedRange.prototype.toObject = function() {
|
||||
return {
|
||||
start: this.start,
|
||||
startOffset: this.startOffset,
|
||||
end: this.end,
|
||||
endOffset: this.endOffset
|
||||
};
|
||||
};
|
||||
|
||||
return SerializedRange;
|
||||
|
||||
})();
|
||||
|
||||
util = {
|
||||
uuid: (function() {
|
||||
var counter;
|
||||
|
||||
counter = 0;
|
||||
return function() {
|
||||
return counter++;
|
||||
};
|
||||
})(),
|
||||
getGlobal: function() {
|
||||
return (function() {
|
||||
return this;
|
||||
})();
|
||||
},
|
||||
maxZIndex: function($elements) {
|
||||
var all, el;
|
||||
|
||||
all = (function() {
|
||||
var _k, _len2, _results;
|
||||
|
||||
_results = [];
|
||||
for (_k = 0, _len2 = $elements.length; _k < _len2; _k++) {
|
||||
el = $elements[_k];
|
||||
if ($(el).css('position') === 'static') {
|
||||
_results.push(-1);
|
||||
} else {
|
||||
_results.push(parseInt($(el).css('z-index'), 10) || -1);
|
||||
}
|
||||
}
|
||||
return _results;
|
||||
})();
|
||||
return Math.max.apply(Math, all);
|
||||
},
|
||||
mousePosition: function(e, offsetEl) {
|
||||
var offset;
|
||||
|
||||
offset = $(offsetEl).position();
|
||||
return {
|
||||
top: e.pageY - offset.top,
|
||||
left: e.pageX - offset.left
|
||||
};
|
||||
},
|
||||
preventEventDefault: function(event) {
|
||||
return event != null ? typeof event.preventDefault === "function" ? event.preventDefault() : void 0 : void 0;
|
||||
}
|
||||
};
|
||||
|
||||
_Annotator = this.Annotator;
|
||||
|
||||
Annotator = (function(_super) {
|
||||
__extends(Annotator, _super);
|
||||
|
||||
Annotator.prototype.events = {
|
||||
".annotator-adder button click": "onAdderClick",
|
||||
".annotator-adder button mousedown": "onAdderMousedown",
|
||||
".annotator-hl mouseover": "onHighlightMouseover",
|
||||
".annotator-hl mouseout": "startViewerHideTimer"
|
||||
};
|
||||
|
||||
Annotator.prototype.html = {
|
||||
adder: '<div class="annotator-adder"><button>' + _t('Annotate') + '</button></div>',
|
||||
wrapper: '<div class="annotator-wrapper"></div>'
|
||||
};
|
||||
|
||||
Annotator.prototype.options = {
|
||||
readOnly: false
|
||||
};
|
||||
|
||||
Annotator.prototype.plugins = {};
|
||||
|
||||
Annotator.prototype.editor = null;
|
||||
|
||||
Annotator.prototype.viewer = null;
|
||||
|
||||
Annotator.prototype.selectedRanges = null;
|
||||
|
||||
Annotator.prototype.mouseIsDown = false;
|
||||
|
||||
Annotator.prototype.ignoreMouseup = false;
|
||||
|
||||
Annotator.prototype.viewerHideTimer = null;
|
||||
|
||||
function Annotator(element, options) {
|
||||
this.onDeleteAnnotation = __bind(this.onDeleteAnnotation, this);
|
||||
this.onEditAnnotation = __bind(this.onEditAnnotation, this);
|
||||
this.onAdderClick = __bind(this.onAdderClick, this);
|
||||
this.onAdderMousedown = __bind(this.onAdderMousedown, this);
|
||||
this.onHighlightMouseover = __bind(this.onHighlightMouseover, this);
|
||||
this.checkForEndSelection = __bind(this.checkForEndSelection, this);
|
||||
this.checkForStartSelection = __bind(this.checkForStartSelection, this);
|
||||
this.clearViewerHideTimer = __bind(this.clearViewerHideTimer, this);
|
||||
this.startViewerHideTimer = __bind(this.startViewerHideTimer, this);
|
||||
this.showViewer = __bind(this.showViewer, this);
|
||||
this.onEditorSubmit = __bind(this.onEditorSubmit, this);
|
||||
this.onEditorHide = __bind(this.onEditorHide, this);
|
||||
this.showEditor = __bind(this.showEditor, this); Annotator.__super__.constructor.apply(this, arguments);
|
||||
this.plugins = {};
|
||||
if (!Annotator.supported()) {
|
||||
return this;
|
||||
}
|
||||
if (!this.options.readOnly) {
|
||||
this._setupDocumentEvents();
|
||||
}
|
||||
this._setupWrapper()._setupViewer()._setupEditor();
|
||||
this._setupDynamicStyle();
|
||||
this.adder = $(this.html.adder).appendTo(this.wrapper).hide();
|
||||
}
|
||||
|
||||
Annotator.prototype._setupWrapper = function() {
|
||||
this.wrapper = $(this.html.wrapper);
|
||||
this.element.find('script').remove();
|
||||
this.element.wrapInner(this.wrapper);
|
||||
this.wrapper = this.element.find('.annotator-wrapper');
|
||||
return this;
|
||||
};
|
||||
|
||||
Annotator.prototype._setupViewer = function() {
|
||||
var _this = this;
|
||||
|
||||
this.viewer = new Annotator.Viewer({
|
||||
readOnly: this.options.readOnly
|
||||
});
|
||||
this.viewer.hide().on("edit", this.onEditAnnotation).on("delete", this.onDeleteAnnotation).addField({
|
||||
load: function(field, annotation) {
|
||||
if (annotation.text) {
|
||||
$(field).escape(annotation.text);
|
||||
} else {
|
||||
$(field).html("<i>" + (_t('No Comment')) + "</i>");
|
||||
}
|
||||
return _this.publish('annotationViewerTextField', [field, annotation]);
|
||||
}
|
||||
}).element.appendTo(this.wrapper).bind({
|
||||
"mouseover": this.clearViewerHideTimer,
|
||||
"mouseout": this.startViewerHideTimer
|
||||
});
|
||||
return this;
|
||||
};
|
||||
|
||||
Annotator.prototype._setupEditor = function() {
|
||||
this.editor = new Annotator.Editor();
|
||||
this.editor.hide().on('hide', this.onEditorHide).on('save', this.onEditorSubmit).addField({
|
||||
type: 'textarea',
|
||||
label: _t('Comments') + '\u2026',
|
||||
load: function(field, annotation) {
|
||||
return $(field).find('textarea').val(annotation.text || '');
|
||||
},
|
||||
submit: function(field, annotation) {
|
||||
return annotation.text = $(field).find('textarea').val();
|
||||
}
|
||||
});
|
||||
this.editor.element.appendTo(this.wrapper);
|
||||
return this;
|
||||
};
|
||||
|
||||
Annotator.prototype._setupDocumentEvents = function() {
|
||||
$(document).bind({
|
||||
"mouseup": this.checkForEndSelection,
|
||||
"mousedown": this.checkForStartSelection
|
||||
});
|
||||
return this;
|
||||
};
|
||||
|
||||
Annotator.prototype._setupDynamicStyle = function() {
|
||||
var max, sel, style, x;
|
||||
|
||||
style = $('#annotator-dynamic-style');
|
||||
if (!style.length) {
|
||||
style = $('<style id="annotator-dynamic-style"></style>').appendTo(document.head);
|
||||
}
|
||||
sel = '*' + ((function() {
|
||||
var _k, _len2, _ref1, _results;
|
||||
|
||||
_ref1 = ['adder', 'outer', 'notice', 'filter'];
|
||||
_results = [];
|
||||
for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) {
|
||||
x = _ref1[_k];
|
||||
_results.push(":not(.annotator-" + x + ")");
|
||||
}
|
||||
return _results;
|
||||
})()).join('');
|
||||
max = util.maxZIndex($(document.body).find(sel));
|
||||
max = Math.max(max, 1000);
|
||||
style.text([".annotator-adder, .annotator-outer, .annotator-notice {", " z-index: " + (max + 20) + ";", "}", ".annotator-filter {", " z-index: " + (max + 10) + ";", "}"].join("\n"));
|
||||
return this;
|
||||
};
|
||||
|
||||
Annotator.prototype.getSelectedRanges = function() {
|
||||
var browserRange, i, normedRange, r, ranges, rangesToIgnore, selection, _k, _len2;
|
||||
|
||||
selection = util.getGlobal().getSelection();
|
||||
ranges = [];
|
||||
rangesToIgnore = [];
|
||||
if (!selection.isCollapsed) {
|
||||
ranges = (function() {
|
||||
var _k, _ref1, _results;
|
||||
|
||||
_results = [];
|
||||
for (i = _k = 0, _ref1 = selection.rangeCount; 0 <= _ref1 ? _k < _ref1 : _k > _ref1; i = 0 <= _ref1 ? ++_k : --_k) {
|
||||
r = selection.getRangeAt(i);
|
||||
browserRange = new Range.BrowserRange(r);
|
||||
normedRange = browserRange.normalize().limit(this.wrapper[0]);
|
||||
if (normedRange === null) {
|
||||
rangesToIgnore.push(r);
|
||||
}
|
||||
_results.push(normedRange);
|
||||
}
|
||||
return _results;
|
||||
}).call(this);
|
||||
selection.removeAllRanges();
|
||||
}
|
||||
for (_k = 0, _len2 = rangesToIgnore.length; _k < _len2; _k++) {
|
||||
r = rangesToIgnore[_k];
|
||||
selection.addRange(r);
|
||||
}
|
||||
return $.grep(ranges, function(range) {
|
||||
if (range) {
|
||||
selection.addRange(range.toRange());
|
||||
}
|
||||
return range;
|
||||
});
|
||||
};
|
||||
|
||||
Annotator.prototype.createAnnotation = function() {
|
||||
var annotation;
|
||||
|
||||
annotation = {};
|
||||
this.publish('beforeAnnotationCreated', [annotation]);
|
||||
return annotation;
|
||||
};
|
||||
|
||||
Annotator.prototype.setupAnnotation = function(annotation) {
|
||||
var e, normed, normedRanges, r, root, _k, _l, _len2, _len3, _ref1;
|
||||
|
||||
root = this.wrapper[0];
|
||||
annotation.ranges || (annotation.ranges = this.selectedRanges);
|
||||
normedRanges = [];
|
||||
_ref1 = annotation.ranges;
|
||||
for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) {
|
||||
r = _ref1[_k];
|
||||
try {
|
||||
normedRanges.push(Range.sniff(r).normalize(root));
|
||||
} catch (_error) {
|
||||
e = _error;
|
||||
if (e instanceof Range.RangeError) {
|
||||
this.publish('rangeNormalizeFail', [annotation, r, e]);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
annotation.quote = [];
|
||||
annotation.ranges = [];
|
||||
annotation.highlights = [];
|
||||
for (_l = 0, _len3 = normedRanges.length; _l < _len3; _l++) {
|
||||
normed = normedRanges[_l];
|
||||
annotation.quote.push($.trim(normed.text()));
|
||||
annotation.ranges.push(normed.serialize(this.wrapper[0], '.annotator-hl'));
|
||||
$.merge(annotation.highlights, this.highlightRange(normed));
|
||||
}
|
||||
annotation.quote = annotation.quote.join(' / ');
|
||||
$(annotation.highlights).data('annotation', annotation);
|
||||
return annotation;
|
||||
};
|
||||
|
||||
Annotator.prototype.updateAnnotation = function(annotation) {
|
||||
this.publish('beforeAnnotationUpdated', [annotation]);
|
||||
this.publish('annotationUpdated', [annotation]);
|
||||
return annotation;
|
||||
};
|
||||
|
||||
Annotator.prototype.deleteAnnotation = function(annotation) {
|
||||
var child, h, _k, _len2, _ref1;
|
||||
|
||||
if (annotation.highlights != null) {
|
||||
_ref1 = annotation.highlights;
|
||||
for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) {
|
||||
h = _ref1[_k];
|
||||
if (!(h.parentNode != null)) {
|
||||
continue;
|
||||
}
|
||||
child = h.childNodes[0];
|
||||
$(h).replaceWith(h.childNodes);
|
||||
}
|
||||
}
|
||||
this.publish('annotationDeleted', [annotation]);
|
||||
return annotation;
|
||||
};
|
||||
|
||||
Annotator.prototype.loadAnnotations = function(annotations) {
|
||||
var clone, loader,
|
||||
_this = this;
|
||||
|
||||
if (annotations == null) {
|
||||
annotations = [];
|
||||
}
|
||||
loader = function(annList) {
|
||||
var n, now, _k, _len2;
|
||||
|
||||
if (annList == null) {
|
||||
annList = [];
|
||||
}
|
||||
now = annList.splice(0, 10);
|
||||
for (_k = 0, _len2 = now.length; _k < _len2; _k++) {
|
||||
n = now[_k];
|
||||
_this.setupAnnotation(n);
|
||||
}
|
||||
if (annList.length > 0) {
|
||||
return setTimeout((function() {
|
||||
return loader(annList);
|
||||
}), 10);
|
||||
} else {
|
||||
return _this.publish('annotationsLoaded', [clone]);
|
||||
}
|
||||
};
|
||||
clone = annotations.slice();
|
||||
if (annotations.length) {
|
||||
loader(annotations);
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
Annotator.prototype.dumpAnnotations = function() {
|
||||
if (this.plugins['Store']) {
|
||||
return this.plugins['Store'].dumpAnnotations();
|
||||
} else {
|
||||
console.warn(_t("Can't dump annotations without Store plugin."));
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
Annotator.prototype.highlightRange = function(normedRange, cssClass) {
|
||||
var hl, node, white, _k, _len2, _ref1, _results;
|
||||
|
||||
if (cssClass == null) {
|
||||
cssClass = 'annotator-hl';
|
||||
}
|
||||
white = /^\s*$/;
|
||||
hl = $("<span class='" + cssClass + "'></span>");
|
||||
_ref1 = normedRange.textNodes();
|
||||
_results = [];
|
||||
for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) {
|
||||
node = _ref1[_k];
|
||||
if (!white.test(node.nodeValue)) {
|
||||
_results.push($(node).wrapAll(hl).parent().show()[0]);
|
||||
}
|
||||
}
|
||||
return _results;
|
||||
};
|
||||
|
||||
Annotator.prototype.highlightRanges = function(normedRanges, cssClass) {
|
||||
var highlights, r, _k, _len2;
|
||||
|
||||
if (cssClass == null) {
|
||||
cssClass = 'annotator-hl';
|
||||
}
|
||||
highlights = [];
|
||||
for (_k = 0, _len2 = normedRanges.length; _k < _len2; _k++) {
|
||||
r = normedRanges[_k];
|
||||
$.merge(highlights, this.highlightRange(r, cssClass));
|
||||
}
|
||||
return highlights;
|
||||
};
|
||||
|
||||
Annotator.prototype.addPlugin = function(name, options) {
|
||||
var klass, _base;
|
||||
|
||||
if (this.plugins[name]) {
|
||||
console.error(_t("You cannot have more than one instance of any plugin."));
|
||||
} else {
|
||||
klass = Annotator.Plugin[name];
|
||||
if (typeof klass === 'function') {
|
||||
this.plugins[name] = new klass(this.element[0], options);
|
||||
this.plugins[name].annotator = this;
|
||||
if (typeof (_base = this.plugins[name]).pluginInit === "function") {
|
||||
_base.pluginInit();
|
||||
}
|
||||
} else {
|
||||
console.error(_t("Could not load ") + name + _t(" plugin. Have you included the appropriate <script> tag?"));
|
||||
}
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
Annotator.prototype.showEditor = function(annotation, location) {
|
||||
this.editor.element.css(location);
|
||||
this.editor.load(annotation);
|
||||
this.publish('annotationEditorShown', [this.editor, annotation]);
|
||||
return this;
|
||||
};
|
||||
|
||||
Annotator.prototype.onEditorHide = function() {
|
||||
this.publish('annotationEditorHidden', [this.editor]);
|
||||
return this.ignoreMouseup = false;
|
||||
};
|
||||
|
||||
Annotator.prototype.onEditorSubmit = function(annotation) {
|
||||
return this.publish('annotationEditorSubmit', [this.editor, annotation]);
|
||||
};
|
||||
|
||||
Annotator.prototype.showViewer = function(annotations, location) {
|
||||
this.viewer.element.css(location);
|
||||
this.viewer.load(annotations);
|
||||
return this.publish('annotationViewerShown', [this.viewer, annotations]);
|
||||
};
|
||||
|
||||
Annotator.prototype.startViewerHideTimer = function() {
|
||||
if (!this.viewerHideTimer) {
|
||||
return this.viewerHideTimer = setTimeout(this.viewer.hide, 250);
|
||||
}
|
||||
};
|
||||
|
||||
Annotator.prototype.clearViewerHideTimer = function() {
|
||||
clearTimeout(this.viewerHideTimer);
|
||||
return this.viewerHideTimer = false;
|
||||
};
|
||||
|
||||
Annotator.prototype.checkForStartSelection = function(event) {
|
||||
if (!(event && this.isAnnotator(event.target))) {
|
||||
this.startViewerHideTimer();
|
||||
return this.mouseIsDown = true;
|
||||
}
|
||||
};
|
||||
|
||||
Annotator.prototype.checkForEndSelection = function(event) {
|
||||
var container, range, _k, _len2, _ref1;
|
||||
|
||||
this.mouseIsDown = false;
|
||||
if (this.ignoreMouseup) {
|
||||
return;
|
||||
}
|
||||
this.selectedRanges = this.getSelectedRanges();
|
||||
_ref1 = this.selectedRanges;
|
||||
for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) {
|
||||
range = _ref1[_k];
|
||||
container = range.commonAncestor;
|
||||
if ($(container).hasClass('annotator-hl')) {
|
||||
container = $(container).parents('[class^=annotator-hl]')[0];
|
||||
}
|
||||
if (this.isAnnotator(container)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (event && this.selectedRanges.length) {
|
||||
return this.adder.css(util.mousePosition(event, this.wrapper[0])).show();
|
||||
} else {
|
||||
return this.adder.hide();
|
||||
}
|
||||
};
|
||||
|
||||
Annotator.prototype.isAnnotator = function(element) {
|
||||
return !!$(element).parents().andSelf().filter('[class^=annotator-]').not(this.wrapper).length;
|
||||
};
|
||||
|
||||
Annotator.prototype.onHighlightMouseover = function(event) {
|
||||
var annotations;
|
||||
|
||||
this.clearViewerHideTimer();
|
||||
if (this.mouseIsDown || this.viewer.isShown()) {
|
||||
return false;
|
||||
}
|
||||
annotations = $(event.target).parents('.annotator-hl').andSelf().map(function() {
|
||||
return $(this).data("annotation");
|
||||
});
|
||||
return this.showViewer($.makeArray(annotations), util.mousePosition(event, this.wrapper[0]));
|
||||
};
|
||||
|
||||
Annotator.prototype.onAdderMousedown = function(event) {
|
||||
if (event != null) {
|
||||
event.preventDefault();
|
||||
}
|
||||
return this.ignoreMouseup = true;
|
||||
};
|
||||
|
||||
Annotator.prototype.onAdderClick = function(event) {
|
||||
var annotation, cancel, cleanup, position, save,
|
||||
_this = this;
|
||||
|
||||
if (event != null) {
|
||||
event.preventDefault();
|
||||
}
|
||||
position = this.adder.position();
|
||||
this.adder.hide();
|
||||
annotation = this.setupAnnotation(this.createAnnotation());
|
||||
$(annotation.highlights).addClass('annotator-hl-temporary');
|
||||
save = function() {
|
||||
cleanup();
|
||||
$(annotation.highlights).removeClass('annotator-hl-temporary');
|
||||
return _this.publish('annotationCreated', [annotation]);
|
||||
};
|
||||
cancel = function() {
|
||||
cleanup();
|
||||
return _this.deleteAnnotation(annotation);
|
||||
};
|
||||
cleanup = function() {
|
||||
_this.unsubscribe('annotationEditorHidden', cancel);
|
||||
return _this.unsubscribe('annotationEditorSubmit', save);
|
||||
};
|
||||
this.subscribe('annotationEditorHidden', cancel);
|
||||
this.subscribe('annotationEditorSubmit', save);
|
||||
return this.showEditor(annotation, position);
|
||||
};
|
||||
|
||||
Annotator.prototype.onEditAnnotation = function(annotation) {
|
||||
var cleanup, offset, update,
|
||||
_this = this;
|
||||
|
||||
offset = this.viewer.element.position();
|
||||
update = function() {
|
||||
cleanup();
|
||||
return _this.updateAnnotation(annotation);
|
||||
};
|
||||
cleanup = function() {
|
||||
_this.unsubscribe('annotationEditorHidden', cleanup);
|
||||
return _this.unsubscribe('annotationEditorSubmit', update);
|
||||
};
|
||||
this.subscribe('annotationEditorHidden', cleanup);
|
||||
this.subscribe('annotationEditorSubmit', update);
|
||||
this.viewer.hide();
|
||||
return this.showEditor(annotation, offset);
|
||||
};
|
||||
|
||||
Annotator.prototype.onDeleteAnnotation = function(annotation) {
|
||||
this.viewer.hide();
|
||||
return this.deleteAnnotation(annotation);
|
||||
};
|
||||
|
||||
return Annotator;
|
||||
|
||||
})(Delegator);
|
||||
|
||||
Annotator.Plugin = (function(_super) {
|
||||
__extends(Plugin, _super);
|
||||
|
||||
function Plugin(element, options) {
|
||||
Plugin.__super__.constructor.apply(this, arguments);
|
||||
}
|
||||
|
||||
Plugin.prototype.pluginInit = function() {};
|
||||
|
||||
return Plugin;
|
||||
|
||||
})(Delegator);
|
||||
|
||||
g = util.getGlobal();
|
||||
|
||||
if (((_ref1 = g.document) != null ? _ref1.evaluate : void 0) == null) {
|
||||
$.getScript('http://assets.annotateit.org/vendor/xpath.min.js');
|
||||
}
|
||||
|
||||
if (g.getSelection == null) {
|
||||
$.getScript('http://assets.annotateit.org/vendor/ierange.min.js');
|
||||
}
|
||||
|
||||
if (g.JSON == null) {
|
||||
$.getScript('http://assets.annotateit.org/vendor/json2.min.js');
|
||||
}
|
||||
|
||||
Annotator.$ = $;
|
||||
|
||||
Annotator.Delegator = Delegator;
|
||||
|
||||
Annotator.Range = Range;
|
||||
|
||||
Annotator._t = _t;
|
||||
|
||||
Annotator.supported = function() {
|
||||
return (function() {
|
||||
return !!this.getSelection;
|
||||
})();
|
||||
};
|
||||
|
||||
Annotator.noConflict = function() {
|
||||
util.getGlobal().Annotator = _Annotator;
|
||||
return this;
|
||||
};
|
||||
|
||||
$.plugin('annotator', Annotator);
|
||||
|
||||
this.Annotator = Annotator;
|
||||
|
||||
Annotator.Widget = (function(_super) {
|
||||
__extends(Widget, _super);
|
||||
|
||||
Widget.prototype.classes = {
|
||||
hide: 'annotator-hide',
|
||||
invert: {
|
||||
x: 'annotator-invert-x',
|
||||
y: 'annotator-invert-y'
|
||||
}
|
||||
};
|
||||
|
||||
function Widget(element, options) {
|
||||
Widget.__super__.constructor.apply(this, arguments);
|
||||
this.classes = $.extend({}, Annotator.Widget.prototype.classes, this.classes);
|
||||
}
|
||||
|
||||
Widget.prototype.checkOrientation = function() {
|
||||
var current, offset, viewport, widget, window;
|
||||
|
||||
this.resetOrientation();
|
||||
window = $(util.getGlobal());
|
||||
widget = this.element.children(":first");
|
||||
offset = widget.offset();
|
||||
viewport = {
|
||||
top: window.scrollTop(),
|
||||
right: window.width() + window.scrollLeft()
|
||||
};
|
||||
current = {
|
||||
top: offset.top,
|
||||
right: offset.left + widget.width()
|
||||
};
|
||||
if ((current.top - viewport.top) < 0) {
|
||||
this.invertY();
|
||||
}
|
||||
if ((current.right - viewport.right) > 0) {
|
||||
this.invertX();
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
Widget.prototype.resetOrientation = function() {
|
||||
this.element.removeClass(this.classes.invert.x).removeClass(this.classes.invert.y);
|
||||
return this;
|
||||
};
|
||||
|
||||
Widget.prototype.invertX = function() {
|
||||
this.element.addClass(this.classes.invert.x);
|
||||
return this;
|
||||
};
|
||||
|
||||
Widget.prototype.invertY = function() {
|
||||
this.element.addClass(this.classes.invert.y);
|
||||
return this;
|
||||
};
|
||||
|
||||
Widget.prototype.isInvertedY = function() {
|
||||
return this.element.hasClass(this.classes.invert.y);
|
||||
};
|
||||
|
||||
Widget.prototype.isInvertedX = function() {
|
||||
return this.element.hasClass(this.classes.invert.x);
|
||||
};
|
||||
|
||||
return Widget;
|
||||
|
||||
})(Delegator);
|
||||
|
||||
Annotator.Editor = (function(_super) {
|
||||
__extends(Editor, _super);
|
||||
|
||||
Editor.prototype.events = {
|
||||
"form submit": "submit",
|
||||
".annotator-save click": "submit",
|
||||
".annotator-cancel click": "hide",
|
||||
".annotator-cancel mouseover": "onCancelButtonMouseover",
|
||||
"textarea keydown": "processKeypress"
|
||||
};
|
||||
|
||||
Editor.prototype.classes = {
|
||||
hide: 'annotator-hide',
|
||||
focus: 'annotator-focus'
|
||||
};
|
||||
|
||||
Editor.prototype.html = "<div class=\"annotator-outer annotator-editor\">\n <form class=\"annotator-widget\">\n <ul class=\"annotator-listing\"></ul>\n <div class=\"annotator-controls\">\n <a href=\"#cancel\" class=\"annotator-cancel\">" + _t('Cancel') + "</a>\n<a href=\"#save\" class=\"annotator-save annotator-focus\">" + _t('Save') + "</a>\n </div>\n </form>\n</div>";
|
||||
|
||||
Editor.prototype.options = {};
|
||||
|
||||
function Editor(options) {
|
||||
this.onCancelButtonMouseover = __bind(this.onCancelButtonMouseover, this);
|
||||
this.processKeypress = __bind(this.processKeypress, this);
|
||||
this.submit = __bind(this.submit, this);
|
||||
this.load = __bind(this.load, this);
|
||||
this.hide = __bind(this.hide, this);
|
||||
this.show = __bind(this.show, this); Editor.__super__.constructor.call(this, $(this.html)[0], options);
|
||||
this.fields = [];
|
||||
this.annotation = {};
|
||||
}
|
||||
|
||||
Editor.prototype.show = function(event) {
|
||||
util.preventEventDefault(event);
|
||||
this.element.removeClass(this.classes.hide);
|
||||
this.element.find('.annotator-save').addClass(this.classes.focus);
|
||||
this.checkOrientation();
|
||||
this.element.find(":input:first").focus();
|
||||
this.setupDraggables();
|
||||
return this.publish('show');
|
||||
};
|
||||
|
||||
Editor.prototype.hide = function(event) {
|
||||
util.preventEventDefault(event);
|
||||
this.element.addClass(this.classes.hide);
|
||||
return this.publish('hide');
|
||||
};
|
||||
|
||||
Editor.prototype.load = function(annotation) {
|
||||
var field, _k, _len2, _ref2;
|
||||
|
||||
this.annotation = annotation;
|
||||
this.publish('load', [this.annotation]);
|
||||
_ref2 = this.fields;
|
||||
for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) {
|
||||
field = _ref2[_k];
|
||||
field.load(field.element, this.annotation);
|
||||
}
|
||||
return this.show();
|
||||
};
|
||||
|
||||
Editor.prototype.submit = function(event) {
|
||||
var field, _k, _len2, _ref2;
|
||||
|
||||
util.preventEventDefault(event);
|
||||
_ref2 = this.fields;
|
||||
for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) {
|
||||
field = _ref2[_k];
|
||||
field.submit(field.element, this.annotation);
|
||||
}
|
||||
this.publish('save', [this.annotation]);
|
||||
return this.hide();
|
||||
};
|
||||
|
||||
Editor.prototype.addField = function(options) {
|
||||
var element, field, input;
|
||||
|
||||
field = $.extend({
|
||||
id: 'annotator-field-' + util.uuid(),
|
||||
type: 'input',
|
||||
label: '',
|
||||
load: function() {},
|
||||
submit: function() {}
|
||||
}, options);
|
||||
input = null;
|
||||
element = $('<li class="annotator-item" />');
|
||||
field.element = element[0];
|
||||
switch (field.type) {
|
||||
case 'textarea':
|
||||
input = $('<textarea />');
|
||||
break;
|
||||
case 'input':
|
||||
case 'checkbox':
|
||||
input = $('<input />');
|
||||
}
|
||||
element.append(input);
|
||||
input.attr({
|
||||
id: field.id,
|
||||
placeholder: field.label
|
||||
});
|
||||
if (field.type === 'checkbox') {
|
||||
input[0].type = 'checkbox';
|
||||
element.addClass('annotator-checkbox');
|
||||
element.append($('<label />', {
|
||||
"for": field.id,
|
||||
html: field.label
|
||||
}));
|
||||
}
|
||||
this.element.find('ul:first').append(element);
|
||||
this.fields.push(field);
|
||||
return field.element;
|
||||
};
|
||||
|
||||
Editor.prototype.checkOrientation = function() {
|
||||
var controls, list;
|
||||
|
||||
Editor.__super__.checkOrientation.apply(this, arguments);
|
||||
list = this.element.find('ul');
|
||||
controls = this.element.find('.annotator-controls');
|
||||
if (this.element.hasClass(this.classes.invert.y)) {
|
||||
controls.insertBefore(list);
|
||||
} else if (controls.is(':first-child')) {
|
||||
controls.insertAfter(list);
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
Editor.prototype.processKeypress = function(event) {
|
||||
if (event.keyCode === 27) {
|
||||
return this.hide();
|
||||
} else if (event.keyCode === 13 && !event.shiftKey) {
|
||||
return this.submit();
|
||||
}
|
||||
};
|
||||
|
||||
Editor.prototype.onCancelButtonMouseover = function() {
|
||||
return this.element.find('.' + this.classes.focus).removeClass(this.classes.focus);
|
||||
};
|
||||
|
||||
Editor.prototype.setupDraggables = function() {
|
||||
var classes, controls, cornerItem, editor, mousedown, onMousedown, onMousemove, onMouseup, resize, textarea, throttle,
|
||||
_this = this;
|
||||
|
||||
this.element.find('.annotator-resize').remove();
|
||||
if (this.element.hasClass(this.classes.invert.y)) {
|
||||
cornerItem = this.element.find('.annotator-item:last');
|
||||
} else {
|
||||
cornerItem = this.element.find('.annotator-item:first');
|
||||
}
|
||||
if (cornerItem) {
|
||||
$('<span class="annotator-resize"></span>').appendTo(cornerItem);
|
||||
}
|
||||
mousedown = null;
|
||||
classes = this.classes;
|
||||
editor = this.element;
|
||||
textarea = null;
|
||||
resize = editor.find('.annotator-resize');
|
||||
controls = editor.find('.annotator-controls');
|
||||
throttle = false;
|
||||
onMousedown = function(event) {
|
||||
if (event.target === this) {
|
||||
mousedown = {
|
||||
element: this,
|
||||
top: event.pageY,
|
||||
left: event.pageX
|
||||
};
|
||||
textarea = editor.find('textarea:first');
|
||||
$(window).bind({
|
||||
'mouseup.annotator-editor-resize': onMouseup,
|
||||
'mousemove.annotator-editor-resize': onMousemove
|
||||
});
|
||||
return event.preventDefault();
|
||||
}
|
||||
};
|
||||
onMouseup = function() {
|
||||
mousedown = null;
|
||||
return $(window).unbind('.annotator-editor-resize');
|
||||
};
|
||||
onMousemove = function(event) {
|
||||
var diff, directionX, directionY, height, width;
|
||||
|
||||
if (mousedown && throttle === false) {
|
||||
diff = {
|
||||
top: event.pageY - mousedown.top,
|
||||
left: event.pageX - mousedown.left
|
||||
};
|
||||
if (mousedown.element === resize[0]) {
|
||||
height = textarea.outerHeight();
|
||||
width = textarea.outerWidth();
|
||||
directionX = editor.hasClass(classes.invert.x) ? -1 : 1;
|
||||
directionY = editor.hasClass(classes.invert.y) ? 1 : -1;
|
||||
textarea.height(height + (diff.top * directionY));
|
||||
textarea.width(width + (diff.left * directionX));
|
||||
if (textarea.outerHeight() !== height) {
|
||||
mousedown.top = event.pageY;
|
||||
}
|
||||
if (textarea.outerWidth() !== width) {
|
||||
mousedown.left = event.pageX;
|
||||
}
|
||||
} else if (mousedown.element === controls[0]) {
|
||||
editor.css({
|
||||
top: parseInt(editor.css('top'), 10) + diff.top,
|
||||
left: parseInt(editor.css('left'), 10) + diff.left
|
||||
});
|
||||
mousedown.top = event.pageY;
|
||||
mousedown.left = event.pageX;
|
||||
}
|
||||
throttle = true;
|
||||
return setTimeout(function() {
|
||||
return throttle = false;
|
||||
}, 1000 / 60);
|
||||
}
|
||||
};
|
||||
resize.bind('mousedown', onMousedown);
|
||||
return controls.bind('mousedown', onMousedown);
|
||||
};
|
||||
|
||||
return Editor;
|
||||
|
||||
})(Annotator.Widget);
|
||||
|
||||
Annotator.Viewer = (function(_super) {
|
||||
__extends(Viewer, _super);
|
||||
|
||||
Viewer.prototype.events = {
|
||||
".annotator-edit click": "onEditClick",
|
||||
".annotator-delete click": "onDeleteClick"
|
||||
};
|
||||
|
||||
Viewer.prototype.classes = {
|
||||
hide: 'annotator-hide',
|
||||
showControls: 'annotator-visible'
|
||||
};
|
||||
|
||||
Viewer.prototype.html = {
|
||||
element: "<div class=\"annotator-outer annotator-viewer\">\n <ul class=\"annotator-widget annotator-listing\"></ul>\n</div>",
|
||||
item: "<li class=\"annotator-annotation annotator-item\">\n <span class=\"annotator-controls\">\n <a href=\"#\" title=\"View as webpage\" class=\"annotator-link\">View as webpage</a>\n <button title=\"Edit\" class=\"annotator-edit\">Edit</button>\n <button title=\"Delete\" class=\"annotator-delete\">Delete</button>\n </span>\n</li>"
|
||||
};
|
||||
|
||||
Viewer.prototype.options = {
|
||||
readOnly: false
|
||||
};
|
||||
|
||||
function Viewer(options) {
|
||||
this.onDeleteClick = __bind(this.onDeleteClick, this);
|
||||
this.onEditClick = __bind(this.onEditClick, this);
|
||||
this.load = __bind(this.load, this);
|
||||
this.hide = __bind(this.hide, this);
|
||||
this.show = __bind(this.show, this); Viewer.__super__.constructor.call(this, $(this.html.element)[0], options);
|
||||
this.item = $(this.html.item)[0];
|
||||
this.fields = [];
|
||||
this.annotations = [];
|
||||
}
|
||||
|
||||
Viewer.prototype.show = function(event) {
|
||||
var controls,
|
||||
_this = this;
|
||||
|
||||
util.preventEventDefault(event);
|
||||
controls = this.element.find('.annotator-controls').addClass(this.classes.showControls);
|
||||
setTimeout((function() {
|
||||
return controls.removeClass(_this.classes.showControls);
|
||||
}), 500);
|
||||
this.element.removeClass(this.classes.hide);
|
||||
return this.checkOrientation().publish('show');
|
||||
};
|
||||
|
||||
Viewer.prototype.isShown = function() {
|
||||
return !this.element.hasClass(this.classes.hide);
|
||||
};
|
||||
|
||||
Viewer.prototype.hide = function(event) {
|
||||
util.preventEventDefault(event);
|
||||
this.element.addClass(this.classes.hide);
|
||||
return this.publish('hide');
|
||||
};
|
||||
|
||||
Viewer.prototype.load = function(annotations) {
|
||||
var annotation, controller, controls, del, edit, element, field, item, link, links, list, _k, _l, _len2, _len3, _ref2, _ref3;
|
||||
|
||||
this.annotations = annotations || [];
|
||||
list = this.element.find('ul:first').empty();
|
||||
_ref2 = this.annotations;
|
||||
for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) {
|
||||
annotation = _ref2[_k];
|
||||
item = $(this.item).clone().appendTo(list).data('annotation', annotation);
|
||||
controls = item.find('.annotator-controls');
|
||||
link = controls.find('.annotator-link');
|
||||
edit = controls.find('.annotator-edit');
|
||||
del = controls.find('.annotator-delete');
|
||||
links = new LinkParser(annotation.links || []).get('alternate', {
|
||||
'type': 'text/html'
|
||||
});
|
||||
if (links.length === 0 || (links[0].href == null)) {
|
||||
link.remove();
|
||||
} else {
|
||||
link.attr('href', links[0].href);
|
||||
}
|
||||
if (this.options.readOnly) {
|
||||
edit.remove();
|
||||
del.remove();
|
||||
} else {
|
||||
controller = {
|
||||
showEdit: function() {
|
||||
return edit.removeAttr('disabled');
|
||||
},
|
||||
hideEdit: function() {
|
||||
return edit.attr('disabled', 'disabled');
|
||||
},
|
||||
showDelete: function() {
|
||||
return del.removeAttr('disabled');
|
||||
},
|
||||
hideDelete: function() {
|
||||
return del.attr('disabled', 'disabled');
|
||||
}
|
||||
};
|
||||
}
|
||||
_ref3 = this.fields;
|
||||
for (_l = 0, _len3 = _ref3.length; _l < _len3; _l++) {
|
||||
field = _ref3[_l];
|
||||
element = $(field.element).clone().appendTo(item)[0];
|
||||
field.load(element, annotation, controller);
|
||||
}
|
||||
}
|
||||
this.publish('load', [this.annotations]);
|
||||
return this.show();
|
||||
};
|
||||
|
||||
Viewer.prototype.addField = function(options) {
|
||||
var field;
|
||||
|
||||
field = $.extend({
|
||||
load: function() {}
|
||||
}, options);
|
||||
field.element = $('<div />')[0];
|
||||
this.fields.push(field);
|
||||
field.element;
|
||||
return this;
|
||||
};
|
||||
|
||||
Viewer.prototype.onEditClick = function(event) {
|
||||
return this.onButtonClick(event, 'edit');
|
||||
};
|
||||
|
||||
Viewer.prototype.onDeleteClick = function(event) {
|
||||
return this.onButtonClick(event, 'delete');
|
||||
};
|
||||
|
||||
Viewer.prototype.onButtonClick = function(event, type) {
|
||||
var item;
|
||||
|
||||
item = $(event.target).parents('.annotator-annotation');
|
||||
return this.publish(type, [item.data('annotation')]);
|
||||
};
|
||||
|
||||
return Viewer;
|
||||
|
||||
})(Annotator.Widget);
|
||||
|
||||
LinkParser = (function() {
|
||||
function LinkParser(data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
LinkParser.prototype.get = function(rel, cond) {
|
||||
var d, k, keys, match, v, _k, _len2, _ref2, _results;
|
||||
|
||||
if (cond == null) {
|
||||
cond = {};
|
||||
}
|
||||
cond = $.extend({}, cond, {
|
||||
rel: rel
|
||||
});
|
||||
keys = (function() {
|
||||
var _results;
|
||||
|
||||
_results = [];
|
||||
for (k in cond) {
|
||||
if (!__hasProp.call(cond, k)) continue;
|
||||
v = cond[k];
|
||||
_results.push(k);
|
||||
}
|
||||
return _results;
|
||||
})();
|
||||
_ref2 = this.data;
|
||||
_results = [];
|
||||
for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) {
|
||||
d = _ref2[_k];
|
||||
match = keys.reduce((function(m, k) {
|
||||
return m && (d[k] === cond[k]);
|
||||
}), true);
|
||||
if (match) {
|
||||
_results.push(d);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return _results;
|
||||
};
|
||||
|
||||
return LinkParser;
|
||||
|
||||
})();
|
||||
|
||||
Annotator = Annotator || {};
|
||||
|
||||
Annotator.Notification = (function(_super) {
|
||||
__extends(Notification, _super);
|
||||
|
||||
Notification.prototype.events = {
|
||||
"click": "hide"
|
||||
};
|
||||
|
||||
Notification.prototype.options = {
|
||||
html: "<div class='annotator-notice'></div>",
|
||||
classes: {
|
||||
show: "annotator-notice-show",
|
||||
info: "annotator-notice-info",
|
||||
success: "annotator-notice-success",
|
||||
error: "annotator-notice-error"
|
||||
}
|
||||
};
|
||||
|
||||
function Notification(options) {
|
||||
this.hide = __bind(this.hide, this);
|
||||
this.show = __bind(this.show, this); Notification.__super__.constructor.call(this, $(this.options.html).appendTo(document.body)[0], options);
|
||||
}
|
||||
|
||||
Notification.prototype.show = function(message, status) {
|
||||
if (status == null) {
|
||||
status = Annotator.Notification.INFO;
|
||||
}
|
||||
$(this.element).addClass(this.options.classes.show).addClass(this.options.classes[status]).escape(message || "");
|
||||
setTimeout(this.hide, 5000);
|
||||
return this;
|
||||
};
|
||||
|
||||
Notification.prototype.hide = function() {
|
||||
$(this.element).removeClass(this.options.classes.show);
|
||||
return this;
|
||||
};
|
||||
|
||||
return Notification;
|
||||
|
||||
})(Delegator);
|
||||
|
||||
Annotator.Notification.INFO = 'show';
|
||||
|
||||
Annotator.Notification.SUCCESS = 'success';
|
||||
|
||||
Annotator.Notification.ERROR = 'error';
|
||||
|
||||
$(function() {
|
||||
var notification;
|
||||
|
||||
notification = new Annotator.Notification;
|
||||
Annotator.showNotification = notification.show;
|
||||
return Annotator.hideNotification = notification.hide;
|
||||
});
|
||||
|
||||
}).call(this);
|
||||
2
common/static/js/vendor/annotator.min.js
vendored
Normal file
2
common/static/js/vendor/annotator.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
273
common/static/js/vendor/annotator.store.js
vendored
Normal file
273
common/static/js/vendor/annotator.store.js
vendored
Normal file
@@ -0,0 +1,273 @@
|
||||
/*
|
||||
** Annotator 1.2.6-dev-dc18206
|
||||
** https://github.com/okfn/annotator/
|
||||
**
|
||||
** Copyright 2012 Aron Carroll, Rufus Pollock, and Nick Stenning.
|
||||
** Dual licensed under the MIT and GPLv3 licenses.
|
||||
** https://github.com/okfn/annotator/blob/master/LICENSE
|
||||
**
|
||||
** Built at: 2013-05-16 18:02:02Z
|
||||
*/
|
||||
|
||||
|
||||
(function() {
|
||||
var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
|
||||
__hasProp = {}.hasOwnProperty,
|
||||
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
|
||||
__indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
|
||||
|
||||
Annotator.Plugin.Store = (function(_super) {
|
||||
__extends(Store, _super);
|
||||
|
||||
Store.prototype.events = {
|
||||
'annotationCreated': 'annotationCreated',
|
||||
'annotationDeleted': 'annotationDeleted',
|
||||
'annotationUpdated': 'annotationUpdated'
|
||||
};
|
||||
|
||||
Store.prototype.options = {
|
||||
annotationData: {},
|
||||
emulateHTTP: false,
|
||||
loadFromSearch: false,
|
||||
prefix: '/store',
|
||||
urls: {
|
||||
create: '/annotations',
|
||||
read: '/annotations/:id',
|
||||
update: '/annotations/:id',
|
||||
destroy: '/annotations/:id',
|
||||
search: '/search'
|
||||
}
|
||||
};
|
||||
|
||||
function Store(element, options) {
|
||||
this._onError = __bind(this._onError, this);
|
||||
this._onLoadAnnotationsFromSearch = __bind(this._onLoadAnnotationsFromSearch, this);
|
||||
this._onLoadAnnotations = __bind(this._onLoadAnnotations, this);
|
||||
this._getAnnotations = __bind(this._getAnnotations, this); Store.__super__.constructor.apply(this, arguments);
|
||||
this.annotations = [];
|
||||
}
|
||||
|
||||
Store.prototype.pluginInit = function() {
|
||||
if (!Annotator.supported()) {
|
||||
return;
|
||||
}
|
||||
if (this.annotator.plugins.Auth) {
|
||||
return this.annotator.plugins.Auth.withToken(this._getAnnotations);
|
||||
} else {
|
||||
return this._getAnnotations();
|
||||
}
|
||||
};
|
||||
|
||||
Store.prototype._getAnnotations = function() {
|
||||
if (this.options.loadFromSearch) {
|
||||
return this.loadAnnotationsFromSearch(this.options.loadFromSearch);
|
||||
} else {
|
||||
return this.loadAnnotations();
|
||||
}
|
||||
};
|
||||
|
||||
Store.prototype.annotationCreated = function(annotation) {
|
||||
var _this = this;
|
||||
|
||||
if (__indexOf.call(this.annotations, annotation) < 0) {
|
||||
this.registerAnnotation(annotation);
|
||||
return this._apiRequest('create', annotation, function(data) {
|
||||
if (data.id == null) {
|
||||
console.warn(Annotator._t("Warning: No ID returned from server for annotation "), annotation);
|
||||
}
|
||||
return _this.updateAnnotation(annotation, data);
|
||||
});
|
||||
} else {
|
||||
return this.updateAnnotation(annotation, {});
|
||||
}
|
||||
};
|
||||
|
||||
Store.prototype.annotationUpdated = function(annotation) {
|
||||
var _this = this;
|
||||
|
||||
if (__indexOf.call(this.annotations, annotation) >= 0) {
|
||||
return this._apiRequest('update', annotation, (function(data) {
|
||||
return _this.updateAnnotation(annotation, data);
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
Store.prototype.annotationDeleted = function(annotation) {
|
||||
var _this = this;
|
||||
|
||||
if (__indexOf.call(this.annotations, annotation) >= 0) {
|
||||
return this._apiRequest('destroy', annotation, (function() {
|
||||
return _this.unregisterAnnotation(annotation);
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
Store.prototype.registerAnnotation = function(annotation) {
|
||||
return this.annotations.push(annotation);
|
||||
};
|
||||
|
||||
Store.prototype.unregisterAnnotation = function(annotation) {
|
||||
return this.annotations.splice(this.annotations.indexOf(annotation), 1);
|
||||
};
|
||||
|
||||
Store.prototype.updateAnnotation = function(annotation, data) {
|
||||
if (__indexOf.call(this.annotations, annotation) < 0) {
|
||||
console.error(Annotator._t("Trying to update unregistered annotation!"));
|
||||
} else {
|
||||
$.extend(annotation, data);
|
||||
}
|
||||
return $(annotation.highlights).data('annotation', annotation);
|
||||
};
|
||||
|
||||
Store.prototype.loadAnnotations = function() {
|
||||
return this._apiRequest('read', null, this._onLoadAnnotations);
|
||||
};
|
||||
|
||||
Store.prototype._onLoadAnnotations = function(data) {
|
||||
if (data == null) {
|
||||
data = [];
|
||||
}
|
||||
this.annotations = this.annotations.concat(data);
|
||||
return this.annotator.loadAnnotations(data.slice());
|
||||
};
|
||||
|
||||
Store.prototype.loadAnnotationsFromSearch = function(searchOptions) {
|
||||
return this._apiRequest('search', searchOptions, this._onLoadAnnotationsFromSearch);
|
||||
};
|
||||
|
||||
Store.prototype._onLoadAnnotationsFromSearch = function(data) {
|
||||
if (data == null) {
|
||||
data = {};
|
||||
}
|
||||
return this._onLoadAnnotations(data.rows || []);
|
||||
};
|
||||
|
||||
Store.prototype.dumpAnnotations = function() {
|
||||
var ann, _i, _len, _ref, _results;
|
||||
|
||||
_ref = this.annotations;
|
||||
_results = [];
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
ann = _ref[_i];
|
||||
_results.push(JSON.parse(this._dataFor(ann)));
|
||||
}
|
||||
return _results;
|
||||
};
|
||||
|
||||
Store.prototype._apiRequest = function(action, obj, onSuccess) {
|
||||
var id, options, request, url;
|
||||
|
||||
id = obj && obj.id;
|
||||
url = this._urlFor(action, id);
|
||||
options = this._apiRequestOptions(action, obj, onSuccess);
|
||||
request = $.ajax(url, options);
|
||||
request._id = id;
|
||||
request._action = action;
|
||||
return request;
|
||||
};
|
||||
|
||||
Store.prototype._apiRequestOptions = function(action, obj, onSuccess) {
|
||||
var data, method, opts;
|
||||
|
||||
method = this._methodFor(action);
|
||||
opts = {
|
||||
type: method,
|
||||
headers: this.element.data('annotator:headers'),
|
||||
dataType: "json",
|
||||
success: onSuccess || function() {},
|
||||
error: this._onError
|
||||
};
|
||||
if (this.options.emulateHTTP && (method === 'PUT' || method === 'DELETE')) {
|
||||
opts.headers = $.extend(opts.headers, {
|
||||
'X-HTTP-Method-Override': method
|
||||
});
|
||||
opts.type = 'POST';
|
||||
}
|
||||
if (action === "search") {
|
||||
opts = $.extend(opts, {
|
||||
data: obj
|
||||
});
|
||||
return opts;
|
||||
}
|
||||
data = obj && this._dataFor(obj);
|
||||
if (this.options.emulateJSON) {
|
||||
opts.data = {
|
||||
json: data
|
||||
};
|
||||
if (this.options.emulateHTTP) {
|
||||
opts.data._method = method;
|
||||
}
|
||||
return opts;
|
||||
}
|
||||
opts = $.extend(opts, {
|
||||
data: data,
|
||||
contentType: "application/json; charset=utf-8"
|
||||
});
|
||||
return opts;
|
||||
};
|
||||
|
||||
Store.prototype._urlFor = function(action, id) {
|
||||
var url;
|
||||
|
||||
url = this.options.prefix != null ? this.options.prefix : '';
|
||||
url += this.options.urls[action];
|
||||
url = url.replace(/\/:id/, id != null ? '/' + id : '');
|
||||
url = url.replace(/:id/, id != null ? id : '');
|
||||
return url;
|
||||
};
|
||||
|
||||
Store.prototype._methodFor = function(action) {
|
||||
var table;
|
||||
|
||||
table = {
|
||||
'create': 'POST',
|
||||
'read': 'GET',
|
||||
'update': 'PUT',
|
||||
'destroy': 'DELETE',
|
||||
'search': 'GET'
|
||||
};
|
||||
return table[action];
|
||||
};
|
||||
|
||||
Store.prototype._dataFor = function(annotation) {
|
||||
var data, highlights;
|
||||
|
||||
highlights = annotation.highlights;
|
||||
delete annotation.highlights;
|
||||
$.extend(annotation, this.options.annotationData);
|
||||
data = JSON.stringify(annotation);
|
||||
if (highlights) {
|
||||
annotation.highlights = highlights;
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
Store.prototype._onError = function(xhr) {
|
||||
var action, message;
|
||||
|
||||
action = xhr._action;
|
||||
message = Annotator._t("Sorry we could not ") + action + Annotator._t(" this annotation");
|
||||
if (xhr._action === 'search') {
|
||||
message = Annotator._t("Sorry we could not search the store for annotations");
|
||||
} else if (xhr._action === 'read' && !xhr._id) {
|
||||
message = Annotator._t("Sorry we could not ") + action + Annotator._t(" the annotations from the store");
|
||||
}
|
||||
switch (xhr.status) {
|
||||
case 401:
|
||||
message = Annotator._t("Sorry you are not allowed to ") + action + Annotator._t(" this annotation");
|
||||
break;
|
||||
case 404:
|
||||
message = Annotator._t("Sorry we could not connect to the annotations store");
|
||||
break;
|
||||
case 500:
|
||||
message = Annotator._t("Sorry something went wrong with the annotation store");
|
||||
}
|
||||
Annotator.showNotification(message, Annotator.Notification.ERROR);
|
||||
return console.error(Annotator._t("API request failed:") + (" '" + xhr.status + "'"));
|
||||
};
|
||||
|
||||
return Store;
|
||||
|
||||
})(Annotator.Plugin);
|
||||
|
||||
}).call(this);
|
||||
1
common/static/js/vendor/annotator.store.min.js
vendored
Normal file
1
common/static/js/vendor/annotator.store.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
136
common/static/js/vendor/annotator.tags.js
vendored
Normal file
136
common/static/js/vendor/annotator.tags.js
vendored
Normal file
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
** Annotator 1.2.6-dev-dc18206
|
||||
** https://github.com/okfn/annotator/
|
||||
**
|
||||
** Copyright 2012 Aron Carroll, Rufus Pollock, and Nick Stenning.
|
||||
** Dual licensed under the MIT and GPLv3 licenses.
|
||||
** https://github.com/okfn/annotator/blob/master/LICENSE
|
||||
**
|
||||
** Built at: 2013-05-16 18:02:02Z
|
||||
*/
|
||||
|
||||
|
||||
(function() {
|
||||
var _ref,
|
||||
__bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
|
||||
__hasProp = {}.hasOwnProperty,
|
||||
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
|
||||
|
||||
Annotator.Plugin.Tags = (function(_super) {
|
||||
__extends(Tags, _super);
|
||||
|
||||
function Tags() {
|
||||
this.setAnnotationTags = __bind(this.setAnnotationTags, this);
|
||||
this.updateField = __bind(this.updateField, this); _ref = Tags.__super__.constructor.apply(this, arguments);
|
||||
return _ref;
|
||||
}
|
||||
|
||||
Tags.prototype.options = {
|
||||
parseTags: function(string) {
|
||||
var tags;
|
||||
|
||||
string = $.trim(string);
|
||||
tags = [];
|
||||
if (string) {
|
||||
tags = string.split(/\s+/);
|
||||
}
|
||||
return tags;
|
||||
},
|
||||
stringifyTags: function(array) {
|
||||
return array.join(" ");
|
||||
}
|
||||
};
|
||||
|
||||
Tags.prototype.field = null;
|
||||
|
||||
Tags.prototype.input = null;
|
||||
|
||||
Tags.prototype.pluginInit = function() {
|
||||
if (!Annotator.supported()) {
|
||||
return;
|
||||
}
|
||||
this.field = this.annotator.editor.addField({
|
||||
label: Annotator._t('Add some tags here') + '\u2026',
|
||||
load: this.updateField,
|
||||
submit: this.setAnnotationTags
|
||||
});
|
||||
this.annotator.viewer.addField({
|
||||
load: this.updateViewer
|
||||
});
|
||||
if (this.annotator.plugins.Filter) {
|
||||
this.annotator.plugins.Filter.addFilter({
|
||||
label: Annotator._t('Tag'),
|
||||
property: 'tags',
|
||||
isFiltered: Annotator.Plugin.Tags.filterCallback
|
||||
});
|
||||
}
|
||||
return this.input = $(this.field).find(':input');
|
||||
};
|
||||
|
||||
Tags.prototype.parseTags = function(string) {
|
||||
return this.options.parseTags(string);
|
||||
};
|
||||
|
||||
Tags.prototype.stringifyTags = function(array) {
|
||||
return this.options.stringifyTags(array);
|
||||
};
|
||||
|
||||
Tags.prototype.updateField = function(field, annotation) {
|
||||
var value;
|
||||
|
||||
value = '';
|
||||
if (annotation.tags) {
|
||||
value = this.stringifyTags(annotation.tags);
|
||||
}
|
||||
return this.input.val(value);
|
||||
};
|
||||
|
||||
Tags.prototype.setAnnotationTags = function(field, annotation) {
|
||||
return annotation.tags = this.parseTags(this.input.val());
|
||||
};
|
||||
|
||||
Tags.prototype.updateViewer = function(field, annotation) {
|
||||
field = $(field);
|
||||
if (annotation.tags && $.isArray(annotation.tags) && annotation.tags.length) {
|
||||
return field.addClass('annotator-tags').html(function() {
|
||||
var string;
|
||||
|
||||
return string = $.map(annotation.tags, function(tag) {
|
||||
return '<span class="annotator-tag">' + Annotator.$.escape(tag) + '</span>';
|
||||
}).join(' ');
|
||||
});
|
||||
} else {
|
||||
return field.remove();
|
||||
}
|
||||
};
|
||||
|
||||
return Tags;
|
||||
|
||||
})(Annotator.Plugin);
|
||||
|
||||
Annotator.Plugin.Tags.filterCallback = function(input, tags) {
|
||||
var keyword, keywords, matches, tag, _i, _j, _len, _len1;
|
||||
|
||||
if (tags == null) {
|
||||
tags = [];
|
||||
}
|
||||
matches = 0;
|
||||
keywords = [];
|
||||
if (input) {
|
||||
keywords = input.split(/\s+/g);
|
||||
for (_i = 0, _len = keywords.length; _i < _len; _i++) {
|
||||
keyword = keywords[_i];
|
||||
if (tags.length) {
|
||||
for (_j = 0, _len1 = tags.length; _j < _len1; _j++) {
|
||||
tag = tags[_j];
|
||||
if (tag.indexOf(keyword) !== -1) {
|
||||
matches += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return matches === keywords.length;
|
||||
};
|
||||
|
||||
}).call(this);
|
||||
1
common/static/js/vendor/annotator.tags.min.js
vendored
Normal file
1
common/static/js/vendor/annotator.tags.min.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
(function(){var _ref,__bind=function(fn,me){return function(){return fn.apply(me,arguments)}},__hasProp={}.hasOwnProperty,__extends=function(child,parent){for(var key in parent){if(__hasProp.call(parent,key))child[key]=parent[key]}function ctor(){this.constructor=child}ctor.prototype=parent.prototype;child.prototype=new ctor;child.__super__=parent.prototype;return child};Annotator.Plugin.Tags=function(_super){__extends(Tags,_super);function Tags(){this.setAnnotationTags=__bind(this.setAnnotationTags,this);this.updateField=__bind(this.updateField,this);_ref=Tags.__super__.constructor.apply(this,arguments);return _ref}Tags.prototype.options={parseTags:function(string){var tags;string=$.trim(string);tags=[];if(string){tags=string.split(/\s+/)}return tags},stringifyTags:function(array){return array.join(" ")}};Tags.prototype.field=null;Tags.prototype.input=null;Tags.prototype.pluginInit=function(){if(!Annotator.supported()){return}this.field=this.annotator.editor.addField({label:Annotator._t("Add some tags here")+"…",load:this.updateField,submit:this.setAnnotationTags});this.annotator.viewer.addField({load:this.updateViewer});if(this.annotator.plugins.Filter){this.annotator.plugins.Filter.addFilter({label:Annotator._t("Tag"),property:"tags",isFiltered:Annotator.Plugin.Tags.filterCallback})}return this.input=$(this.field).find(":input")};Tags.prototype.parseTags=function(string){return this.options.parseTags(string)};Tags.prototype.stringifyTags=function(array){return this.options.stringifyTags(array)};Tags.prototype.updateField=function(field,annotation){var value;value="";if(annotation.tags){value=this.stringifyTags(annotation.tags)}return this.input.val(value)};Tags.prototype.setAnnotationTags=function(field,annotation){return annotation.tags=this.parseTags(this.input.val())};Tags.prototype.updateViewer=function(field,annotation){field=$(field);if(annotation.tags&&$.isArray(annotation.tags)&&annotation.tags.length){return field.addClass("annotator-tags").html(function(){var string;return string=$.map(annotation.tags,function(tag){return'<span class="annotator-tag">'+Annotator.$.escape(tag)+"</span>"}).join(" ")})}else{return field.remove()}};return Tags}(Annotator.Plugin);Annotator.Plugin.Tags.filterCallback=function(input,tags){var keyword,keywords,matches,tag,_i,_j,_len,_len1;if(tags==null){tags=[]}matches=0;keywords=[];if(input){keywords=input.split(/\s+/g);for(_i=0,_len=keywords.length;_i<_len;_i++){keyword=keywords[_i];if(tags.length){for(_j=0,_len1=tags.length;_j<_len1;_j++){tag=tags[_j];if(tag.indexOf(keyword)!==-1){matches+=1}}}}}return matches===keywords.length}}).call(this);
|
||||
152
common/static/js/vendor/flot/jquery.timeago.js
vendored
Normal file
152
common/static/js/vendor/flot/jquery.timeago.js
vendored
Normal file
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* 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.11.4
|
||||
* @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-2012, Ryan McGeary (ryan -[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 if (typeof timestamp === "number") {
|
||||
return inWords(new Date(timestamp));
|
||||
} else {
|
||||
return inWords($.timeago.datetime(timestamp));
|
||||
}
|
||||
};
|
||||
var $t = $.timeago;
|
||||
|
||||
$.extend($.timeago, {
|
||||
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",
|
||||
wordSeparator: " ",
|
||||
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 < 42 && substitute($l.day, 1) ||
|
||||
days < 30 && substitute($l.days, Math.round(days)) ||
|
||||
days < 45 && substitute($l.month, 1) ||
|
||||
days < 365 && substitute($l.months, Math.round(days / 30)) ||
|
||||
years < 1.5 && substitute($l.year, 1) ||
|
||||
substitute($l.years, Math.round(years));
|
||||
|
||||
var separator = $l.wordSeparator === undefined ? " " : $l.wordSeparator;
|
||||
return $.trim([prefix, words, suffix].join(separator));
|
||||
},
|
||||
parse: function(iso8601) {
|
||||
var s = $.trim(iso8601);
|
||||
s = s.replace(/\.\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) {
|
||||
var iso8601 = $t.isTime(elem) ? $(elem).attr("datetime") : $(elem).attr("title");
|
||||
return $t.parse(iso8601);
|
||||
},
|
||||
isTime: function(elem) {
|
||||
// jQuery's `is()` doesn't play well with HTML5 in IE
|
||||
return $(elem).get(0).tagName.toLowerCase() === "time"; // $(elem).is("time");
|
||||
}
|
||||
});
|
||||
|
||||
$.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) });
|
||||
var text = $.trim(element.text());
|
||||
if (text.length > 0 && !($t.isTime(element) && element.attr("title"))) {
|
||||
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));
|
||||
152
common/static/js/vendor/jquery.timeago.js
vendored
Normal file
152
common/static/js/vendor/jquery.timeago.js
vendored
Normal file
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* 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.11.4
|
||||
* @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-2012, Ryan McGeary (ryan -[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 if (typeof timestamp === "number") {
|
||||
return inWords(new Date(timestamp));
|
||||
} else {
|
||||
return inWords($.timeago.datetime(timestamp));
|
||||
}
|
||||
};
|
||||
var $t = $.timeago;
|
||||
|
||||
$.extend($.timeago, {
|
||||
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",
|
||||
wordSeparator: " ",
|
||||
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 < 42 && substitute($l.day, 1) ||
|
||||
days < 30 && substitute($l.days, Math.round(days)) ||
|
||||
days < 45 && substitute($l.month, 1) ||
|
||||
days < 365 && substitute($l.months, Math.round(days / 30)) ||
|
||||
years < 1.5 && substitute($l.year, 1) ||
|
||||
substitute($l.years, Math.round(years));
|
||||
|
||||
var separator = $l.wordSeparator === undefined ? " " : $l.wordSeparator;
|
||||
return $.trim([prefix, words, suffix].join(separator));
|
||||
},
|
||||
parse: function(iso8601) {
|
||||
var s = $.trim(iso8601);
|
||||
s = s.replace(/\.\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) {
|
||||
var iso8601 = $t.isTime(elem) ? $(elem).attr("datetime") : $(elem).attr("title");
|
||||
return $t.parse(iso8601);
|
||||
},
|
||||
isTime: function(elem) {
|
||||
// jQuery's `is()` doesn't play well with HTML5 in IE
|
||||
return $(elem).get(0).tagName.toLowerCase() === "time"; // $(elem).is("time");
|
||||
}
|
||||
});
|
||||
|
||||
$.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) });
|
||||
var text = $.trim(element.text());
|
||||
if (text.length > 0 && !($t.isTime(element) && element.attr("title"))) {
|
||||
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));
|
||||
Reference in New Issue
Block a user