Commit a7014aef by Matthew Mongeau Committed by Ibrahim Awwal

Add backbone checks.

parent 1bd52684
class @DiscussionRouter extends Backbone.Router if Backbone?
routes: class @DiscussionRouter extends Backbone.Router
"": "allThreads" routes:
":forum_name/threads/:thread_id" : "showThread" "": "allThreads"
":forum_name/threads/:thread_id" : "showThread"
initialize: (options) ->
@discussion = options['discussion'] initialize: (options) ->
@nav = new DiscussionThreadListView(collection: @discussion, el: $(".sidebar")) @discussion = options['discussion']
@nav.on "thread:selected", @navigateToThread @nav = new DiscussionThreadListView(collection: @discussion, el: $(".sidebar"))
@nav.on "thread:removed", @navigateToAllThreads @nav.on "thread:selected", @navigateToThread
@nav.on "threads:rendered", @setActiveThread @nav.on "thread:removed", @navigateToAllThreads
@nav.render() @nav.on "threads:rendered", @setActiveThread
@nav.render()
@newPostView = new NewPostView(el: $(".new-post-article"), collection: @discussion)
@newPostView.on "thread:created", @navigateToThread @newPostView = new NewPostView(el: $(".new-post-article"), collection: @discussion)
@newPostView.on "thread:created", @navigateToThread
allThreads: ->
@nav.updateSidebar() allThreads: ->
setActiveThread: =>
if @thread
@nav.setActiveThread(@thread.get("id"))
showThread: (forum_name, thread_id) ->
@thread = @discussion.get(thread_id)
@setActiveThread()
if(@main)
@main.undelegateEvents()
@main = new DiscussionThreadView(el: $(".discussion-column"), model: @thread)
@main.render()
@main.on "thread:responses:rendered", =>
@nav.updateSidebar() @nav.updateSidebar()
navigateToThread: (thread_id) => setActiveThread: =>
thread = @discussion.get(thread_id) if @thread
@navigate("#{thread.get("commentable_id")}/threads/#{thread_id}", trigger: true) @nav.setActiveThread(@thread.get("id"))
navigateToAllThreads: => showThread: (forum_name, thread_id) ->
console.log "navigating" @thread = @discussion.get(thread_id)
@navigate("", trigger: true) @setActiveThread()
if(@main)
@main.undelegateEvents()
@main = new DiscussionThreadView(el: $(".discussion-column"), model: @thread)
@main.render()
@main.on "thread:responses:rendered", =>
@nav.updateSidebar()
navigateToThread: (thread_id) =>
thread = @discussion.get(thread_id)
@navigate("#{thread.get("commentable_id")}/threads/#{thread_id}", trigger: true)
navigateToAllThreads: =>
console.log "navigating"
@navigate("", trigger: true)
DiscussionApp = if Backbone?
start: (elem)-> DiscussionApp =
# TODO: Perhaps eliminate usage of global variables when possible start: (elem)->
element = $(elem) # TODO: Perhaps eliminate usage of global variables when possible
window.$$course_id = element.data("course-id") element = $(elem)
user_info = element.data("user-info") window.$$course_id = element.data("course-id")
threads = element.data("threads") user_info = element.data("user-info")
content_info = element.data("content-info") threads = element.data("threads")
window.user = new DiscussionUser(user_info) content_info = element.data("content-info")
Content.loadContentInfos(content_info) window.user = new DiscussionUser(user_info)
discussion = new Discussion(threads) Content.loadContentInfos(content_info)
new DiscussionRouter({discussion: discussion}) discussion = new Discussion(threads)
Backbone.history.start({pushState: true, root: "/courses/#{$$course_id}/discussion/forum/"}) new DiscussionRouter({discussion: discussion})
Backbone.history.start({pushState: true, root: "/courses/#{$$course_id}/discussion/forum/"})
$ -> $ ->
$("section.discussion").each (index, elem) -> $("section.discussion").each (index, elem) ->
DiscussionApp.start(elem) DiscussionApp.start(elem)
class @DiscussionUser extends Backbone.Model if Backbone?
following: (thread) -> class @DiscussionUser extends Backbone.Model
_.include(@get('subscribed_thread_ids'), thread.id) following: (thread) ->
_.include(@get('subscribed_thread_ids'), thread.id)
voted: (thread) -> voted: (thread) ->
_.include(@get('upvoted_ids'), thread.id) _.include(@get('upvoted_ids'), thread.id)
vote: (thread) -> vote: (thread) ->
@get('upvoted_ids').push(thread.id) @get('upvoted_ids').push(thread.id)
thread.vote() thread.vote()
unvote: (thread) -> unvote: (thread) ->
@set('upvoted_ids', _.without(@get('upvoted_ids'), thread.id)) @set('upvoted_ids', _.without(@get('upvoted_ids'), thread.id))
thread.unvote() thread.unvote()
class @DiscussionUserProfileView extends Backbone.View if Backbone?
toggleModeratorStatus: (event) -> class @DiscussionUserProfileView extends Backbone.View
confirmValue = confirm("Are you sure?") toggleModeratorStatus: (event) ->
if not confirmValue then return confirmValue = confirm("Are you sure?")
$elem = $(event.target) if not confirmValue then return
if $elem.hasClass("sidebar-promote-moderator-button") $elem = $(event.target)
isModerator = true if $elem.hasClass("sidebar-promote-moderator-button")
else if $elem.hasClass("sidebar-revoke-moderator-button") isModerator = true
isModerator = false else if $elem.hasClass("sidebar-revoke-moderator-button")
else isModerator = false
console.error "unrecognized moderator status" else
return console.error "unrecognized moderator status"
url = DiscussionUtil.urlFor('update_moderator_status', $$profiled_user_id) return
DiscussionUtil.safeAjax url = DiscussionUtil.urlFor('update_moderator_status', $$profiled_user_id)
$elem: $elem DiscussionUtil.safeAjax
url: url $elem: $elem
type: "POST" url: url
dataType: 'json' type: "POST"
data: dataType: 'json'
is_moderator: isModerator data:
error: (response, textStatus, e) -> is_moderator: isModerator
console.log e error: (response, textStatus, e) ->
success: (response, textStatus) => console.log e
parent = @$el.parent() success: (response, textStatus) =>
@$el.replaceWith(response.html) parent = @$el.parent()
view = new DiscussionUserProfileView el: parent.children(".user-profile") @$el.replaceWith(response.html)
view = new DiscussionUserProfileView el: parent.children(".user-profile")
events: events:
"click .sidebar-toggle-moderator-button": "toggleModeratorStatus" "click .sidebar-toggle-moderator-button": "toggleModeratorStatus"
class @DiscussionContentView extends Backbone.View if Backbone?
class @DiscussionContentView extends Backbone.View
attrRenderer:
endorsed: (endorsed) -> attrRenderer:
if endorsed endorsed: (endorsed) ->
@$(".action-endorse").addClass("is-endorsed") if endorsed
else @$(".action-endorse").addClass("is-endorsed")
@$(".action-endorse").removeClass("is-endorsed") else
@$(".action-endorse").removeClass("is-endorsed")
closed: (closed) ->
return if not @$(".action-openclose").length closed: (closed) ->
return if not @$(".post-status-closed").length return if not @$(".action-openclose").length
if closed return if not @$(".post-status-closed").length
@$(".post-status-closed").show() if closed
@$(".action-openclose").html(@$(".action-openclose").html().replace("Close", "Open")) @$(".post-status-closed").show()
@$(".discussion-reply-new").hide() @$(".action-openclose").html(@$(".action-openclose").html().replace("Close", "Open"))
else @$(".discussion-reply-new").hide()
@$(".post-status-closed").hide() else
@$(".action-openclose").html(@$(".action-openclose").html().replace("Open", "Close")) @$(".post-status-closed").hide()
@$(".discussion-reply-new").show() @$(".action-openclose").html(@$(".action-openclose").html().replace("Open", "Close"))
@$(".discussion-reply-new").show()
voted: (voted) ->
voted: (voted) ->
votes_point: (votes_point) ->
votes_point: (votes_point) ->
comments_count: (comments_count) ->
comments_count: (comments_count) ->
subscribed: (subscribed) ->
if subscribed subscribed: (subscribed) ->
@$(".dogear").addClass("is-followed") if subscribed
else @$(".dogear").addClass("is-followed")
@$(".dogear").removeClass("is-followed")
ability: (ability) ->
for action, selector of @abilityRenderer
if not ability[action]
selector.disable.apply(@)
else else
selector.enable.apply(@) @$(".dogear").removeClass("is-followed")
abilityRenderer: ability: (ability) ->
editable: for action, selector of @abilityRenderer
enable: -> @$(".action-edit").closest("li").show() if not ability[action]
disable: -> @$(".action-edit").closest("li").hide() selector.disable.apply(@)
can_delete: else
enable: -> @$(".action-delete").closest("li").show() selector.enable.apply(@)
disable: -> @$(".action-delete").closest("li").hide()
can_endorse: abilityRenderer:
enable: -> @$(".action-endorse").css("cursor", "auto") editable:
disable: -> @$(".action-endorse").css("cursor", "default") enable: -> @$(".action-edit").closest("li").show()
can_openclose: disable: -> @$(".action-edit").closest("li").hide()
enable: -> @$(".action-openclose").closest("li").show() can_delete:
disable: -> @$(".action-openclose").closest("li").hide() enable: -> @$(".action-delete").closest("li").show()
disable: -> @$(".action-delete").closest("li").hide()
renderPartialAttrs: -> can_endorse:
for attr, value of @model.changedAttributes() enable: -> @$(".action-endorse").css("cursor", "auto")
if @attrRenderer[attr] disable: -> @$(".action-endorse").css("cursor", "default")
@attrRenderer[attr].apply(@, [value]) can_openclose:
enable: -> @$(".action-openclose").closest("li").show()
renderAttrs: -> disable: -> @$(".action-openclose").closest("li").hide()
for attr, value of @model.attributes
if @attrRenderer[attr] renderPartialAttrs: ->
@attrRenderer[attr].apply(@, [value]) for attr, value of @model.changedAttributes()
if @attrRenderer[attr]
$: (selector) -> @attrRenderer[attr].apply(@, [value])
@$local.find(selector)
renderAttrs: ->
initLocal: -> for attr, value of @model.attributes
@$local = @$el.children(".local") if @attrRenderer[attr]
if not @$local.length @attrRenderer[attr].apply(@, [value])
@$local = @$el
@$delegateElement = @$local $: (selector) ->
@$local.find(selector)
makeWmdEditor: (cls_identifier) =>
if not @$el.find(".wmd-panel").length initLocal: ->
DiscussionUtil.makeWmdEditor @$el, $.proxy(@$, @), cls_identifier @$local = @$el.children(".local")
if not @$local.length
getWmdEditor: (cls_identifier) => @$local = @$el
DiscussionUtil.getWmdEditor @$el, $.proxy(@$, @), cls_identifier @$delegateElement = @$local
getWmdContent: (cls_identifier) => makeWmdEditor: (cls_identifier) =>
DiscussionUtil.getWmdContent @$el, $.proxy(@$, @), cls_identifier if not @$el.find(".wmd-panel").length
DiscussionUtil.makeWmdEditor @$el, $.proxy(@$, @), cls_identifier
setWmdContent: (cls_identifier, text) =>
DiscussionUtil.setWmdContent @$el, $.proxy(@$, @), cls_identifier, text getWmdEditor: (cls_identifier) =>
DiscussionUtil.getWmdEditor @$el, $.proxy(@$, @), cls_identifier
initialize: ->
@initLocal() getWmdContent: (cls_identifier) =>
@model.bind('change', @renderPartialAttrs, @) DiscussionUtil.getWmdContent @$el, $.proxy(@$, @), cls_identifier
setWmdContent: (cls_identifier, text) =>
DiscussionUtil.setWmdContent @$el, $.proxy(@$, @), cls_identifier, text
initialize: ->
@initLocal()
@model.bind('change', @renderPartialAttrs, @)
class @DiscussionThreadInlineView extends DiscussionContentView if Backbone?
expanded = false class @DiscussionThreadInlineView extends DiscussionContentView
events: expanded = false
"click .discussion-vote": "toggleVote" events:
"click .action-follow": "toggleFollowing" "click .discussion-vote": "toggleVote"
"click .discussion-submit-post": "submitComment" "click .action-follow": "toggleFollowing"
"click .action-edit": "edit" "click .discussion-submit-post": "submitComment"
"click .action-delete": "delete" "click .action-edit": "edit"
"click .action-openclose": "toggleClosed" "click .action-delete": "delete"
"click .expand-post": "expandPost" "click .action-openclose": "toggleClosed"
"click .collapse-post": "collapsePost" "click .expand-post": "expandPost"
"click .collapse-post": "collapsePost"
template: -> DiscussionUtil.getTemplate("_inline_thread")
template: -> DiscussionUtil.getTemplate("_inline_thread")
initLocal: ->
@$local = @$el.children(".discussion-article").children(".local") initLocal: ->
@$delegateElement = @$local @$local = @$el.children(".discussion-article").children(".local")
@$delegateElement = @$local
initialize: ->
super() initialize: ->
@model.on "change", @updateModelDetails super()
@model.on "change", @updateModelDetails
render: ->
if not @model.has('abbreviatedBody') render: ->
@abbreviateBody() if not @model.has('abbreviatedBody')
@$el.html(Mustache.render(@template(), $.extend(@model.toJSON(),{expanded: @expanded}) )) @abbreviateBody()
@initLocal() @$el.html(Mustache.render(@template(), $.extend(@model.toJSON(),{expanded: @expanded}) ))
@delegateEvents() @initLocal()
@renderDogear() @delegateEvents()
@renderVoted() @renderDogear()
@renderAttrs() @renderVoted()
@$("span.timeago").timeago() @renderAttrs()
@convertMath() @$("span.timeago").timeago()
if @expanded @convertMath()
if @expanded
@makeWmdEditor "reply-body"
@renderResponses()
@
renderDogear: ->
if window.user.following(@model)
@$(".dogear").addClass("is-followed")
renderVoted: =>
if window.user.voted(@model)
@$("[data-role=discussion-vote]").addClass("is-cast")
else
@$("[data-role=discussion-vote]").removeClass("is-cast")
updateModelDetails: =>
@renderVoted()
@$("[data-role=discussion-vote] .votes-count-number").html(@model.get("votes")["up_count"])
convertMath: ->
element = @$(".post-body")
element.html DiscussionUtil.postMathJaxProcessor DiscussionUtil.markdownWithHighlight element.html()
MathJax.Hub.Queue ["Typeset", MathJax.Hub, element[0]]
renderResponses: ->
DiscussionUtil.safeAjax
url: "/courses/#{$$course_id}/discussion/forum/#{@model.get('commentable_id')}/threads/#{@model.id}"
$loading: @$el
success: (data, textStatus, xhr) =>
@$el.find(".loading").remove()
Content.loadContentInfos(data['annotated_content_info'])
comments = new Comments(data['content']['children'])
comments.each @renderResponse
@trigger "thread:responses:rendered"
renderResponse: (response) =>
response.set('thread', @model)
view = new ThreadResponseView(model: response)
view.on "comment:add", @addComment
view.render()
@$el.find(".responses").append(view.el)
addComment: =>
@model.comment()
toggleVote: (event) ->
event.preventDefault()
if window.user.voted(@model)
@unvote()
else
@vote()
toggleFollowing: (event) ->
$elem = $(event.target)
url = null
console.log "follow"
if not @model.get('subscribed')
@model.follow()
url = @model.urlFor("follow")
else
@model.unfollow()
url = @model.urlFor("unfollow")
DiscussionUtil.safeAjax
$elem: $elem
url: url
type: "POST"
vote: ->
window.user.vote(@model)
url = @model.urlFor("upvote")
DiscussionUtil.safeAjax
$elem: @$(".discussion-vote")
url: url
type: "POST"
success: (response, textStatus) =>
if textStatus == 'success'
@model.set(response)
unvote: ->
window.user.unvote(@model)
url = @model.urlFor("unvote")
DiscussionUtil.safeAjax
$elem: @$(".discussion-vote")
url: url
type: "POST"
success: (response, textStatus) =>
if textStatus == 'success'
@model.set(response)
submitComment: (event) ->
event.preventDefault()
url = @model.urlFor('reply')
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.set('thread', @model.get('thread'))
@renderResponse(comment)
@model.addComment()
DiscussionUtil.safeAjax
$elem: $(event.target)
url: url
type: "POST"
dataType: 'json'
data:
body: body
success: (data, textStatus) =>
comment.updateInfo(data.annotated_content_info)
comment.set(data.content)
edit: ->
delete: (event) ->
url = @model.urlFor('delete')
if not @model.can('can_delete')
return
if not confirm "Are you sure to delete thread \"#{@model.get('title')}\"?"
return
@model.remove()
@$el.empty()
$elem = $(event.target)
DiscussionUtil.safeAjax
$elem: $elem
url: url
type: "POST"
success: (response, textStatus) =>
toggleClosed: (event) ->
$elem = $(event.target)
url = @model.urlFor('close')
closed = @model.get('closed')
data = { closed: not closed }
DiscussionUtil.safeAjax
$elem: $elem
url: url
data: data
type: "POST"
success: (response, textStatus) =>
@model.set('closed', not closed)
@model.set('ability', response.ability)
toggleEndorse: (event) ->
$elem = $(event.target)
url = @model.urlFor('endorse')
endorsed = @model.get('endorsed')
data = { endorsed: not endorsed }
DiscussionUtil.safeAjax
$elem: $elem
url: url
data: data
type: "POST"
success: (response, textStatus) =>
@model.set('endorsed', not endorsed)
abbreviateBody: ->
abbreviated = DiscussionUtil.abbreviateString @model.get('body'), 140
@model.set('abbreviatedBody', abbreviated)
expandPost: (event) ->
@expanded = true
@$el.addClass('expanded')
@$el.find('.post-body').html(@model.get('body'))
@convertMath()
@$el.find('.expand-post').css('display', 'none')
@$el.find('.collapse-post').css('display', 'block')
@$el.find('.post-extended-content').show()
@makeWmdEditor "reply-body" @makeWmdEditor "reply-body"
@renderResponses() if @$el.find('.loading').length
@ @renderResponses()
renderDogear: -> collapsePost: (event) ->
if window.user.following(@model) @expanded = false
@$(".dogear").addClass("is-followed") @$el.removeClass('expanded')
@$el.find('.post-body').html(@model.get('abbreviatedBody'))
renderVoted: => @convertMath()
if window.user.voted(@model) @$el.find('.collapse-post').css('display', 'none')
@$("[data-role=discussion-vote]").addClass("is-cast") @$el.find('.post-extended-content').hide()
else @$el.find('.expand-post').css('display', 'block')
@$("[data-role=discussion-vote]").removeClass("is-cast")
updateModelDetails: =>
@renderVoted()
@$("[data-role=discussion-vote] .votes-count-number").html(@model.get("votes")["up_count"])
convertMath: ->
element = @$(".post-body")
element.html DiscussionUtil.postMathJaxProcessor DiscussionUtil.markdownWithHighlight element.html()
MathJax.Hub.Queue ["Typeset", MathJax.Hub, element[0]]
renderResponses: ->
DiscussionUtil.safeAjax
url: "/courses/#{$$course_id}/discussion/forum/#{@model.get('commentable_id')}/threads/#{@model.id}"
$loading: @$el
success: (data, textStatus, xhr) =>
@$el.find(".loading").remove()
Content.loadContentInfos(data['annotated_content_info'])
comments = new Comments(data['content']['children'])
comments.each @renderResponse
@trigger "thread:responses:rendered"
renderResponse: (response) =>
response.set('thread', @model)
view = new ThreadResponseView(model: response)
view.on "comment:add", @addComment
view.render()
@$el.find(".responses").append(view.el)
addComment: =>
@model.comment()
toggleVote: (event) ->
event.preventDefault()
if window.user.voted(@model)
@unvote()
else
@vote()
toggleFollowing: (event) ->
$elem = $(event.target)
url = null
console.log "follow"
if not @model.get('subscribed')
@model.follow()
url = @model.urlFor("follow")
else
@model.unfollow()
url = @model.urlFor("unfollow")
DiscussionUtil.safeAjax
$elem: $elem
url: url
type: "POST"
vote: ->
window.user.vote(@model)
url = @model.urlFor("upvote")
DiscussionUtil.safeAjax
$elem: @$(".discussion-vote")
url: url
type: "POST"
success: (response, textStatus) =>
if textStatus == 'success'
@model.set(response)
unvote: ->
window.user.unvote(@model)
url = @model.urlFor("unvote")
DiscussionUtil.safeAjax
$elem: @$(".discussion-vote")
url: url
type: "POST"
success: (response, textStatus) =>
if textStatus == 'success'
@model.set(response)
submitComment: (event) ->
event.preventDefault()
url = @model.urlFor('reply')
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.set('thread', @model.get('thread'))
@renderResponse(comment)
@model.addComment()
DiscussionUtil.safeAjax
$elem: $(event.target)
url: url
type: "POST"
dataType: 'json'
data:
body: body
success: (data, textStatus) =>
comment.updateInfo(data.annotated_content_info)
comment.set(data.content)
edit: ->
delete: (event) ->
url = @model.urlFor('delete')
if not @model.can('can_delete')
return
if not confirm "Are you sure to delete thread \"#{@model.get('title')}\"?"
return
@model.remove()
@$el.empty()
$elem = $(event.target)
DiscussionUtil.safeAjax
$elem: $elem
url: url
type: "POST"
success: (response, textStatus) =>
toggleClosed: (event) ->
$elem = $(event.target)
url = @model.urlFor('close')
closed = @model.get('closed')
data = { closed: not closed }
DiscussionUtil.safeAjax
$elem: $elem
url: url
data: data
type: "POST"
success: (response, textStatus) =>
@model.set('closed', not closed)
@model.set('ability', response.ability)
toggleEndorse: (event) ->
$elem = $(event.target)
url = @model.urlFor('endorse')
endorsed = @model.get('endorsed')
data = { endorsed: not endorsed }
DiscussionUtil.safeAjax
$elem: $elem
url: url
data: data
type: "POST"
success: (response, textStatus) =>
@model.set('endorsed', not endorsed)
abbreviateBody: ->
abbreviated = DiscussionUtil.abbreviateString @model.get('body'), 140
@model.set('abbreviatedBody', abbreviated)
expandPost: (event) ->
@expanded = true
@$el.addClass('expanded')
@$el.find('.post-body').html(@model.get('body'))
@convertMath()
@$el.find('.expand-post').css('display', 'none')
@$el.find('.collapse-post').css('display', 'block')
@$el.find('.post-extended-content').show()
@makeWmdEditor "reply-body"
if @$el.find('.loading').length
@renderResponses()
collapsePost: (event) ->
@expanded = false
@$el.removeClass('expanded')
@$el.find('.post-body').html(@model.get('abbreviatedBody'))
@convertMath()
@$el.find('.collapse-post').css('display', 'none')
@$el.find('.post-extended-content').hide()
@$el.find('.expand-post').css('display', 'block')
class @DiscussionThreadListView extends Backbone.View if Backbone?
template: _.template($("#thread-list-template").html()) class @DiscussionThreadListView extends Backbone.View
events: template: _.template($("#thread-list-template").html())
"click .search": "showSearch" events:
"click .browse": "toggleTopicDrop" "click .search": "showSearch"
"keydown .post-search-field": "performSearch" "click .browse": "toggleTopicDrop"
"click .sort-bar a": "sortThreads" "keydown .post-search-field": "performSearch"
"click .browse-topic-drop-menu": "filterTopic" "click .sort-bar a": "sortThreads"
"click .browse-topic-drop-search-input": "ignoreClick" "click .browse-topic-drop-menu": "filterTopic"
"click .post-list a": "threadSelected" "click .browse-topic-drop-search-input": "ignoreClick"
"click .post-list a": "threadSelected"
initialize: ->
@displayedCollection = new Discussion(@collection.models) initialize: ->
@collection.on "change", @reloadDisplayedCollection @displayedCollection = new Discussion(@collection.models)
@collection.on "add", @addAndSelectThread @collection.on "change", @reloadDisplayedCollection
@sidebar_padding = 10 @collection.on "add", @addAndSelectThread
@sidebar_header_height = 87 @sidebar_padding = 10
@boardName @sidebar_header_height = 87
@boardName
reloadDisplayedCollection: (thread) =>
thread_id = thread.get('id') reloadDisplayedCollection: (thread) =>
content = @renderThread(thread) thread_id = thread.get('id')
current_el = @$("a[data-id=#{thread_id}]") content = @renderThread(thread)
active = current_el.hasClass("active") current_el = @$("a[data-id=#{thread_id}]")
current_el.replaceWith(content) active = current_el.hasClass("active")
if active current_el.replaceWith(content)
@setActiveThread(thread_id) if active
@setActiveThread(thread_id)
addAndSelectThread: (thread) =>
commentable_id = thread.get("commentable_id")
commentable = @$(".board-name[data-discussion_id]").filter(-> $(this).data("discussion_id").id == commentable_id)
commentable.click()
@displayedCollection.add thread
content = @renderThread(thread)
$(".post-list").prepend content
content.wrap("<li data-id='#{thread.get('id')}' />")
content.click()
addAndSelectThread: (thread) => updateSidebar: =>
commentable_id = thread.get("commentable_id")
commentable = @$(".board-name[data-discussion_id]").filter(-> $(this).data("discussion_id").id == commentable_id)
commentable.click()
@displayedCollection.add thread
content = @renderThread(thread)
$(".post-list").prepend content
content.wrap("<li data-id='#{thread.get('id')}' />")
content.click()
updateSidebar: => scrollTop = $(window).scrollTop();
windowHeight = $(window).height();
scrollTop = $(window).scrollTop(); discussionBody = $(".discussion-article")
windowHeight = $(window).height(); discussionsBodyTop = if discussionBody[0] then discussionBody.offset().top
discussionsBodyBottom = discussionsBodyTop + discussionBody.outerHeight()
discussionBody = $(".discussion-article") sidebar = $(".sidebar")
discussionsBodyTop = if discussionBody[0] then discussionBody.offset().top if scrollTop > discussionsBodyTop - @sidebar_padding
discussionsBodyBottom = discussionsBodyTop + discussionBody.outerHeight() sidebar.addClass('fixed');
sidebar.css('top', @sidebar_padding);
else
sidebar.removeClass('fixed');
sidebar.css('top', '0');
sidebar = $(".sidebar") sidebarWidth = .31 * $(".discussion-body").width();
if scrollTop > discussionsBodyTop - @sidebar_padding sidebar.css('width', sidebarWidth + 'px');
sidebar.addClass('fixed');
sidebar.css('top', @sidebar_padding);
else
sidebar.removeClass('fixed');
sidebar.css('top', '0');
sidebarWidth = .31 * $(".discussion-body").width(); sidebarHeight = windowHeight - Math.max(discussionsBodyTop - scrollTop, @sidebar_padding)
sidebar.css('width', sidebarWidth + 'px');
sidebarHeight = windowHeight - Math.max(discussionsBodyTop - scrollTop, @sidebar_padding) topOffset = scrollTop + windowHeight
discussionBottomOffset = discussionsBodyBottom + @sidebar_padding
amount = Math.max(topOffset - discussionBottomOffset, 0)
topOffset = scrollTop + windowHeight sidebarHeight = sidebarHeight - @sidebar_padding - amount
discussionBottomOffset = discussionsBodyBottom + @sidebar_padding sidebarHeight = Math.min(Math.max(sidebarHeight, 400), discussionBody.outerHeight())
amount = Math.max(topOffset - discussionBottomOffset, 0) sidebar.css 'height', sidebarHeight
sidebarHeight = sidebarHeight - @sidebar_padding - amount postListWrapper = @$('.post-list-wrapper')
sidebarHeight = Math.min(Math.max(sidebarHeight, 400), discussionBody.outerHeight()) postListWrapper.css('height', (sidebarHeight - @sidebar_header_height - 4) + 'px')
sidebar.css 'height', sidebarHeight
postListWrapper = @$('.post-list-wrapper')
postListWrapper.css('height', (sidebarHeight - @sidebar_header_height - 4) + 'px')
# Because we want the behavior that when the body is clicked the menu is
# closed, we need to ignore clicks in the search field and stop propagation.
# Without this, clicking the search field would also close the menu.
ignoreClick: (event) ->
event.stopPropagation()
# Because we want the behavior that when the body is clicked the menu is render: ->
# closed, we need to ignore clicks in the search field and stop propagation. @timer = 0
# Without this, clicking the search field would also close the menu. @$el.html(@template())
ignoreClick: (event) ->
event.stopPropagation()
render: -> $(window).bind "scroll", @updateSidebar
@timer = 0 $(window).bind "resize", @updateSidebar
@$el.html(@template())
$(window).bind "scroll", @updateSidebar @displayedCollection.on "reset", @renderThreads
$(window).bind "resize", @updateSidebar @displayedCollection.on "thread:remove", @renderThreads
@renderThreads()
@
@displayedCollection.on "reset", @renderThreads renderThreads: =>
@displayedCollection.on "thread:remove", @renderThreads @$(".post-list").html("")
@renderThreads() rendered = $("<div></div>")
@ for thread in @displayedCollection.models
content = @renderThread(thread)
rendered.append content
content.wrap("<li data-id='#{thread.get('id')}' />")
renderThreads: => @$(".post-list").html(rendered.html())
@$(".post-list").html("") @trigger "threads:rendered"
rendered = $("<div></div>")
for thread in @displayedCollection.models renderThread: (thread) =>
content = @renderThread(thread) content = $(_.template($("#thread-list-item-template").html())(thread.toJSON()))
rendered.append content if thread.get('subscribed')
content.wrap("<li data-id='#{thread.get('id')}' />") content.addClass("followed")
if thread.get('endorsed')
content.addClass("resolved")
@highlight(content)
highlight: (el) ->
el.html(el.html().replace(/&lt;mark&gt;/g, "<mark>").replace(/&lt;\/mark&gt;/g, "</mark>"))
renderThreadListItem: (thread) =>
view = new ThreadListItemView(model: thread)
view.on "thread:selected", @threadSelected
view.on "thread:removed", @threadRemoved
view.render()
@$(".post-list").append(view.el)
threadSelected: (e) =>
thread_id = $(e.target).closest("a").data("id")
@setActiveThread(thread_id)
@trigger("thread:selected", thread_id)
false
threadRemoved: (thread_id) =>
@trigger("thread:removed", thread_id)
setActiveThread: (thread_id) ->
@$("a[data-id!='#{thread_id}']").removeClass("active")
@$("a[data-id='#{thread_id}']").addClass("active")
showSearch: ->
@$(".search").addClass('is-open')
@$(".browse").removeClass('is-open')
setTimeout (-> @$(".post-search-field").focus()), 200
toggleTopicDrop: (event) =>
event.stopPropagation()
@$(".browse").toggleClass('is-dropped')
if @$(".browse").hasClass('is-dropped')
@$(".browse-topic-drop-menu-wrapper").show()
$(".browse-topic-drop-search-input").focus()
$("body").bind "click", @toggleTopicDrop
$("body").bind "keydown", @setActiveItem
else
@$(".browse-topic-drop-menu-wrapper").hide()
$("body").unbind "click", @toggleTopicDrop
$("body").unbind "keydown", @setActiveItem
setTopic: (event) ->
item = $(event.target).closest('a')
boardName = item.find(".board-name").html()
_.each item.parents('ul').not('.browse-topic-drop-menu'), (parent) ->
boardName = $(parent).siblings('a').find('.board-name').html() + ' / ' + boardName
@$(".current-board").html(@fitName(boardName))
fontSize = 16
@$(".current-board").css('font-size', '16px')
while @$(".current-board").width() > (@$el.width() * .8) - 40
fontSize--
if fontSize < 11
break
@$(".current-board").css('font-size', fontSize + 'px')
setSelectedTopic: (name) ->
@$(".current-board").html(@fitName(name))
getNameWidth: (name) ->
test = $("<div>")
test.css
"font-size": @$(".current-board").css('font-size')
opacity: 0
position: 'absolute'
left: -1000
top: -1000
$("body").append(test)
test.html(name)
width = test.width()
test.remove()
return width
fitName: (name) ->
width = @getNameWidth(name)
if width < @maxNameWidth
return name
path = (x.replace /^\s+|\s+$/g, "" for x in name.split("/"))
while path.length > 1
path.shift()
partialName = "... / " + path.join(" / ")
if @getNameWidth(partialName) < @maxNameWidth
return partialName
rawName = path[0]
name = "... / " + rawName
while @getNameWidth(name) > @maxNameWidth
rawName = rawName[0...rawName.length-1]
name = "... / " + rawName + " ..."
@$(".post-list").html(rendered.html())
@trigger "threads:rendered"
renderThread: (thread) =>
content = $(_.template($("#thread-list-item-template").html())(thread.toJSON()))
if thread.get('subscribed')
content.addClass("followed")
if thread.get('endorsed')
content.addClass("resolved")
@highlight(content)
highlight: (el) ->
el.html(el.html().replace(/&lt;mark&gt;/g, "<mark>").replace(/&lt;\/mark&gt;/g, "</mark>"))
renderThreadListItem: (thread) =>
view = new ThreadListItemView(model: thread)
view.on "thread:selected", @threadSelected
view.on "thread:removed", @threadRemoved
view.render()
@$(".post-list").append(view.el)
threadSelected: (e) =>
thread_id = $(e.target).closest("a").data("id")
@setActiveThread(thread_id)
@trigger("thread:selected", thread_id)
false
threadRemoved: (thread_id) =>
@trigger("thread:removed", thread_id)
setActiveThread: (thread_id) ->
@$("a[data-id!='#{thread_id}']").removeClass("active")
@$("a[data-id='#{thread_id}']").addClass("active")
showSearch: ->
@$(".search").addClass('is-open')
@$(".browse").removeClass('is-open')
setTimeout (-> @$(".post-search-field").focus()), 200
toggleTopicDrop: (event) =>
event.stopPropagation()
@$(".browse").toggleClass('is-dropped')
if @$(".browse").hasClass('is-dropped')
@$(".browse-topic-drop-menu-wrapper").show()
$(".browse-topic-drop-search-input").focus()
$("body").bind "click", @toggleTopicDrop
$("body").bind "keydown", @setActiveItem
else
@$(".browse-topic-drop-menu-wrapper").hide()
$("body").unbind "click", @toggleTopicDrop
$("body").unbind "keydown", @setActiveItem
setTopic: (event) ->
item = $(event.target).closest('a')
boardName = item.find(".board-name").html()
_.each item.parents('ul').not('.browse-topic-drop-menu'), (parent) ->
boardName = $(parent).siblings('a').find('.board-name').html() + ' / ' + boardName
@$(".current-board").html(@fitName(boardName))
fontSize = 16
@$(".current-board").css('font-size', '16px')
while @$(".current-board").width() > (@$el.width() * .8) - 40
fontSize--
if fontSize < 11
break
@$(".current-board").css('font-size', fontSize + 'px')
setSelectedTopic: (name) ->
@$(".current-board").html(@fitName(name))
getNameWidth: (name) ->
test = $("<div>")
test.css
"font-size": @$(".current-board").css('font-size')
opacity: 0
position: 'absolute'
left: -1000
top: -1000
$("body").append(test)
test.html(name)
width = test.width()
test.remove()
return width
fitName: (name) ->
width = @getNameWidth(name)
if width < @maxNameWidth
return name return name
path = (x.replace /^\s+|\s+$/g, "" for x in name.split("/"))
while path.length > 1 filterTopic: (event) ->
path.shift() @setTopic(event)
partialName = "... / " + path.join(" / ") item = $(event.target).closest('li')
if @getNameWidth(partialName) < @maxNameWidth if item.find("span.board-name").data("discussion_id") == "#all"
return partialName item = item.parent()
discussionIds = _.map item.find(".board-name[data-discussion_id]"), (board) -> $(board).data("discussion_id").id
rawName = path[0] filtered = @collection.filter (thread) =>
_.include(discussionIds, thread.get('commentable_id'))
name = "... / " + rawName @displayedCollection.reset filtered
while @getNameWidth(name) > @maxNameWidth sortThreads: (event) ->
rawName = rawName[0...rawName.length-1] @$(".sort-bar a").removeClass("active")
name = "... / " + rawName + " ..." $(event.target).addClass("active")
sortBy = $(event.target).data("sort")
return name if sortBy == "date"
@displayedCollection.comparator = @displayedCollection.sortByDateRecentFirst
filterTopic: (event) -> else if sortBy == "votes"
@setTopic(event) @displayedCollection.comparator = @displayedCollection.sortByVotes
item = $(event.target).closest('li') else if sortBy == "comments"
if item.find("span.board-name").data("discussion_id") == "#all" @displayedCollection.comparator = @displayedCollection.sortByComments
item = item.parent() @displayedCollection.sort()
discussionIds = _.map item.find(".board-name[data-discussion_id]"), (board) -> $(board).data("discussion_id").id
filtered = @collection.filter (thread) => performSearch: (event) ->
_.include(discussionIds, thread.get('commentable_id')) if event.which == 13
@displayedCollection.reset filtered event.preventDefault()
url = DiscussionUtil.urlFor("search")
sortThreads: (event) -> text = @$(".post-search-field").val()
@$(".sort-bar a").removeClass("active") DiscussionUtil.safeAjax
$(event.target).addClass("active") $elem: @$(".post-search-field")
sortBy = $(event.target).data("sort") data: { text: text }
if sortBy == "date" url: url
@displayedCollection.comparator = @displayedCollection.sortByDateRecentFirst type: "GET"
else if sortBy == "votes" success: (response, textStatus) =>
@displayedCollection.comparator = @displayedCollection.sortByVotes if textStatus == 'success'
else if sortBy == "comments" @collection.reset(response.discussion_data)
@displayedCollection.comparator = @displayedCollection.sortByComments @displayedCollection.reset(@collection.models)
@displayedCollection.sort()
setActiveItem: (event) ->
performSearch: (event) -> if event.which == 13
if event.which == 13 $(".browse-topic-drop-menu-wrapper .focused").click()
return
if event.which != 40 && event.which != 38
return
event.preventDefault() event.preventDefault()
url = DiscussionUtil.urlFor("search")
text = @$(".post-search-field").val()
DiscussionUtil.safeAjax
$elem: @$(".post-search-field")
data: { text: text }
url: url
type: "GET"
success: (response, textStatus) =>
if textStatus == 'success'
@collection.reset(response.discussion_data)
@displayedCollection.reset(@collection.models)
setActiveItem: (event) ->
if event.which == 13
$(".browse-topic-drop-menu-wrapper .focused").click()
return
if event.which != 40 && event.which != 38
return
event.preventDefault()
items = $.makeArray($(".browse-topic-drop-menu-wrapper a").not(".hidden"))
index = items.indexOf($('.browse-topic-drop-menu-wrapper .focused')[0])
if event.which == 40
index = Math.min(index + 1, items.length - 1)
if event.which == 38
index = Math.max(index - 1, 0)
$(".browse-topic-drop-menu-wrapper .focused").removeClass("focused")
$(items[index]).addClass("focused")
itemTop = $(items[index]).parent().offset().top
scrollTop = $(".browse-topic-drop-menu").scrollTop()
itemFromTop = $(".browse-topic-drop-menu").offset().top - itemTop
scrollTarget = Math.min(scrollTop - itemFromTop, scrollTop)
scrollTarget = Math.max(scrollTop - itemFromTop - $(".browse-topic-drop-menu").height() + $(items[index]).height(), scrollTarget)
$(".browse-topic-drop-menu").scrollTop(scrollTarget)
items = $.makeArray($(".browse-topic-drop-menu-wrapper a").not(".hidden"))
index = items.indexOf($('.browse-topic-drop-menu-wrapper .focused')[0])
if event.which == 40
index = Math.min(index + 1, items.length - 1)
if event.which == 38
index = Math.max(index - 1, 0)
$(".browse-topic-drop-menu-wrapper .focused").removeClass("focused")
$(items[index]).addClass("focused")
itemTop = $(items[index]).parent().offset().top
scrollTop = $(".browse-topic-drop-menu").scrollTop()
itemFromTop = $(".browse-topic-drop-menu").offset().top - itemTop
scrollTarget = Math.min(scrollTop - itemFromTop, scrollTop)
scrollTarget = Math.max(scrollTop - itemFromTop - $(".browse-topic-drop-menu").height() + $(items[index]).height(), scrollTarget)
$(".browse-topic-drop-menu").scrollTop(scrollTarget)
class @DiscussionThreadView extends DiscussionContentView if Backbone?
class @DiscussionThreadView extends DiscussionContentView
events: events:
"click .discussion-submit-post": "submitComment" "click .discussion-submit-post": "submitComment"
template: _.template($("#thread-template").html()) template: _.template($("#thread-template").html())
$: (selector) -> $: (selector) ->
@$el.find(selector) @$el.find(selector)
initialize: -> initialize: ->
super() super()
@showView = new DiscussionThreadShowView(model: @model) @showView = new DiscussionThreadShowView(model: @model)
@showView.bind "thread:delete", @delete @showView.bind "thread:delete", @delete
@showView.bind "thread:edit", @edit @showView.bind "thread:edit", @edit
render: -> render: ->
@$el.html(@template(@model.toJSON())) @$el.html(@template(@model.toJSON()))
@delegateEvents() @delegateEvents()
@showView.setElement(@$('.thread-content-wrapper')) @showView.setElement(@$('.thread-content-wrapper'))
@showView.render() @showView.render()
@showView.delegateEvents() @showView.delegateEvents()
@renderAttrs() @renderAttrs()
@$("span.timeago").timeago() @$("span.timeago").timeago()
@makeWmdEditor "reply-body" @makeWmdEditor "reply-body"
@renderResponses() @renderResponses()
@ @
renderResponses: -> renderResponses: ->
DiscussionUtil.safeAjax DiscussionUtil.safeAjax
url: "/courses/#{$$course_id}/discussion/forum/#{@model.get('commentable_id')}/threads/#{@model.id}" url: "/courses/#{$$course_id}/discussion/forum/#{@model.get('commentable_id')}/threads/#{@model.id}"
success: (data, textStatus, xhr) => success: (data, textStatus, xhr) =>
@$el.find(".loading").remove() @$el.find(".loading").remove()
Content.loadContentInfos(data['annotated_content_info']) Content.loadContentInfos(data['annotated_content_info'])
comments = new Comments(data['content']['children']) comments = new Comments(data['content']['children'])
comments.each @renderResponse comments.each @renderResponse
@trigger "thread:responses:rendered" @trigger "thread:responses:rendered"
renderResponse: (response) => renderResponse: (response) =>
response.set('thread', @model) response.set('thread', @model)
view = new ThreadResponseView(model: response) view = new ThreadResponseView(model: response)
view.on "comment:add", @addComment view.on "comment:add", @addComment
view.on "comment:endorse", @endorseThread view.on "comment:endorse", @endorseThread
view.render() view.render()
@$el.find(".responses").append(view.el) @$el.find(".responses").append(view.el)
addComment: => addComment: =>
@model.comment() @model.comment()
endorseThread: (endorsed) => endorseThread: (endorsed) =>
is_endorsed = @$el.find(".is-endorsed").length is_endorsed = @$el.find(".is-endorsed").length
@model.set 'endorsed', is_endorsed @model.set 'endorsed', is_endorsed
submitComment: (event) -> submitComment: (event) ->
event.preventDefault() event.preventDefault()
url = @model.urlFor('reply') url = @model.urlFor('reply')
body = @getWmdContent("reply-body") body = @getWmdContent("reply-body")
return if not body.trim().length return if not body.trim().length
@setWmdContent("reply-body", "") @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 }, endorsed: false, user_id: window.user.get("id"))
comment.set('thread', @model.get('thread')) comment.set('thread', @model.get('thread'))
@renderResponse(comment) @renderResponse(comment)
@model.addComment() @model.addComment()
DiscussionUtil.safeAjax DiscussionUtil.safeAjax
$elem: $(event.target) $elem: $(event.target)
url: url url: url
type: "POST" type: "POST"
dataType: 'json' dataType: 'json'
data: data:
body: body body: body
success: (data, textStatus) => success: (data, textStatus) =>
comment.updateInfo(data.annotated_content_info) comment.updateInfo(data.annotated_content_info)
comment.set(data.content) comment.set(data.content)
edit: -> edit: ->
delete: (event) -> delete: (event) ->
url = @model.urlFor('delete') url = @model.urlFor('delete')
if not @model.can('can_delete') if not @model.can('can_delete')
return return
if not confirm "Are you sure to delete thread \"#{@model.get('title')}\"?" if not confirm "Are you sure to delete thread \"#{@model.get('title')}\"?"
return return
@model.remove() @model.remove()
@$el.empty() @$el.empty()
$elem = $(event.target) $elem = $(event.target)
DiscussionUtil.safeAjax DiscussionUtil.safeAjax
$elem: $elem $elem: $elem
url: url url: url
type: "POST" type: "POST"
success: (response, textStatus) => success: (response, textStatus) =>
class @NewPostInlineView extends Backbone.View if Backbone?
class @NewPostInlineView extends Backbone.View
initialize: () ->
initialize: () ->
@topicId = @$(".topic").first().data("discussion-id")
@topicId = @$(".topic").first().data("discussion-id")
@maxNameWidth = 100
@maxNameWidth = 100
DiscussionUtil.makeWmdEditor @$el, $.proxy(@$, @), "new-post-body"
@$(".new-post-tags").tagsInput DiscussionUtil.tagsInputOptions() DiscussionUtil.makeWmdEditor @$el, $.proxy(@$, @), "new-post-body"
@$(".new-post-tags").tagsInput DiscussionUtil.tagsInputOptions()
events:
"submit .new-post-form": "createPost" events:
"submit .new-post-form": "createPost"
# Because we want the behavior that when the body is clicked the menu is
# closed, we need to ignore clicks in the search field and stop propagation. # Because we want the behavior that when the body is clicked the menu is
# Without this, clicking the search field would also close the menu. # closed, we need to ignore clicks in the search field and stop propagation.
ignoreClick: (event) -> # Without this, clicking the search field would also close the menu.
event.stopPropagation() ignoreClick: (event) ->
event.stopPropagation()
createPost: (event) ->
event.preventDefault() createPost: (event) ->
title = @$(".new-post-title").val() event.preventDefault()
body = @$(".new-post-body").find(".wmd-input").val() title = @$(".new-post-title").val()
tags = @$(".new-post-tags").val() body = @$(".new-post-body").find(".wmd-input").val()
tags = @$(".new-post-tags").val()
anonymous = false || @$("input.discussion-anonymous").is(":checked")
follow = false || @$("input.discussion-follow").is(":checked") anonymous = false || @$("input.discussion-anonymous").is(":checked")
follow = false || @$("input.discussion-follow").is(":checked")
url = DiscussionUtil.urlFor('create_thread', @topicId)
url = DiscussionUtil.urlFor('create_thread', @topicId)
DiscussionUtil.safeAjax
$elem: $(event.target) DiscussionUtil.safeAjax
$loading: $(event.target) if event $elem: $(event.target)
url: url $loading: $(event.target) if event
type: "POST" url: url
dataType: 'json' type: "POST"
async: false # TODO when the rest of the stuff below is made to work properly.. dataType: 'json'
data: async: false # TODO when the rest of the stuff below is made to work properly..
title: title data:
body: body title: title
tags: tags body: body
anonymous: anonymous tags: tags
auto_subscribe: follow anonymous: anonymous
error: DiscussionUtil.formErrorHandler(@$(".new-post-form-errors")) auto_subscribe: follow
success: (response, textStatus) => error: DiscussionUtil.formErrorHandler(@$(".new-post-form-errors"))
# TODO: Move this out of the callback, this makes it feel sluggish success: (response, textStatus) =>
thread = new Thread response['content'] # TODO: Move this out of the callback, this makes it feel sluggish
DiscussionUtil.clearFormErrors(@$(".new-post-form-errors")) thread = new Thread response['content']
@$el.hide() DiscussionUtil.clearFormErrors(@$(".new-post-form-errors"))
@$(".new-post-title").val("").attr("prev-text", "") @$el.hide()
@$(".new-post-body textarea").val("").attr("prev-text", "") @$(".new-post-title").val("").attr("prev-text", "")
@$(".new-post-tags").val("") @$(".new-post-body textarea").val("").attr("prev-text", "")
@$(".new-post-tags").importTags("") @$(".new-post-tags").val("")
@collection.add thread @$(".new-post-tags").importTags("")
@collection.add thread
class @NewPostView extends Backbone.View if Backbone?
class @NewPostView extends Backbone.View
initialize: () ->
@dropdownButton = @$(".topic_dropdown_button") initialize: () ->
@topicMenu = @$(".topic_menu_wrapper") @dropdownButton = @$(".topic_dropdown_button")
@topicMenu = @$(".topic_menu_wrapper")
@menuOpen = @dropdownButton.hasClass('dropped')
@menuOpen = @dropdownButton.hasClass('dropped')
@topicId = @$(".topic").first().data("discussion_id")
@topicText = @getFullTopicName(@$(".topic").first()) @topicId = @$(".topic").first().data("discussion_id")
@topicText = @getFullTopicName(@$(".topic").first())
@maxNameWidth = 100
@setSelectedTopic() @maxNameWidth = 100
@setSelectedTopic()
DiscussionUtil.makeWmdEditor @$el, $.proxy(@$, @), "new-post-body"
@$(".new-post-tags").tagsInput DiscussionUtil.tagsInputOptions() DiscussionUtil.makeWmdEditor @$el, $.proxy(@$, @), "new-post-body"
@$(".new-post-tags").tagsInput DiscussionUtil.tagsInputOptions()
events:
"submit .new-post-form": "createPost" events:
"click .topic_dropdown_button": "toggleTopicDropdown" "submit .new-post-form": "createPost"
"click .topic_menu_wrapper": "setTopic" "click .topic_dropdown_button": "toggleTopicDropdown"
"click .topic_menu_search": "ignoreClick" "click .topic_menu_wrapper": "setTopic"
"click .topic_menu_search": "ignoreClick"
# Because we want the behavior that when the body is clicked the menu is
# closed, we need to ignore clicks in the search field and stop propagation. # Because we want the behavior that when the body is clicked the menu is
# Without this, clicking the search field would also close the menu. # closed, we need to ignore clicks in the search field and stop propagation.
ignoreClick: (event) -> # Without this, clicking the search field would also close the menu.
event.stopPropagation() ignoreClick: (event) ->
event.stopPropagation()
toggleTopicDropdown: (event) ->
event.stopPropagation() toggleTopicDropdown: (event) ->
if @menuOpen event.stopPropagation()
@hideTopicDropdown() if @menuOpen
else @hideTopicDropdown()
@showTopicDropdown() else
@showTopicDropdown()
showTopicDropdown: () ->
@menuOpen = true showTopicDropdown: () ->
@dropdownButton.addClass('dropped') @menuOpen = true
@topicMenu.show() @dropdownButton.addClass('dropped')
$(".form-topic-drop-search-input").focus() @topicMenu.show()
$(".form-topic-drop-search-input").focus()
$("body").bind "keydown", @setActiveItem
$("body").bind "click", @hideTopicDropdown $("body").bind "keydown", @setActiveItem
$("body").bind "click", @hideTopicDropdown
# Set here because 1) the window might get resized and things could
# change and 2) can't set in initialize because the button is hidden # Set here because 1) the window might get resized and things could
@maxNameWidth = @dropdownButton.width() * 0.9 # change and 2) can't set in initialize because the button is hidden
@maxNameWidth = @dropdownButton.width() * 0.9
# Need a fat arrow because hideTopicDropdown is passed as a callback to bind
hideTopicDropdown: () => # Need a fat arrow because hideTopicDropdown is passed as a callback to bind
@menuOpen = false hideTopicDropdown: () =>
@dropdownButton.removeClass('dropped') @menuOpen = false
@topicMenu.hide() @dropdownButton.removeClass('dropped')
@topicMenu.hide()
$("body").unbind "keydown", @setActiveItem
$("body").unbind "click", @hideTopicDropdown $("body").unbind "keydown", @setActiveItem
$("body").unbind "click", @hideTopicDropdown
setTopic: (event) ->
$target = $(event.target) setTopic: (event) ->
if $target.data('discussion_id') $target = $(event.target)
@topicText = $target.html() if $target.data('discussion_id')
@topicText = @getFullTopicName($target) @topicText = $target.html()
@topicId = $target.data('discussion_id') @topicText = @getFullTopicName($target)
@setSelectedTopic() @topicId = $target.data('discussion_id')
@setSelectedTopic()
setSelectedTopic: ->
@dropdownButton.html(@fitName(@topicText) + ' <span class="drop-arrow">▾</span>') setSelectedTopic: ->
@dropdownButton.html(@fitName(@topicText) + ' <span class="drop-arrow">▾</span>')
getFullTopicName: (topicElement) ->
name = topicElement.html() getFullTopicName: (topicElement) ->
topicElement.parents('ul').not('.topic_menu').each -> name = topicElement.html()
name = $(this).siblings('a').html() + ' / ' + name topicElement.parents('ul').not('.topic_menu').each ->
return name name = $(this).siblings('a').html() + ' / ' + name
return name
getNameWidth: (name) ->
test = $("<div>") getNameWidth: (name) ->
test.css test = $("<div>")
"font-size": @dropdownButton.css('font-size') test.css
opacity: 0 "font-size": @dropdownButton.css('font-size')
position: 'absolute' opacity: 0
left: -1000 position: 'absolute'
top: -1000 left: -1000
$("body").append(test) top: -1000
test.html(name) $("body").append(test)
width = test.width() test.html(name)
test.remove() width = test.width()
return width test.remove()
return width
fitName: (name) ->
width = @getNameWidth(name) fitName: (name) ->
if width < @maxNameWidth width = @getNameWidth(name)
return name if width < @maxNameWidth
path = (x.replace /^\s+|\s+$/g, "" for x in name.split("/")) return name
while path.length > 1 path = (x.replace /^\s+|\s+$/g, "" for x in name.split("/"))
path.shift() while path.length > 1
partialName = "... / " + path.join(" / ") path.shift()
if @getNameWidth(partialName) < @maxNameWidth partialName = "... / " + path.join(" / ")
return partialName if @getNameWidth(partialName) < @maxNameWidth
return partialName
rawName = path[0]
rawName = path[0]
name = "... / " + rawName
name = "... / " + rawName
while @getNameWidth(name) > @maxNameWidth
rawName = rawName[0...rawName.length-1] while @getNameWidth(name) > @maxNameWidth
name = "... / " + rawName + " ..." rawName = rawName[0...rawName.length-1]
name = "... / " + rawName + " ..."
return name
return name
createPost: (event) ->
event.preventDefault() createPost: (event) ->
title = @$(".new-post-title").val() event.preventDefault()
body = @$(".new-post-body").find(".wmd-input").val() title = @$(".new-post-title").val()
tags = @$(".new-post-tags").val() body = @$(".new-post-body").find(".wmd-input").val()
tags = @$(".new-post-tags").val()
anonymous = false || @$("input.discussion-anonymous").is(":checked")
follow = false || @$("input.discussion-follow").is(":checked") anonymous = false || @$("input.discussion-anonymous").is(":checked")
follow = false || @$("input.discussion-follow").is(":checked")
$formTopicDropBtn.bind('click', showFormTopicDrop)
$formTopicDropMenu.bind('click', setFormTopic) $formTopicDropBtn.bind('click', showFormTopicDrop)
$formTopicDropMenu.bind('click', setFormTopic)
url = DiscussionUtil.urlFor('create_thread', @topicId)
url = DiscussionUtil.urlFor('create_thread', @topicId)
DiscussionUtil.safeAjax
$elem: $(event.target) DiscussionUtil.safeAjax
$loading: $(event.target) if event $elem: $(event.target)
url: url $loading: $(event.target) if event
type: "POST" url: url
dataType: 'json' type: "POST"
async: false # TODO when the rest of the stuff below is made to work properly.. dataType: 'json'
data: async: false # TODO when the rest of the stuff below is made to work properly..
title: title data:
body: body title: title
tags: tags body: body
anonymous: anonymous tags: tags
auto_subscribe: follow anonymous: anonymous
error: DiscussionUtil.formErrorHandler(@$(".new-post-form-errors")) auto_subscribe: follow
success: (response, textStatus) => error: DiscussionUtil.formErrorHandler(@$(".new-post-form-errors"))
# TODO: Move this out of the callback, this makes it feel sluggish success: (response, textStatus) =>
thread = new Thread response['content'] # TODO: Move this out of the callback, this makes it feel sluggish
DiscussionUtil.clearFormErrors(@$(".new-post-form-errors")) thread = new Thread response['content']
@$el.hide() DiscussionUtil.clearFormErrors(@$(".new-post-form-errors"))
@$(".new-post-title").val("").attr("prev-text", "") @$el.hide()
@$(".new-post-body textarea").val("").attr("prev-text", "") @$(".new-post-title").val("").attr("prev-text", "")
@$(".new-post-tags").val("") @$(".new-post-body textarea").val("").attr("prev-text", "")
@$(".new-post-tags").importTags("") @$(".new-post-tags").val("")
@collection.add thread @$(".new-post-tags").importTags("")
@collection.add thread
setActiveItem: (event) ->
if event.which == 13 setActiveItem: (event) ->
$(".topic_menu_wrapper .focused").click() if event.which == 13
return $(".topic_menu_wrapper .focused").click()
if event.which != 40 && event.which != 38 return
return if event.which != 40 && event.which != 38
event.preventDefault() return
event.preventDefault()
items = $.makeArray($(".topic_menu_wrapper a").not(".hidden"))
index = items.indexOf($('.topic_menu_wrapper .focused')[0]) items = $.makeArray($(".topic_menu_wrapper a").not(".hidden"))
index = items.indexOf($('.topic_menu_wrapper .focused')[0])
if event.which == 40
index = Math.min(index + 1, items.length - 1) if event.which == 40
if event.which == 38 index = Math.min(index + 1, items.length - 1)
index = Math.max(index - 1, 0) if event.which == 38
index = Math.max(index - 1, 0)
$(".topic_menu_wrapper .focused").removeClass("focused")
$(items[index]).addClass("focused") $(".topic_menu_wrapper .focused").removeClass("focused")
$(items[index]).addClass("focused")
itemTop = $(items[index]).parent().offset().top
scrollTop = $(".topic_menu").scrollTop() itemTop = $(items[index]).parent().offset().top
itemFromTop = $(".topic_menu").offset().top - itemTop scrollTop = $(".topic_menu").scrollTop()
scrollTarget = Math.min(scrollTop - itemFromTop, scrollTop) itemFromTop = $(".topic_menu").offset().top - itemTop
scrollTarget = Math.max(scrollTop - itemFromTop - $(".topic_menu").height() + $(items[index]).height() + 20, scrollTarget) scrollTarget = Math.min(scrollTop - itemFromTop, scrollTop)
$(".topic_menu").scrollTop(scrollTarget) scrollTarget = Math.max(scrollTop - itemFromTop - $(".topic_menu").height() + $(items[index]).height() + 20, scrollTarget)
$(".topic_menu").scrollTop(scrollTarget)
class @ResponseCommentView extends DiscussionContentView if Backbone?
tagName: "li" class @ResponseCommentView extends DiscussionContentView
template: _.template($("#response-comment-template").html()) tagName: "li"
initLocal: -> template: _.template($("#response-comment-template").html())
# TODO .response-local is the parent of the comments so @$local is null, not sure what was intended here... initLocal: ->
@$local = @$el.find(".response-local") # TODO .response-local is the parent of the comments so @$local is null, not sure what was intended here...
@$delegateElement = @$local @$local = @$el.find(".response-local")
@$delegateElement = @$local
render: -> render: ->
@$el.html(@template(@model.toJSON())) @$el.html(@template(@model.toJSON()))
@initLocal() @initLocal()
@delegateEvents() @delegateEvents()
@renderAttrs() @renderAttrs()
@$el.find(".timeago").timeago() @$el.find(".timeago").timeago()
@convertMath() @convertMath()
@ @
convertMath: -> convertMath: ->
body = @$el.find(".response-body") body = @$el.find(".response-body")
body.html DiscussionUtil.postMathJaxProcessor DiscussionUtil.markdownWithHighlight body.html() body.html DiscussionUtil.postMathJaxProcessor DiscussionUtil.markdownWithHighlight body.html()
# This removes paragraphs so that comments are more compact # This removes paragraphs so that comments are more compact
body.children("p").each (index, elem) -> body.children("p").each (index, elem) ->
$(elem).replaceWith($(elem).html()) $(elem).replaceWith($(elem).html())
MathJax.Hub.Queue ["Typeset", MathJax.Hub, body[0]] MathJax.Hub.Queue ["Typeset", MathJax.Hub, body[0]]
class @ThreadResponseView extends DiscussionContentView if Backbone?
tagName: "li" class @ThreadResponseView extends DiscussionContentView
template: _.template($("#thread-response-template").html()) tagName: "li"
template: _.template($("#thread-response-template").html())
events: events:
"click .vote-btn": "toggleVote" "click .vote-btn": "toggleVote"
"submit .comment-form": "submitComment" "submit .comment-form": "submitComment"
"click .action-endorse": "toggleEndorse" "click .action-endorse": "toggleEndorse"
"click .action-delete": "delete" "click .action-delete": "delete"
render: -> render: ->
@$el.html(@template(@model.toJSON())) @$el.html(@template(@model.toJSON()))
@initLocal() @initLocal()
@delegateEvents() @delegateEvents()
if window.user.voted(@model) if window.user.voted(@model)
@$(".vote-btn").addClass("is-cast") @$(".vote-btn").addClass("is-cast")
@renderAttrs() @renderAttrs()
@$el.find(".posted-details").timeago() @$el.find(".posted-details").timeago()
@convertMath() @convertMath()
@renderComments() @renderComments()
@ @
convertMath: -> convertMath: ->
element = @$(".response-body") element = @$(".response-body")
element.html DiscussionUtil.postMathJaxProcessor DiscussionUtil.markdownWithHighlight element.html() element.html DiscussionUtil.postMathJaxProcessor DiscussionUtil.markdownWithHighlight element.html()
MathJax.Hub.Queue ["Typeset", MathJax.Hub, element[0]] MathJax.Hub.Queue ["Typeset", MathJax.Hub, element[0]]
renderComments: -> renderComments: ->
@model.get("comments").each @renderComment @model.get("comments").each @renderComment
renderComment: (comment) => renderComment: (comment) =>
comment.set('thread', @model.get('thread')) comment.set('thread', @model.get('thread'))
view = new ResponseCommentView(model: comment) view = new ResponseCommentView(model: comment)
view.render() view.render()
@$el.find(".comments li:last").before(view.el) @$el.find(".comments li:last").before(view.el)
toggleVote: (event) -> toggleVote: (event) ->
event.preventDefault() event.preventDefault()
@$(".vote-btn").toggleClass("is-cast") @$(".vote-btn").toggleClass("is-cast")
if @$(".vote-btn").hasClass("is-cast") if @$(".vote-btn").hasClass("is-cast")
@vote() @vote()
else else
@unvote() @unvote()
vote: -> vote: ->
url = @model.urlFor("upvote") url = @model.urlFor("upvote")
@$(".votes-count-number").html(parseInt(@$(".votes-count-number").html()) + 1) @$(".votes-count-number").html(parseInt(@$(".votes-count-number").html()) + 1)
DiscussionUtil.safeAjax DiscussionUtil.safeAjax
$elem: @$(".discussion-vote") $elem: @$(".discussion-vote")
url: url url: url
type: "POST" type: "POST"
success: (response, textStatus) => success: (response, textStatus) =>
if textStatus == 'success' if textStatus == 'success'
@model.set(response) @model.set(response)
unvote: -> unvote: ->
url = @model.urlFor("unvote") url = @model.urlFor("unvote")
@$(".votes-count-number").html(parseInt(@$(".votes-count-number").html()) - 1) @$(".votes-count-number").html(parseInt(@$(".votes-count-number").html()) - 1)
DiscussionUtil.safeAjax DiscussionUtil.safeAjax
$elem: @$(".discussion-vote") $elem: @$(".discussion-vote")
url: url url: url
type: "POST" type: "POST"
success: (response, textStatus) => success: (response, textStatus) =>
if textStatus == 'success' if textStatus == 'success'
@model.set(response) @model.set(response)
submitComment: (event) -> submitComment: (event) ->
event.preventDefault() event.preventDefault()
url = @model.urlFor('reply') url = @model.urlFor('reply')
body = @$(".comment-form-input").val() body = @$(".comment-form-input").val()
if not body.trim().length if not body.trim().length
return return
comment = new Comment(body: body, created_at: (new Date()).toISOString(), username: window.user.get("username"), user_id: window.user.get("id")) comment = new Comment(body: body, created_at: (new Date()).toISOString(), username: window.user.get("username"), user_id: window.user.get("id"))
@renderComment(comment) @renderComment(comment)
@trigger "comment:add", comment @trigger "comment:add", comment
@$(".comment-form-input").val("") @$(".comment-form-input").val("")
DiscussionUtil.safeAjax DiscussionUtil.safeAjax
$elem: $(event.target) $elem: $(event.target)
url: url url: url
type: "POST" type: "POST"
dataType: 'json' dataType: 'json'
data: data:
body: body body: body
delete: (event) -> delete: (event) ->
event.preventDefault() event.preventDefault()
if not @model.can('can_delete') if not @model.can('can_delete')
return return
console.log $(event.target) console.log $(event.target)
url = @model.urlFor('delete') url = @model.urlFor('delete')
if not confirm "Are you sure to delete this response? " if not confirm "Are you sure to delete this response? "
return return
@model.remove() @model.remove()
@$el.remove() @$el.remove()
$elem = $(event.target) $elem = $(event.target)
DiscussionUtil.safeAjax DiscussionUtil.safeAjax
$elem: $elem $elem: $elem
url: url url: url
type: "POST" type: "POST"
success: (response, textStatus) => success: (response, textStatus) =>
toggleEndorse: (event) -> toggleEndorse: (event) ->
event.preventDefault() event.preventDefault()
if not @model.can('can_endorse') if not @model.can('can_endorse')
return return
$elem = $(event.target) $elem = $(event.target)
url = @model.urlFor('endorse') url = @model.urlFor('endorse')
endorsed = @model.get('endorsed') endorsed = @model.get('endorsed')
data = { endorsed: not endorsed } data = { endorsed: not endorsed }
@model.set('endorsed', not endorsed) @model.set('endorsed', not endorsed)
@trigger "comment:endorse", not endorsed @trigger "comment:endorse", not endorsed
DiscussionUtil.safeAjax DiscussionUtil.safeAjax
$elem: $elem $elem: $elem
url: url url: url
data: data data: data
type: "POST" type: "POST"
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment