diff --git a/lms/djangoapps/django_comment_client/base/views.py b/lms/djangoapps/django_comment_client/base/views.py index dd9da857c6..68250a035e 100644 --- a/lms/djangoapps/django_comment_client/base/views.py +++ b/lms/djangoapps/django_comment_client/base/views.py @@ -55,7 +55,7 @@ def ajax_content_response(request, course_id, content, template_name): annotated_content_info = utils.get_annotated_content_info(course_id, content, request.user, user_info) return JsonResponse({ 'html': html, - 'content': content, + 'content': utils.safe_content(content), 'annotated_content_info': annotated_content_info, }) @@ -78,7 +78,7 @@ def create_thread(request, course_id, commentable_id): if request.is_ajax(): return ajax_content_response(request, course_id, thread.to_dict(), 'discussion/ajax_create_thread.html') else: - return JsonResponse(thread.to_dict()) + return JsonResponse(utils.safe_content(thread.to_dict())) @require_POST @login_required @@ -90,7 +90,7 @@ def update_thread(request, course_id, thread_id): if request.is_ajax(): return ajax_content_response(request, course_id, thread.to_dict(), 'discussion/ajax_update_thread.html') else: - return JsonResponse(thread.to_dict()) + return JsonResponse(utils.safe_content(thread.to_dict())) def _create_comment(request, course_id, thread_id=None, parent_id=None): post = request.POST @@ -109,7 +109,7 @@ def _create_comment(request, course_id, thread_id=None, parent_id=None): if request.is_ajax(): return ajax_content_response(request, course_id, comment.to_dict(), 'discussion/ajax_create_comment.html') else: - return JsonResponse(comment.to_dict()) + return JsonResponse(utils.safe_content(comment.to_dict())) @require_POST @login_required @@ -126,7 +126,7 @@ def create_comment(request, course_id, thread_id): def delete_thread(request, course_id, thread_id): thread = cc.Thread.find(thread_id) thread.delete() - return JsonResponse(thread.to_dict()) + return JsonResponse(utils.safe_content(thread.to_dict())) @require_POST @login_required @@ -138,7 +138,7 @@ def update_comment(request, course_id, comment_id): if request.is_ajax(): return ajax_content_response(request, course_id, comment.to_dict(), 'discussion/ajax_update_comment.html') else: - return JsonResponse(comment.to_dict()), + return JsonResponse(utils.safe_content(comment.to_dict())) @require_POST @login_required @@ -147,7 +147,7 @@ def endorse_comment(request, course_id, comment_id): comment = cc.Comment.find(comment_id) comment.endorsed = request.POST.get('endorsed', 'false').lower() == 'true' comment.save() - return JsonResponse(comment.to_dict()) + return JsonResponse(utils.safe_content(comment.to_dict())) @require_POST @login_required @@ -156,7 +156,11 @@ def openclose_thread(request, course_id, thread_id): thread = cc.Thread.find(thread_id) thread.closed = request.POST.get('closed', 'false').lower() == 'true' thread.save() - return JsonResponse(thread.to_dict()) + thread = thread.to_dict() + return JsonResponse({ + 'content': utils.safe_content(thread), + 'ability': utils.get_ability(course_id, thread, request.user), + }) @require_POST @login_required @@ -173,7 +177,7 @@ def create_sub_comment(request, course_id, comment_id): def delete_comment(request, course_id, comment_id): comment = cc.Comment.find(comment_id) comment.delete() - return JsonResponse(comment.to_dict()) + return JsonResponse(utils.safe_content(comment.to_dict())) @require_POST @login_required @@ -182,7 +186,7 @@ def vote_for_comment(request, course_id, comment_id, value): user = cc.User.from_django_user(request.user) comment = cc.Comment.find(comment_id) user.vote(comment, value) - return JsonResponse(comment.to_dict()) + return JsonResponse(utils.safe_content(comment.to_dict())) @require_POST @login_required @@ -191,7 +195,7 @@ def undo_vote_for_comment(request, course_id, comment_id): user = cc.User.from_django_user(request.user) comment = cc.Comment.find(comment_id) user.unvote(comment) - return JsonResponse(comment.to_dict()) + return JsonResponse(utils.safe_content(comment.to_dict())) @require_POST @login_required @@ -200,7 +204,7 @@ def vote_for_thread(request, course_id, thread_id, value): user = cc.User.from_django_user(request.user) thread = cc.Thread.find(thread_id) user.vote(thread, value) - return JsonResponse(thread.to_dict()) + return JsonResponse(utils.safe_content(thread.to_dict())) @require_POST @login_required @@ -209,7 +213,7 @@ def undo_vote_for_thread(request, course_id, thread_id): user = cc.User.from_django_user(request.user) thread = cc.Thread.find(thread_id) user.unvote(thread) - return JsonResponse(thread.to_dict()) + return JsonResponse(utils.safe_content(thread.to_dict())) @require_POST diff --git a/lms/djangoapps/django_comment_client/forum/views.py b/lms/djangoapps/django_comment_client/forum/views.py index eda574cb6e..62055f0ab7 100644 --- a/lms/djangoapps/django_comment_client/forum/views.py +++ b/lms/djangoapps/django_comment_client/forum/views.py @@ -83,7 +83,7 @@ def render_discussion(request, course_id, threads, *args, **kwargs): 'base_url': base_url, 'query_params': strip_blank(strip_none(extract(query_params, ['page', 'sort_key', 'sort_order', 'tags', 'text']))), 'annotated_content_info': json.dumps(annotated_content_info), - 'discussion_data': json.dumps({ (discussion_id or user_id): threads }) + 'discussion_data': json.dumps({ (discussion_id or user_id): map(utils.safe_content, threads) }) } context = dict(context.items() + query_params.items()) return render_to_string(template, context) @@ -128,7 +128,7 @@ def inline_discussion(request, course_id, discussion_id): return utils.JsonResponse({ 'html': html, - 'discussionData': threads, + 'discussion_data': map(utils.safe_content, threads), }) def render_search_bar(request, course_id, discussion_id=None, text=''): @@ -149,7 +149,7 @@ def forum_form_discussion(request, course_id): if request.is_ajax(): return utils.JsonResponse({ 'html': content, - 'discussionData': threads, + 'discussion_data': map(utils.safe_content, threads), }) else: recent_active_threads = cc.search_recent_active_threads( @@ -186,7 +186,7 @@ def render_single_thread(request, discussion_id, course_id, thread_id): 'annotated_content_info': json.dumps(annotated_content_info), 'course_id': course_id, 'request': request, - 'discussion_data': json.dumps({ discussion_id: [thread] }), + 'discussion_data': json.dumps({ discussion_id: [utils.safe_content(thread)] }), } return render_to_string('discussion/_single_thread.html', context) @@ -202,7 +202,7 @@ def single_thread(request, course_id, discussion_id, thread_id): return utils.JsonResponse({ 'html': html, - 'content': thread.to_dict(), + 'content': utils.safe_content(thread.to_dict()), 'annotated_content_info': annotated_content_info, }) @@ -252,7 +252,7 @@ def user_profile(request, course_id, user_id): if request.is_ajax(): return utils.JsonResponse({ 'html': content, - 'discussionData': threads, + 'discussion_data': map(utils.safe_content, threads), }) else: context = { diff --git a/lms/djangoapps/django_comment_client/utils.py b/lms/djangoapps/django_comment_client/utils.py index fded387462..516344d79b 100644 --- a/lms/djangoapps/django_comment_client/utils.py +++ b/lms/djangoapps/django_comment_client/utils.py @@ -164,6 +164,16 @@ class QueryCountDebugMiddleware(object): logging.info('%s queries run, total %s seconds' % (len(connection.queries), total_time)) return response +def get_ability(course_id, content, user): + return { + '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_info(course_id, content, user, user_info): voted = '' if content['id'] in user_info['upvoted_ids']: @@ -173,14 +183,7 @@ def get_annotated_content_info(course_id, content, user, user_info): 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"), - }, + 'ability': get_ability(course_id, content, user), } def get_annotated_content_infos(course_id, thread, user, user_info): @@ -216,3 +219,17 @@ def extend_content(content): 'permalink': permalink(content), } return merge_dict(content, content_info) + +def safe_content(content): + fields = [ + 'id', 'title', 'body', 'course_id', 'anonymous', 'endorsed', + 'parent_id', 'thread_id', 'votes', 'closed', + 'created_at', 'updated_at', 'depth', 'type', + 'commentable_id', 'comments_count', 'at_position_list', + 'children', 'highlighted_title', 'highlighted_body', + ] + + if content.get('anonymous') is False: + fields += ['username', 'user_id'] + + return strip_none(extract(content, fields)) diff --git a/lms/envs/dev_ike.py b/lms/envs/dev_ike.py index 3ae141a037..297b179fae 100644 --- a/lms/envs/dev_ike.py +++ b/lms/envs/dev_ike.py @@ -16,6 +16,8 @@ WIKI_ENABLED = False MITX_FEATURES['ENABLE_TEXTBOOK'] = False MITX_FEATURES['ENABLE_DISCUSSION'] = False MITX_FEATURES['ACCESS_REQUIRE_STAFF_FOR_COURSE'] = True # require that user be in the staff_* group to be able to enroll +MITX_FEATURES['SUBDOMAIN_COURSE_LISTINGS'] = False +MITX_FEATURES['SUBDOMAIN_BRANDING'] = False MITX_FEATURES['DISABLE_START_DATES'] = True # MITX_FEATURES['USE_DJANGO_PIPELINE']=False # don't recompile scss diff --git a/lms/static/coffee/src/discussion/content.coffee b/lms/static/coffee/src/discussion/content.coffee index f7e7bcc5dc..13d6f1094f 100644 --- a/lms/static/coffee/src/discussion/content.coffee +++ b/lms/static/coffee/src/discussion/content.coffee @@ -90,7 +90,9 @@ if Backbone? ability: (ability) -> for action, elemSelector of @model.actions if not ability[action] - @$(elemSelector).parent().remove() + @$(elemSelector).parent().hide() + else + @$(elemSelector).parent().show() $discussionContent: -> @_discussionContent ||= @$el.children(".discussion-content") @@ -191,6 +193,8 @@ if Backbone? comment = @model.addComment response.content commentView = new CommentView el: $comment[0], model: comment comment.updateInfo response.annotated_content_info + if autowatch + @model.get('thread').set('subscribed', true) @cancelReply() cancelReply: -> @@ -269,6 +273,7 @@ if Backbone? data: data success: (response, textStatus) => @model.set('closed', not closed) + @model.set('ability', response.ability) edit: (event) -> @$(".discussion-content-wrapper").hide() @@ -279,11 +284,11 @@ if Backbone? view = {} view.id = @model.id if @model.get('type') == 'thread' - view.title = @$(".thread-raw-title").html() - view.body = @$(".thread-raw-body").html() - view.tags = @$(".thread-raw-tags").html() + view.title = @model.get('title') + view.body = @model.get('body') + view.tags = @model.get('tags') else - view.body = @$(".comment-raw-body").html() + view.body = @model.get('body') @$discussionContent().append Mustache.render DiscussionUtil.getTemplate("_edit_#{@model.get('type')}"), view DiscussionUtil.makeWmdEditor @$el, $.proxy(@$, @), "#{@model.get('type')}-body-edit" @$(".thread-tags-edit").tagsInput DiscussionUtil.tagsInputOptions() @@ -311,8 +316,12 @@ if Backbone? success: (response, textStatus) => DiscussionUtil.clearFormErrors @$(".discussion-update-errors") @$discussionContent().replaceWith(response.html) - @model.set response.content - @model.updateInfo response.annotated_content_info + if @model.get('type') == 'thread' + @model = new Thread response.content + else + @model = new Comment $.extend {}, response.content, { thread: @model.get('thread') } + @reconstruct() + @model.updateInfo response.annotated_content_info, { forceUpdate: true } cancelEdit: (event) -> @$(".discussion-content-edit").hide() @@ -330,9 +339,11 @@ if Backbone? DiscussionUtil.safeAjax $elem: $elem url: url + type: "POST" success: (response, textStatus) => @$el.remove() - @model.get('thread').removeComment(@model) + if @model.get('type') == 'comment' + @model.get('thread').removeComment(@model) events: "click .discussion-follow-thread": "toggleFollow" @@ -381,6 +392,14 @@ if Backbone? @initTitle() @initBody() @initCommentViews() + + reconstruct: -> + @initBindings() + @initLocal() + @initTimeago() + @initTitle() + @initBody() + @delegateEvents() class @Thread extends @Content urlMappers: diff --git a/lms/static/coffee/src/discussion/discussion.coffee b/lms/static/coffee/src/discussion/discussion.coffee index d4b6a8a9c4..d8df704d1c 100644 --- a/lms/static/coffee/src/discussion/discussion.coffee +++ b/lms/static/coffee/src/discussion/discussion.coffee @@ -42,13 +42,17 @@ if Backbone? DiscussionUtil.safeAjax $elem: $elem $loading: $elem + loadingCallback: -> + $(this).parent().append("") + loadedCallback: -> + $(this).parent().children(".discussion-loading").remove() url: url type: "GET" success: (response, textStatus) => $parent = @$el.parent() @$el.replaceWith(response.html) $discussion = $parent.find("section.discussion") - @model.reset(response.discussionData, { silent: false }) + @model.reset(response.discussion_data, { silent: false }) view = new DiscussionView el: $discussion[0], model: @model DiscussionUtil.bulkUpdateContentInfo(window.$$annotated_content_info) $("html, body").animate({ scrollTop: 0 }, 0) @@ -109,6 +113,7 @@ if Backbone? @$(".discussion-cancel-post").click $.proxy(@cancelNewPost, @) + @$el.children(".blank").hide() @$(".new-post-form").show() submitNewPost: (event) -> @@ -136,6 +141,8 @@ if Backbone? $thread = $(response.html) @$el.children(".threads").prepend($thread) + @$el.children(".blank").remove() + @$(".new-post-similar-posts").empty() @$(".new-post-similar-posts-wrapper").hide() @$(".new-post-title").val("").attr("prev-text", "") @@ -154,6 +161,7 @@ if Backbone? @$(".new-post-form").addClass("collapsed") else if @$el.hasClass("forum-discussion") @$(".new-post-form").hide() + @$el.children(".blank").show() search: (event) -> event.preventDefault() diff --git a/lms/static/coffee/src/discussion/utils.coffee b/lms/static/coffee/src/discussion/utils.coffee index e156b09a63..94807654c9 100644 --- a/lms/static/coffee/src/discussion/utils.coffee +++ b/lms/static/coffee/src/discussion/utils.coffee @@ -72,13 +72,17 @@ class @DiscussionUtil params["beforeSend"] = -> $elem.attr("disabled", "disabled") if params["$loading"] - console.log "loading" - params["$loading"].loading() + if params["loadingCallback"]? + params["loadingCallback"].apply(params["$loading"]) + else + params["$loading"].loading() $.ajax(params).always -> $elem.removeAttr("disabled") if params["$loading"] - console.log "loaded" - params["$loading"].loaded() + if params["loadedCallback"]? + params["loadedCallback"].apply(params["$loading"]) + else + params["$loading"].loaded() @get: ($elem, url, data, success) -> @safeAjax diff --git a/lms/static/sass/_discussion.scss b/lms/static/sass/_discussion.scss index b44f8120b9..6c1988684e 100644 --- a/lms/static/sass/_discussion.scss +++ b/lms/static/sass/_discussion.scss @@ -152,7 +152,8 @@ $tag-text-color: #5b614f; .user-profile { @extend .sidebar; - } + margin-top: 24px; + } .sidebar-username { font-size: 1.5em; diff --git a/lms/static/sass/course/wiki/_wiki.scss b/lms/static/sass/course/wiki/_wiki.scss index bcc2c8855d..d15f408e13 100644 --- a/lms/static/sass/course/wiki/_wiki.scss +++ b/lms/static/sass/course/wiki/_wiki.scss @@ -885,8 +885,7 @@ section.wiki { .alert { position: relative; - top: -15px; - margin-bottom: 24px; + margin: 24px 40px; padding: 8px 12px; border: 1px solid #EBE8BF; border-radius: 3px; @@ -903,6 +902,10 @@ section.wiki { } } + .main-article .alert { + margin: 0 0 24px; + } + .missing { max-width: 400px; margin: lh(2) auto; diff --git a/lms/templates/discussion/_content_renderer.html b/lms/templates/discussion/_content_renderer.html index 54371bf4dd..ac2b0b4897 100644 --- a/lms/templates/discussion/_content_renderer.html +++ b/lms/templates/discussion/_content_renderer.html @@ -5,7 +5,7 @@ %def> <%def name="render_content_with_comments(content, *args, **kwargs)"> -