diff --git a/lms/djangoapps/django_comment_client/forum/views.py b/lms/djangoapps/django_comment_client/forum/views.py index 77883ae898..fd71c0abfb 100644 --- a/lms/djangoapps/django_comment_client/forum/views.py +++ b/lms/djangoapps/django_comment_client/forum/views.py @@ -146,7 +146,14 @@ def forum_form_discussion(request, course_id): category_map = utils.get_discussion_category_map(course) threads, query_params = get_threads(request, course_id) content = render_forum_discussion(request, course_id, threads, discussion_id=_general_discussion_id(course_id), query_params=query_params) + 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), {}) + if request.is_ajax(): return utils.JsonResponse({ 'html': content, @@ -172,6 +179,7 @@ def forum_form_discussion(request, course_id): 'staff_access' : has_access(request.user, course, 'staff'), 'threads': saxutils.escape(json.dumps(threads),escapedict), 'user_info': saxutils.escape(json.dumps(user_info),escapedict), + 'annotated_content_info': saxutils.escape(json.dumps(annotated_content_info),escapedict), 'course_id': course.id, 'category_map': category_map, } @@ -232,12 +240,18 @@ def single_thread(request, course_id, discussion_id, thread_id): user_info = cc.User.from_django_user(request.user).to_dict() escapedict = {'"': '"'} + + 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 = { 'discussion_id': discussion_id, 'csrf': csrf(request)['csrf_token'], 'init': '', 'user_info': saxutils.escape(json.dumps(user_info),escapedict), - 'content': render_single_thread(request, discussion_id, course_id, thread_id), + 'annotated_content_info': saxutils.escape(json.dumps(annotated_content_info), escapedict), 'course': course, 'recent_active_threads': recent_active_threads, 'trending_tags': trending_tags, diff --git a/lms/static/coffee/src/discussion/content.coffee b/lms/static/coffee/src/discussion/content.coffee index 56e66fa607..93d1414035 100644 --- a/lms/static/coffee/src/discussion/content.coffee +++ b/lms/static/coffee/src/discussion/content.coffee @@ -1,6 +1,9 @@ if Backbone? class @Content extends Backbone.Model + @contents: {} + @contentInfos: {} + template: -> DiscussionUtil.getTemplate('_content') actions: @@ -13,15 +16,18 @@ if Backbone? urlMappers: {} urlFor: (name) -> + console.log @ @urlMappers[name].apply(@) can: (action) -> - DiscussionUtil.getContentInfo @id, action + (@get('ability') || {})[action] updateInfo: (info) -> - @set('ability', info.ability) - @set('voted', info.voted) - @set('subscribed', info.subscribed) + if info + console.log info.ability + @set('ability', info.ability) + @set('voted', info.voted) + @set('subscribed', info.subscribed) addComment: (comment, options) -> options ||= {} @@ -46,10 +52,24 @@ if Backbone? @addComment comment, { silent: true } initialize: -> - DiscussionUtil.addContent @id, @ + Content.addContent @id, @ + if Content.getInfo(@id) + @updateInfo(Content.getInfo(@id)) @set 'user_url', DiscussionUtil.urlFor('user_profile', @get('user_id')) @resetComments(@get('children')) - + + @addContent: (id, content) -> @contents[id] = content + + @getContent: (id) -> @contents[id] + + @getInfo: (id) -> + @contentInfos[id] + + @loadContentInfos: (infos) -> + for id, info of infos + if @getContent(id) + @getContent(id).updateInfo(info) + $.extend @contentInfos, infos class @ContentView extends Backbone.View @@ -423,9 +443,11 @@ if Backbone? super() follow: -> + @set('subscribed', true) @trigger "thread:follow" unfollow: -> + @set('subscribed', false) @trigger "thread:unfollow" display_body: -> diff --git a/lms/static/coffee/src/discussion/main.coffee b/lms/static/coffee/src/discussion/main.coffee index e1f651ddb2..4ec7dc9b8d 100644 --- a/lms/static/coffee/src/discussion/main.coffee +++ b/lms/static/coffee/src/discussion/main.coffee @@ -6,7 +6,9 @@ DiscussionApp = window.$$course_id = element.data("course-id") user_info = element.data("user-info") threads = element.data("threads") + content_info = element.data("content-info") window.user = new DiscussionUser(user_info) + Content.loadContentInfos(content_info) discussion = new Discussion(threads) new DiscussionRouter({discussion: discussion}) Backbone.history.start({pushState: true, root: "/courses/#{$$course_id}/discussion/forum/"}) diff --git a/lms/static/coffee/src/discussion/views/discussion_content_view.coffee b/lms/static/coffee/src/discussion/views/discussion_content_view.coffee new file mode 100644 index 0000000000..5d7aeb2a37 --- /dev/null +++ b/lms/static/coffee/src/discussion/views/discussion_content_view.coffee @@ -0,0 +1,51 @@ +class @DiscussionContentView extends Backbone.View + + partialRenderer: + endorsed: (endorsed) -> + + closed: (closed) -> # we should just re-render the whole thread, or update according to new abilities + + voted: (voted) -> + if voted + @$(".discussion-vote").addClass("is-cast") + else + @$(".discussion-vote").removeClass("is-cast") + + votes_point: (votes_point) -> + @$(".discussion-vote .votes-count-number").html(votes_point) + + comments_count: (comments_count) -> + + subscribed: (subscribed) -> + if subscribed + @$(".dogear").addClass("is-followed") + else + @$(".dogear").removeClass("is-followed") + + ability: (ability) -> + console.log "ability changed" + for action, selector of @abilityRenderer + if not ability[action] + selector.disable.apply(@) + else + selector.enable.apply(@) + + abilityRenderer: + editable: + enable: -> @$(".action-edit").closest("li").show() + disable: -> @$(".action-edit").closest("li").hide() + can_delete: + enable: -> @$(".action-delete").closest("li").show() + disable: -> @$(".action-delete").closest("li").hide() + can_endorse: + enable: -> @$(".action-endorse").css("cursor", "auto") + disable: -> @$(".action-endorse").css("cursor", "default") + + renderPartial: -> + console.log "changed" + for attr, value of @model.changedAttributes() + if @partialRenderer[attr] + @partialRenderer[attr].apply(@, [value]) + + initialize: -> + @model.bind('change', @renderPartial, @) diff --git a/lms/static/coffee/src/discussion/views/discussion_thread_view.coffee b/lms/static/coffee/src/discussion/views/discussion_thread_view.coffee index 441929a05f..061e1afbc7 100644 --- a/lms/static/coffee/src/discussion/views/discussion_thread_view.coffee +++ b/lms/static/coffee/src/discussion/views/discussion_thread_view.coffee @@ -1,8 +1,25 @@ -class @DiscussionThreadView extends Backbone.View +class @DiscussionThreadView extends DiscussionContentView + + abilityRenderer: + editable: + enable: -> @$(".action-edit").closest("li").show() + disable: -> @$(".action-edit").closest("li").hide() + can_delete: + enable: -> @$(".action-delete").closest("li").show() + disable: -> @$(".action-delete").closest("li").hide() + can_endorse: + enable: -> + @$(".action-endorse").css("cursor", "auto") + disable: -> + @$(".action-endorse").css("cursor", "default") + events: "click .discussion-vote": "toggleVote" - "click .dogear": "toggleFollowing" + "click .action-follow": "toggleFollowing" "click .discussion-submit-post": "submitComment" + "click .action-edit": "edit" + "click .action-delete": "delete" + template: _.template($("#thread-template").html()) render: -> @@ -32,6 +49,7 @@ class @DiscussionThreadView extends Backbone.View url: @model.id success: (data, textStatus, xhr) => @$(".loading").remove() + Content.loadContentInfos(data['annotated_content_info']) comments = new Comments(data['content']['children']) comments.each @renderResponse @@ -44,19 +62,17 @@ class @DiscussionThreadView extends Backbone.View addComment: => @model.trigger "comment:add" - toggleVote: -> - @$(".discussion-vote").toggleClass("is-cast") - if @$(".discussion-vote").hasClass("is-cast") + toggleVote: (event) -> + event.preventDefault() + if not @model.get('voted')#@$(".discussion-vote").hasClass("is-cast") @vote() else @unvote() - false toggleFollowing: (event) -> $elem = $(event.target) - @$(".dogear").toggleClass("is-followed") url = null - if @$(".dogear").hasClass("is-followed") + if not @model.get('subscribed') @model.follow() url = @model.urlFor("follow") else @@ -69,7 +85,8 @@ class @DiscussionThreadView extends Backbone.View vote: -> url = @model.urlFor("upvote") - @$(".discussion-vote .votes-count-number").html(parseInt(@$(".discussion-vote .votes-count-number").html()) + 1) + @model.set('votes_point', parseInt(@model.get('votes_point')) + 1) + #@$(".discussion-vote .votes-count-number").html(parseInt(@$(".discussion-vote .votes-count-number").html()) + 1) DiscussionUtil.safeAjax $elem: @$(".discussion-vote") url: url @@ -80,7 +97,8 @@ class @DiscussionThreadView extends Backbone.View unvote: -> url = @model.urlFor("unvote") - @$(".discussion-vote .votes-count-number").html(parseInt(@$(".discussion-vote .votes-count-number").html()) - 1) + @model.set('votes_point', parseInt(@model.get('votes_point')) - 1) + #@$(".discussion-vote .votes-count-number").html(parseInt(@$(".discussion-vote .votes-count-number").html()) - 1) DiscussionUtil.safeAjax $elem: @$(".discussion-vote") url: url @@ -89,7 +107,8 @@ class @DiscussionThreadView extends Backbone.View if textStatus == 'success' @model.set(response) - submitComment: -> + submitComment: (event) -> + event.preventDefault() url = @model.urlFor('reply') body = @$("#wmd-input").val() response = new Comment(body: body, created_at: (new Date()).toISOString(), username: window.user.get("username"), votes: { up_count: 0 }) @@ -103,4 +122,20 @@ class @DiscussionThreadView extends Backbone.View dataType: 'json' data: body: body - false + + edit: -> + + delete: -> + + toggleEndorse: -> + $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) diff --git a/lms/static/coffee/src/discussion/views/response_comment_view.coffee b/lms/static/coffee/src/discussion/views/response_comment_view.coffee index d43832f537..634fad6556 100644 --- a/lms/static/coffee/src/discussion/views/response_comment_view.coffee +++ b/lms/static/coffee/src/discussion/views/response_comment_view.coffee @@ -1,4 +1,4 @@ -class @ResponseCommentView extends Backbone.View +class @ResponseCommentView extends DiscussionContentView tagName: "li" template: _.template($("#response-comment-template").html()) render: -> diff --git a/lms/static/coffee/src/discussion/views/thread_list_item_view.coffee b/lms/static/coffee/src/discussion/views/thread_list_item_view.coffee index de8ae74f05..5cf2954357 100644 --- a/lms/static/coffee/src/discussion/views/thread_list_item_view.coffee +++ b/lms/static/coffee/src/discussion/views/thread_list_item_view.coffee @@ -13,9 +13,9 @@ class @ThreadListItemView extends Backbone.View if window.user.following(@model) @follow() @ - threadSelected: -> + threadSelected: (event) -> + event.preventDefault() @trigger("thread:selected", @model.id) - false follow: => @$("a").addClass("followed") diff --git a/lms/static/coffee/src/discussion/views/thread_response_view.coffee b/lms/static/coffee/src/discussion/views/thread_response_view.coffee index a66e781b27..8e0509f0f4 100644 --- a/lms/static/coffee/src/discussion/views/thread_response_view.coffee +++ b/lms/static/coffee/src/discussion/views/thread_response_view.coffee @@ -1,9 +1,11 @@ -class @ThreadResponseView extends Backbone.View +class @ThreadResponseView extends DiscussionContentView tagName: "li" template: _.template($("#thread-response-template").html()) + events: "click .vote-btn": "toggleVote" "submit form": "submitComment" + "click .action-endorse": "toggleEndorse" render: -> @$el.html(@template(@model.toJSON())) @@ -27,13 +29,13 @@ class @ThreadResponseView extends Backbone.View view.render() @$(".comments li:last").before(view.el) - toggleVote: -> + toggleVote: (event) -> + event.preventDefault() @$(".vote-btn").toggleClass("is-cast") if @$(".vote-btn").hasClass("is-cast") @vote() else @unvote() - false vote: -> url = @model.urlFor("upvote") @@ -58,10 +60,11 @@ class @ThreadResponseView extends Backbone.View @model.set(response) submitComment: (event) -> + event.preventDefault() url = @model.urlFor('reply') body = @$(".comment-form-input").val() if not body.trim().length - return false + return comment = new Comment(body: body, created_at: (new Date()).toISOString(), username: window.user.get("username")) @renderComment(comment) @trigger "comment:add" @@ -74,5 +77,18 @@ class @ThreadResponseView extends Backbone.View dataType: 'json' data: body: body - - false + + toggleEndorse: (event) -> + event.preventDefault() + if not @model.can('can_endorse') + return + $elem = $(event.target) + url = @model.urlFor('endorse') + endorsed = @model.get('endorsed') + data = { endorsed: not endorsed } + @model.set('endorsed', not endorsed) + DiscussionUtil.safeAjax + $elem: $elem + url: url + data: data + type: "POST" diff --git a/lms/static/sass/_discussion.scss b/lms/static/sass/_discussion.scss index 6e92d8deed..3734236685 100644 --- a/lms/static/sass/_discussion.scss +++ b/lms/static/sass/_discussion.scss @@ -1249,12 +1249,16 @@ body.discussion { } .moderator-actions { + margin: 0; margin-top: 20px; + padding: 0; @include clearfix; + li { float: left; margin-right: 8px; + list-style: none; } a { diff --git a/lms/templates/discussion/_underscore_templates.html b/lms/templates/discussion/_underscore_templates.html index 1a28843271..ae92fba003 100644 --- a/lms/templates/discussion/_underscore_templates.html +++ b/lms/templates/discussion/_underscore_templates.html @@ -1,6 +1,6 @@