Commit fdf92d68 by Rocky Duan

more refactoring; enabled partial re-rendering for votes, endorse, and closed

parent 72e6588b
......@@ -51,7 +51,8 @@ def ajax_content_response(request, course_id, content, template_name):
'content': content,
}
html = render_to_string(template_name, context)
annotated_content_info = utils.get_annotated_content_info(course_id, content, request.user)
user_info = cc.User.from_django_user(request.user).to_dict()
annotated_content_info = utils.get_annotated_content_info(course_id, content, request.user, user_info)
return JsonResponse({
'html': html,
'content': content,
......
......@@ -15,7 +15,6 @@ from django_comment_client.permissions import check_permissions_by_view
from django_comment_client.utils import merge_dict, extract, strip_none
import json
import dateutil
import django_comment_client.utils as utils
import comment_client as cc
......@@ -64,14 +63,17 @@ def render_discussion(request, course_id, threads, *args, **kwargs):
'user': (lambda: reverse('django_comment_client.forum.views.user_profile', args=[course_id, user_id])),
}[discussion_type]()
annotated_content_infos = map(lambda x: utils.get_annotated_content_infos(course_id, x, request.user), threads)
annotated_content_info = reduce(merge_dict, annotated_content_infos, {})
user_info = cc.User.from_django_user(request.user).to_dict()
def infogetter(thread):
return utils.get_annotated_content_infos(course_id, thread, request.user, user_info)
annotated_content_info = reduce(merge_dict, map(infogetter, threads), {})
context = {
'threads': threads,
'discussion_id': discussion_id,
'user_id': user_id,
'user_info': json.dumps(cc.User.from_django_user(request.user).to_dict()),
'course_id': course_id,
'request': request,
'performed_search': _should_perform_search(request),
......@@ -166,12 +168,13 @@ def render_single_thread(request, discussion_id, course_id, thread_id):
thread = cc.Thread.find(thread_id).retrieve(recursive=True).to_dict()
annotated_content_info = utils.get_annotated_content_infos(course_id, thread=thread, user=request.user)
user_info = cc.User.from_django_user(request.user).to_dict()
annotated_content_info = utils.get_annotated_content_infos(course_id, thread=thread, user=request.user, user_info=user_info)
context = {
'discussion_id': discussion_id,
'thread': thread,
'user_info': json.dumps(cc.User.from_django_user(request.user).to_dict()),
'annotated_content_info': json.dumps(annotated_content_info),
'course_id': course_id,
'request': request,
......@@ -183,8 +186,9 @@ def single_thread(request, course_id, discussion_id, thread_id):
if request.is_ajax():
user_info = cc.User.from_django_user(request.user).to_dict()
thread = cc.Thread.find(thread_id).retrieve(recursive=True)
annotated_content_info = utils.get_annotated_content_infos(course_id, thread, request.user)
annotated_content_info = utils.get_annotated_content_infos(course_id, thread, request.user, user_info=user_info)
context = {'thread': thread.to_dict(), 'course_id': course_id}
html = render_to_string('discussion/_ajax_single_thread.html', context)
......
......@@ -160,23 +160,32 @@ class QueryCountDebugMiddleware(object):
logging.info('%s queries run, total %s seconds' % (len(connection.queries), total_time))
return response
def get_annotated_content_info(course_id, content, user):
def get_annotated_content_info(course_id, content, user, user_info):
voted = ''
if content['id'] in user_info['upvoted_ids']:
voted = 'up'
elif content['id'] in user_info['downvoted_ids']:
voted = 'down'
return {
'voted': voted,
'subscribed': content['id'] in user_info['subscribed_thread_ids'],
'ability': {
'editable': check_permissions_by_view(user, course_id, content, "update_thread" if content['type'] == 'thread' else "update_comment"),
'can_reply': check_permissions_by_view(user, course_id, content, "create_comment" if content['type'] == 'thread' else "create_sub_comment"),
'can_endorse': check_permissions_by_view(user, course_id, content, "endorse_comment") if content['type'] == 'comment' else False,
'can_delete': check_permissions_by_view(user, course_id, content, "delete_thread" if content['type'] == 'thread' else "delete_comment"),
'can_openclose': check_permissions_by_view(user, course_id, content, "openclose_thread") if content['type'] == 'thread' else False,
'can_vote': check_permissions_by_view(user, course_id, content, "vote_for_thread" if content['type'] == 'thread' else "vote_for_comment"),
},
}
def get_annotated_content_infos(course_id, thread, user):
def get_annotated_content_infos(course_id, thread, user, user_info):
infos = {}
def _annotate(content):
infos[str(content['id'])] = get_annotated_content_info(course_id, content, user)
def annotate(content):
infos[str(content['id'])] = get_annotated_content_info(course_id, content, user, user_info)
for child in content.get('children', []):
_annotate(child)
_annotate(thread)
annotate(child)
annotate(thread)
return infos
def render_mustache(template_name, dictionary, *args, **kwargs):
......
......@@ -9,60 +9,107 @@ class @Content extends Backbone.Model
can_delete: '.admin-delete'
can_openclose: '.admin-openclose'
isUpvoted: ->
DiscussionUtil.isUpvoted @id
urlMappers: {}
isDownvoted: ->
DiscussionUtil.isDownvoted @id
urlFor: (name) ->
@urlMappers[name].apply(@)
can: (action) ->
DiscussionUtil.getContentInfo @id, action
thread_id: ->
if @get('type') == "comment" then @get('thread_id') else @id
updateInfo: (info) ->
@set('ability', info.ability)
@set('voted', info.voted)
@set('subscribed', info.subscribed)
addComment: (comment) ->
@get('children').push comment
model = new Comment $.extend {}, comment, { thread: @get('thread') }
@get('comments').add model
model
resetComments: (children) ->
@set 'children', []
@set 'comments', new Comments()
for comment in (children || [])
@addComment comment
initialize: ->
@set('comments', new Comments())
if @get('children')
@get('comments').reset @get('children'), {silent: false}
DiscussionUtil.addContent @id, @
@resetComments(@get('children'))
class @ContentView extends Backbone.View
$: (selector) ->
@$local.find(selector)
discussionContent: ->
partial:
endorsed: (endorsed) ->
if endorsed
@$el.addClass("endorsed")
else
@$el.removeClass("endorsed")
closed: (closed) -> # we should just re-render the whole thread, or update according to new abilities
if closed
@$el.addClass("closed")
@$(".admin-openclose").text "Re-open Thread"
else
@$el.removeClass("closed")
@$(".admin-openclose").text "Close Thread"
voted: (voted) ->
@$(".discussion-vote-up").removeClass("voted") if voted != "up"
@$(".discussion-vote-down").removeClass("voted") if voted != "down"
@$(".discussion-vote-#{voted}").addClass("voted") if voted in ["up", "down"]
votes_point: (votes_point) ->
@$(".discussion-votes-point").html(votes_point)
subscribed: (subscribed) -> #later
ability: (ability) ->
for action, elemSelector of @model.actions
if not ability[action]
@$(elemSelector).remove()
$discussionContent: ->
@_discussionContent ||= @$el.children(".discussion-content")
$showComments: ->
@_showComments ||= @$(".discussion-show-comments")
updateShowComments: ->
if @showed
@$showComments().html @$showComments().html().replace "Show", "Hide"
else
@$showComments().html @$showComments().html().replace "Hide", "Show"
retrieved: ->
@$showComments().hasClass("retrieved")
hideSingleThread: (event) ->
$showComments = @$(".discussion-show-comments")
@$el.children(".comments").hide()
@showed = false
$showComments.html $showComments.html().replace "Hide", "Show"
@updateShowComments()
showSingleThread: (event) ->
$showComments = @$(".discussion-show-comments")
retrieved = not $showComments.hasClass("first-time") and @model.get("children") != undefined
if retrieved
if @retrieved()
@$el.children(".comments").show()
$showComments.html $showComments.html().replace "Show", "Hide"
@showed = true
@updateShowComments()
else
discussion_id = @model.discussion.id
url = DiscussionUtil.urlFor('retrieve_single_thread', discussion_id, @model.id)
DiscussionUtil.safeAjax
$elem: $.merge @$(".thread-title"), @$(".discussion-show-comments")
url: url
type: "GET"
dataType: 'json'
success: (response, textStatus) =>
DiscussionUtil.bulkExtendContentInfo response['annotated_content_info']
$elem = $.merge @$(".thread-title"), @$showComments()
url = @model.urlFor('retrieve_single_thread')
DiscussionUtil.get $elem, url, (response, textStatus) =>
@showed = true
$showComments.html $showComments.html().replace "Show", "Hide"
@$el.append(response['html'])
@model.set('children', response.content.children)
@model.get('comments').reset response.content.children, {silent: false}
@updateShowComments()
@$showComments().addClass("retrieved")
@$el.children(".comments").replaceWith response.html
@model.resetComments response.content.children
@initCommentViews()
DiscussionUtil.bulkUpdateContentInfo response.annotated_content_info
toggleSingleThread: (event) ->
if @showed
......@@ -83,12 +130,11 @@ class @ContentView extends Backbone.View
if $replyView.length
$replyView.show()
else
thread_id = @model.thread_id()
view =
id: @model.id
showWatchCheckbox: not DiscussionUtil.isSubscribed(thread_id, "thread")
view = {}
view.id = @model.id
view.showWatchCheckbox = not @model.get('thread').get('subscribed')
html = Mustache.render DiscussionUtil.getTemplate('_reply'), view
@discussionContent().append html
@$discussionContent().append html
DiscussionUtil.makeWmdEditor @$el, $.proxy(@$, @), "reply-body"
@$(".discussion-submit-post").click $.proxy(@submitReply, @)
@$(".discussion-cancel-post").click $.proxy(@cancelReply, @)
......@@ -96,13 +142,7 @@ class @ContentView extends Backbone.View
@$(".discussion-edit").hide()
submitReply: (event) ->
if @model.get('type') == 'thread'
url = DiscussionUtil.urlFor('create_comment', @model.id)
else if @model.get('type') == 'comment'
url = DiscussionUtil.urlFor('create_sub_comment', @model.id)
else
console.error "unrecognized model type #{@model.get('type')}"
return
url = @model.urlFor('reply')
body = DiscussionUtil.getWmdContent @$el, $.proxy(@$, @), "reply-body"
......@@ -122,19 +162,13 @@ class @ContentView extends Backbone.View
success: (response, textStatus) =>
DiscussionUtil.clearFormErrors @$(".discussion-errors")
$comment = $(response.html)
@$el.children(".comments").prepend($comment)
@$el.children(".comments").prepend $comment
DiscussionUtil.setWmdContent @$el, $.proxy(@$, @), "reply-body", ""
#DiscussionUtil.setContentInfo response.content['id'], 'can_reply', true
#DiscussionUtil.setContentInfo response.content['id'], 'editable', true
comment = new Comment(response.content)
DiscussionUtil.extendContentInfo comment.id, response['annotated_content_info']
@model.get('children').push(response.content)
@model.get('comments').add(comment)
comment = @model.addComment response.content
comment.updateInfo response.annotated_content_info
commentView = new CommentView el: $comment[0], model: comment
@$(".discussion-reply-new").hide()
@$(".discussion-reply").show()
@$(".discussion-edit").show()
@discussionContent().attr("status", "normal")
@cancelReply()
cancelReply: ->
$replyView = @$(".discussion-reply-new")
if $replyView.length
......@@ -143,77 +177,42 @@ class @ContentView extends Backbone.View
@$(".discussion-edit").show()
unvote: (event) ->
url = DiscussionUtil.urlFor("undo_vote_for_#{@model.get('type')}", @model.id)
DiscussionUtil.safeAjax
$elem: @$(".discussion-vote")
url: url
type: "POST"
dataType: "json"
success: (response, textStatus) =>
if textStatus == "success"
@$(".discussion-vote").removeClass("voted")
@$(".discussion-votes-point").html response.votes.point
vote: (event) ->
url = @model.urlFor('unvote')
$elem = @$(".discussion-vote")
DiscussionUtil.post $elem, url, {}, (response, textStatus) =>
@model.set('voted', '')
@model.set('votes_point', response.votes.point)
vote: (event, value) ->
url = @model.urlFor("#{value}vote")
$elem = @$(".discussion-vote")
DiscussionUtil.post $elem, url, {}, (response, textStatus) =>
@model.set('voted', value)
@model.set('votes_point', response.votes.point)
toggleVote: (event) ->
$elem = $(event.target)
if $elem.hasClass("voted")
value = $elem.attr("value")
if @model.get("voted") == value
@unvote(event)
else
value = $elem.attr("value")
url = Discussion.urlFor("#{value}vote_#{@model.get('type')}", @model.id)
DiscussionUtil.safeAjax
$elem: @$(".discussion-vote")
url: url
type: "POST"
dataType: "json"
success: (response, textStatus) =>
if textStatus == "success"
@$(".discussion-vote").removeClass("voted")
@$(".discussion-vote-#{value}").addClass("voted")
@$(".discussion-votes-point").html response.votes.point
@vote(event, value)
endorse: (event) ->
url = DiscussionUtil.urlFor('endorse_comment', @model.id)
endorsed = not @$el.hasClass("endorsed")
Discussion.safeAjax
$elem: $(event.target)
url: url
type: "POST"
dataType: "json"
data: {endorsed: endorsed}
success: (response, textStatus) =>
if textStatus == "success"
if endorsed
@$el.addClass("endorsed")
else
@$el.removeClass("endorsed")
toggleEndorse: (event) ->
$elem = $(event.target)
url = @model.urlFor('endorse')
endorsed = @model.get('endorsed')
data = { endorsed: not endorsed }
DiscussionUtil.post $elem, url, data, (response, textStatus) =>
@model.set('endorsed', not endorsed)
close: (event) ->
url = DiscussionUtil.urlFor('openclose_thread', @model.id)
closed = undefined
text = $(event.target).text()
if text.match(/Close/)
closed = true
else if text.match(/[Oo]pen/)
closed = false
else
console.log "Unexpected text " + text + "for open/close thread."
Discussion.safeAjax
$elem: $(event.target)
url: url
type: "POST"
dataType: "json"
data: {closed: closed}
success: (response, textStatus) =>
if textStatus == "success"
if closed
@$el.addClass("closed")
$(event.target).text "Re-open Thread"
else
@$el.removeClass("closed")
$(event.target).text "Close Thread"
error: (response, textStatus, e) ->
console.log e
toggleClosed: (event) ->
$elem = $(event.target)
url = @model.urlFor('close')
closed = @model.get('closed')
data = { closed: not closed }
DiscussionUtil.post $elem, url, data, (response, textStatus) =>
@model.set('closed', not closed)
edit: ->
$local(".discussion-content-wrapper").hide()
......@@ -227,7 +226,7 @@ class @ContentView extends Backbone.View
body: $local(".thread-raw-body").html()
tags: $local(".thread-raw-tags").html()
}
@discussionContent().append Mustache.render Discussion.editThreadTemplate, view
@$discussionContent().append Mustache.render Discussion.editThreadTemplate, view
Discussion.makeWmdEditor $content, $local, "thread-body-edit"
$local(".thread-tags-edit").tagsInput Discussion.tagsInputOptions()
$local(".discussion-submit-update").unbind("click").click -> handleSubmitEditThread(this)
......@@ -247,7 +246,7 @@ class @ContentView extends Backbone.View
error: Discussion.formErrorHandler($local(".discussion-update-errors"))
success: (response, textStatus) ->
Discussion.clearFormErrors($local(".discussion-update-errors"))
@discussionContent().replaceWith(response.html)
@$discussionContent().replaceWith(response.html)
Discussion.extendContentInfo response.content['id'], response['annotated_content_info']
Discussion.initializeContent($content)
Discussion.bindContentEvents($content)
......@@ -274,15 +273,15 @@ class @ContentView extends Backbone.View
console.log e
events:
"click .thread-title": "showSingleThread"
"click .discussion-show-comments": "showSingleThread"
"click .thread-title": "toggleSingleThread"
"click .discussion-show-comments": "toggleSingleThread"
"click .discussion-reply-thread": "reply"
"click .discussion-reply-comment": "reply"
"click .discussion-cancel-reply": "cancelReply"
"click .discussion-vote-up": "vote"
"click .discussion-vote-down": "vote"
"click .admin-endorse": "endorse"
"click .admin-openclose": "close"
"click .discussion-vote-up": "toggleVote"
"click .discussion-vote-down": "toggleVote"
"click .admin-endorse": "toggleEndorse"
"click .admin-openclose": "toggleClosed"
"click .admin-edit": "edit"
"click .admin-delete": "delete"
......@@ -290,57 +289,65 @@ class @ContentView extends Backbone.View
@$local = @$el.children(".local")
@$delegateElement = @$local
initFollowThread: ->
$el.children(".discussion-content")
.find(".follow-wrapper")
.append(DiscussionUtil.subscriptionLink('thread', id))
initVote: ->
if @model.isUpvoted()
@$(".discussion-vote-up").addClass("voted")
else if @model.isDownvoted()
@$(".discussion-vote-down").addClass("voted")
initBody: ->
$contentBody = @$(".content-body")
$contentBody.html DiscussionUtil.postMathJaxProcessor DiscussionUtil.markdownWithHighlight $contentBody.html()
MathJax.Hub.Queue ["Typeset", MathJax.Hub, $contentBody.attr("id")]
initActions: ->
for action, elemSelector of @model.actions
if not @model.can action
@$(elemSelector).remove()
initTimeago: ->
@$("span.timeago").timeago()
initPermalink: ->
@$(".discussion-permanent-link").attr "href", @model.permalink()
if @model.get('type') == 'thread'
discussion_id = @model.get('commentable_id')
permalink = Discussion.urlFor("permanent_link_thread", discussion_id, @model.id)
else
thread_id = @model.get('thread_id')
discussion_id = @$el.parents(".thread").attr("_discussion_id")
permalink = Discussion.urlFor("permanent_link_comment", discussion_id, thread_id, @model.id)
renderPartial: ->
for attr, value of @model.changedAttributes()
if @partial[attr]
@partial[attr].apply(@, [value])
@$(".discussion-permanent-link").attr "href", permalink
initBindings: ->
@model.view = @
@model.bind('change', @renderPartial, @)
initialize: ->
@model.view = @
@initBindings()
@initLocal()
@initVote()
@initTimeago()
@initBody()
@initPermalink()
@initActions()
@initCommentViews()
class @Thread extends @Content
urlMappers:
'retrieve_single_thread': -> 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)
initialize: ->
@set('thread', @)
super()
permalink: ->
discussion_id = @get('commentable_id')
return Discussion.urlFor("permanent_link_thread", discussion_id, @id)
class @ThreadView extends @ContentView
class @Comment extends @Content
urlMappers:
'reply': -> DiscussionUtil.urlFor('create_sub_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)
'endorse': -> DiscussionUtil.urlFor('endorse_comment', @id)
permalink: ->
thread_id = @get('thread').id
discussion_id = @get('thread').get('commentable_id')
return Discussion.urlFor("permanent_link_comment", discussion_id, thread_id, @id)
class @CommentView extends @ContentView
......
......@@ -2,8 +2,9 @@ class @Discussion extends Backbone.Collection
model: Thread
initialize: ->
DiscussionUtil.addDiscussion @id, @
@bind "add", (item) =>
console.log item
item.discussion = @
find: (id) ->
......
$ ->
window.$$contents = {}
window.$$discussions = {}
$(".discussion-module").each (index, elem) ->
view = new DiscussionModuleView(el: elem)
......@@ -8,3 +11,5 @@ $ ->
discussion = new Discussion()
discussion.reset(discussionData, {silent: false})
view = new DiscussionView(el: elem, model: discussion)
DiscussionUtil.bulkUpdateContentInfo(window.$$annotated_content_info)
......@@ -12,26 +12,17 @@ class @DiscussionUtil
id = $(id).attr("_id")
return $$discussion_data[id]
@getContentInfo: (id, attr) ->
if not window.$$annotated_content_info?
window.$$annotated_content_info = {}
(window.$$annotated_content_info[id] || {})[attr]
@setContentInfo: (id, attr, value) ->
if not window.$$annotated_content_info?
window.$$annotated_content_info = {}
window.$$annotated_content_info[id] ||= {}
window.$$annotated_content_info[id][attr] = value
@extendContentInfo: (id, newInfo) ->
if not window.$$annotated_content_info?
window.$$annotated_content_info = {}
window.$$annotated_content_info[id] = newInfo
@bulkExtendContentInfo: (newInfos) ->
if not window.$$annotated_content_info?
window.$$annotated_content_info = {}
window.$$annotated_content_info = $.extend window.$$annotated_content_info, newInfos
@addContent: (id, content) -> window.$$contents[id] = content
@getContent: (id) -> window.$$contents[id]
@addDiscussion: (id, discussion) -> window.$$discussions[id] = discussion
@getDiscussion: (id) -> window.$$discussions[id]
@bulkUpdateContentInfo: (infos) ->
for id, info of infos
@getContent(id).updateInfo(info)
@generateDiscussionLink: (cls, txt, handler) ->
$("<a>").addClass("discussion-link")
......@@ -79,9 +70,22 @@ class @DiscussionUtil
$.ajax(params).always ->
$elem.removeAttr("disabled")
@handleAnchorAndReload: (response) ->
#window.location = window.location.pathname + "#" + response['id']
window.location.reload()
@get: ($elem, url, success) ->
@safeAjax
$elem: $elem
url: url
type: "GET"
dataType: "json"
success: success
@post: ($elem, url, data, success) ->
@safeAjax
$elem: $elem
url: url
type: "POST"
dataType: "json"
data: data
success: success
@bindLocalEvents: ($local, eventsHandler) ->
for eventSelector, handler of eventsHandler
......@@ -98,22 +102,6 @@ class @DiscussionUtil
defaultText: "Tag your post: press enter after each tag"
removeWithBackspace: true
@isSubscribed: (id, type) ->
$$user_info? and (
if type == "thread"
id in $$user_info.subscribed_thread_ids
else if type == "commentable" or type == "discussion"
id in $$user_info.subscribed_commentable_ids
else
id in $$user_info.subscribed_user_ids
)
@isUpvoted: (id) ->
$$user_info? and (id in $$user_info.upvoted_ids)
@isDownvoted: (id) ->
$$user_info? and (id in $$user_info.downvoted_ids)
@formErrorHandler: (errorsField) ->
(xhr, textStatus, error) ->
response = JSON.parse(xhr.responseText)
......
......@@ -85,6 +85,7 @@ initializeFollowThread = (thread) ->
Discussion.extendContentInfo response.content['id'], response['annotated_content_info']
Discussion.initializeContent($comment)
Discussion.bindContentEvents($comment)
@cancelReply()
$local(".discussion-reply-new").hide()
$local(".discussion-reply").show()
$local(".discussion-edit").show()
......
......@@ -5,11 +5,9 @@
</%def>
<%def name="render_content_with_comments(content)">
<div class="${content['type']}${helpers.show_if(' endorsed', content.get('endorsed'))}" _id="${content['id']}" _discussion_id="${content.get('commentable_id', '')}" _author_id="${helpers.show_if(content['user_id'], not content.get('anonymous'))}">
<div class="${content['type']}${helpers.show_if(' endorsed', content.get('endorsed'))}" _id="${content['id']}" _discussion_id="${content.get('commentable_id', '')}" _author_id="${helpers.show_if(content['user_id'], not content.get('anonymous'))}">
${render_content(content)}
% if content.get('children') is not None:
${render_comments(content['children'])}
% endif
${render_comments(content.get('children', []))}
</div>
</%def>
......
<%! from django.template.defaultfilters import escapejs %>
<script type="text/javascript">
var $$user_info = JSON.parse("${user_info | escapejs}");
var $$course_id = "${course_id | escapejs}";
if (typeof $$annotated_content_info === undefined) {
var $$annotated_content_info = {};
......
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