diff --git a/common/lib/capa/capa/inputtypes.py b/common/lib/capa/capa/inputtypes.py
index f19e7555a1..ad54736359 100644
--- a/common/lib/capa/capa/inputtypes.py
+++ b/common/lib/capa/capa/inputtypes.py
@@ -185,7 +185,7 @@ def choicegroup(element, value, status, render_template, msg=''):
if choice.text is not None:
ctext += choice.text # TODO: fix order?
choices.append((choice.get("name"), ctext))
- context = {'id': eid, 'value': value, 'state': status, 'input_type': type, 'choices': choices, 'inline': True, 'name_array_suffix': ''}
+ context = {'id': eid, 'value': value, 'state': status, 'input_type': type, 'choices': choices, 'name_array_suffix': ''}
html = render_template("choicegroup.html", context)
return etree.XML(html)
@@ -226,7 +226,7 @@ def radiogroup(element, value, status, render_template, msg=''):
choices = extract_choices(element)
- context = {'id': eid, 'value': value, 'state': status, 'input_type': 'radio', 'choices': choices, 'inline': False, 'name_array_suffix': '[]'}
+ context = {'id': eid, 'value': value, 'state': status, 'input_type': 'radio', 'choices': choices, 'name_array_suffix': '[]'}
html = render_template("choicegroup.html", context)
return etree.XML(html)
@@ -244,7 +244,7 @@ def checkboxgroup(element, value, status, render_template, msg=''):
choices = extract_choices(element)
- context = {'id': eid, 'value': value, 'state': status, 'input_type': 'checkbox', 'choices': choices, 'inline': False, 'name_array_suffix': '[]'}
+ context = {'id': eid, 'value': value, 'state': status, 'input_type': 'checkbox', 'choices': choices, 'name_array_suffix': '[]'}
html = render_template("choicegroup.html", context)
return etree.XML(html)
diff --git a/common/lib/capa/capa/templates/choicegroup.html b/common/lib/capa/capa/templates/choicegroup.html
index 3beb45e073..2bed2d875e 100644
--- a/common/lib/capa/capa/templates/choicegroup.html
+++ b/common/lib/capa/capa/templates/choicegroup.html
@@ -6,9 +6,6 @@
checked="true"
% endif
/> ${choice_description}
- % if not inline:
-
- % endif
% endfor
diff --git a/lms/djangoapps/django_comment_client/base/views.py b/lms/djangoapps/django_comment_client/base/views.py
index ee293351c0..dd9da857c6 100644
--- a/lms/djangoapps/django_comment_client/base/views.py
+++ b/lms/djangoapps/django_comment_client/base/views.py
@@ -8,6 +8,8 @@ import functools
import comment_client as cc
import django_comment_client.utils as utils
+import django_comment_client.settings as cc_settings
+
from django.core import exceptions
from django.contrib.auth.decorators import login_required
@@ -15,13 +17,11 @@ from django.views.decorators.http import require_POST, require_GET
from django.views.decorators import csrf
from django.core.files.storage import get_storage_class
from django.utils.translation import ugettext as _
-from django.conf import settings
from django.contrib.auth.models import User
from mitxmako.shortcuts import render_to_response, render_to_string
from courseware.courses import get_course_with_access
-
from django_comment_client.utils import JsonResponse, JsonError, extract
from django_comment_client.permissions import check_permissions_by_view
@@ -115,6 +115,9 @@ def _create_comment(request, course_id, thread_id=None, parent_id=None):
@login_required
@permitted
def create_comment(request, course_id, thread_id):
+ if cc_settings.MAX_COMMENT_DEPTH is not None:
+ if cc_settings.MAX_COMMENT_DEPTH < 0:
+ return JsonError("Comment level too deep")
return _create_comment(request, course_id, thread_id=thread_id)
@require_POST
@@ -159,6 +162,9 @@ def openclose_thread(request, course_id, thread_id):
@login_required
@permitted
def create_sub_comment(request, course_id, comment_id):
+ if cc_settings.MAX_COMMENT_DEPTH is not None:
+ if cc_settings.MAX_COMMENT_DEPTH <= cc.Comment.find(comment_id).depth:
+ return JsonError("Comment level too deep")
return _create_comment(request, course_id, parent_id=comment_id)
@require_POST
@@ -282,7 +288,7 @@ def update_moderator_status(request, course_id, user_id):
'course_id': course_id,
'user': request.user,
'django_user': user,
- 'discussion_user': discussion_user.to_dict(),
+ 'profiled_user': discussion_user.to_dict(),
}
return JsonResponse({
'html': render_to_string('discussion/ajax_user_profile.html', context)
@@ -298,10 +304,13 @@ def search_similar_threads(request, course_id, commentable_id):
'text': text,
'commentable_id': commentable_id,
}
- result = cc.search_similar_threads(course_id, recursive=False, query_params=query_params)
- return JsonResponse(result)
+ threads = cc.search_similar_threads(course_id, recursive=False, query_params=query_params)
else:
- return JsonResponse([])
+ theads = []
+ context = { 'threads': map(utils.extend_content, threads) }
+ return JsonResponse({
+ 'html': render_to_string('discussion/_similar_posts.html', context)
+ })
@require_GET
def tags_autocomplete(request, course_id):
@@ -334,8 +343,8 @@ def upload(request, course_id):#ajax upload file to a question or answer
# check file type
f = request.FILES['file-upload']
file_extension = os.path.splitext(f.name)[1].lower()
- if not file_extension in settings.DISCUSSION_ALLOWED_UPLOAD_FILE_TYPES:
- file_types = "', '".join(settings.DISCUSSION_ALLOWED_UPLOAD_FILE_TYPES)
+ if not file_extension in cc_settings.ALLOWED_UPLOAD_FILE_TYPES:
+ file_types = "', '".join(cc_settings.ALLOWED_UPLOAD_FILE_TYPES)
msg = _("allowed file types are '%(file_types)s'") % \
{'file_types': file_types}
raise exceptions.PermissionDenied(msg)
@@ -354,15 +363,16 @@ def upload(request, course_id):#ajax upload file to a question or answer
# check file size
# byte
size = file_storage.size(new_file_name)
- if size > settings.ASKBOT_MAX_UPLOAD_FILE_SIZE:
+ if size > cc_settings.MAX_UPLOAD_FILE_SIZE:
file_storage.delete(new_file_name)
msg = _("maximum upload file size is %(file_size)sK") % \
- {'file_size': settings.ASKBOT_MAX_UPLOAD_FILE_SIZE}
+ {'file_size': cc_settings.MAX_UPLOAD_FILE_SIZE}
raise exceptions.PermissionDenied(msg)
except exceptions.PermissionDenied, e:
error = unicode(e)
except Exception, e:
+ print e
logging.critical(unicode(e))
error = _('Error uploading file. Please contact the site administrator. Thank you.')
diff --git a/lms/djangoapps/django_comment_client/forum/views.py b/lms/djangoapps/django_comment_client/forum/views.py
index 592cb0a1d2..eda574cb6e 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: threads }),
+ 'discussion_data': json.dumps({ (discussion_id or user_id): threads })
}
context = dict(context.items() + query_params.items())
return render_to_string(template, context)
@@ -250,7 +250,10 @@ def user_profile(request, course_id, user_id):
content = render_user_discussion(request, course_id, threads, user_id=user_id, query_params=query_params)
if request.is_ajax():
- return utils.HtmlResponse(content)
+ return utils.JsonResponse({
+ 'html': content,
+ 'discussionData': threads,
+ })
else:
context = {
'course': course,
diff --git a/lms/djangoapps/django_comment_client/helpers.py b/lms/djangoapps/django_comment_client/helpers.py
index 779ae3c9ee..671659aed8 100644
--- a/lms/djangoapps/django_comment_client/helpers.py
+++ b/lms/djangoapps/django_comment_client/helpers.py
@@ -6,13 +6,14 @@ from django.core.urlresolvers import reverse
from functools import partial
from utils import *
+import django_comment_client.settings as cc_settings
import pystache_custom as pystache
import urllib
import os
def pluralize(singular_term, count):
- if int(count) >= 2:
+ if int(count) >= 2 or int(count) == 0:
return singular_term + 's'
return singular_term
@@ -33,26 +34,19 @@ def include_mustache_templates():
file_contents = map(read_file, filter(valid_file_name, os.listdir(mustache_dir)))
return '\n'.join(map(wrap_in_tag, map(strip_file_name, file_contents)))
-def permalink(content):
- if content['type'] == 'thread':
- return reverse('django_comment_client.forum.views.single_thread',
- args=[content['course_id'], content['commentable_id'], content['id']])
- else:
- return reverse('django_comment_client.forum.views.single_thread',
- args=[content['course_id'], content['commentable_id'], content['thread_id']]) + '#' + content['id']
-
def render_content(content, additional_context={}):
- content_info = {
- 'displayed_title': content.get('highlighted_title') or content.get('title', ''),
- 'displayed_body': content.get('highlighted_body') or content.get('body', ''),
- 'raw_tags': ','.join(content.get('tags', [])),
- 'permalink': permalink(content),
- }
context = {
- 'content': merge_dict(content, content_info),
+ 'content': extend_content(content),
content['type']: True,
}
+ if cc_settings.MAX_COMMENT_DEPTH is not None:
+ if content['type'] == 'thread':
+ if cc_settings.MAX_COMMENT_DEPTH < 0:
+ context['max_depth'] = True
+ elif content['type'] == 'comment':
+ if cc_settings.MAX_COMMENT_DEPTH <= content['depth']:
+ context['max_depth'] = True
context = merge_dict(context, additional_context)
partial_mustache_helpers = {k: partial(v, content) for k, v in mustache_helpers.items()}
context = merge_dict(context, partial_mustache_helpers)
diff --git a/lms/djangoapps/django_comment_client/mustache_helpers.py b/lms/djangoapps/django_comment_client/mustache_helpers.py
index 1961bf5c90..6f04ca527c 100644
--- a/lms/djangoapps/django_comment_client/mustache_helpers.py
+++ b/lms/djangoapps/django_comment_client/mustache_helpers.py
@@ -7,7 +7,8 @@ import inspect
def pluralize(content, text):
num, word = text.split(' ')
- if int(num or '0') >= 2:
+ num = int(num or '0')
+ if num >= 2 or num == 0:
return word + 's'
else:
return word
diff --git a/lms/djangoapps/django_comment_client/settings.py b/lms/djangoapps/django_comment_client/settings.py
new file mode 100644
index 0000000000..3234c32478
--- /dev/null
+++ b/lms/djangoapps/django_comment_client/settings.py
@@ -0,0 +1,10 @@
+from django.conf import settings
+
+MAX_COMMENT_DEPTH = None
+MAX_UPLOAD_FILE_SIZE = 1024 * 1024 #result in bytes
+ALLOWED_UPLOAD_FILE_TYPES = ('.jpg', '.jpeg', '.gif', '.bmp', '.png', '.tiff')
+
+if hasattr(settings, 'DISCUSSION_SETTINGS'):
+ MAX_COMMENT_DEPTH = settings.DISCUSSION_SETTINGS.get('MAX_COMMENT_DEPTH')
+ MAX_UPLOAD_FILE_SIZE = settings.DISCUSSION_SETTINGS.get('MAX_UPLOAD_FILE_SIZE') or MAX_UPLOAD_FILE_SIZE
+ ALLOWED_UPLOAD_FILE_TYPES = settings.DISCUSSION_SETTINGS.get('ALLOWED_UPLOAD_FILE_TYPES') or ALLOWED_UPLOAD_FILE_TYPES
diff --git a/lms/djangoapps/django_comment_client/utils.py b/lms/djangoapps/django_comment_client/utils.py
index 9e2f9c8a03..fded387462 100644
--- a/lms/djangoapps/django_comment_client/utils.py
+++ b/lms/djangoapps/django_comment_client/utils.py
@@ -21,6 +21,8 @@ import pystache_custom as pystache
_FULLMODULES = None
_DISCUSSIONINFO = None
+
+
def extract(dic, keys):
return {k: dic.get(k) for k in keys}
@@ -197,3 +199,20 @@ def url_for_tags(course_id, tags):
def render_mustache(template_name, dictionary, *args, **kwargs):
template = middleware.lookup['main'].get_template(template_name).source
return pystache.render(template, dictionary)
+
+def permalink(content):
+ if content['type'] == 'thread':
+ return reverse('django_comment_client.forum.views.single_thread',
+ args=[content['course_id'], content['commentable_id'], content['id']])
+ else:
+ return reverse('django_comment_client.forum.views.single_thread',
+ args=[content['course_id'], content['commentable_id'], content['thread_id']]) + '#' + content['id']
+
+def extend_content(content):
+ content_info = {
+ 'displayed_title': content.get('highlighted_title') or content.get('title', ''),
+ 'displayed_body': content.get('highlighted_body') or content.get('body', ''),
+ 'raw_tags': ','.join(content.get('tags', [])),
+ 'permalink': permalink(content),
+ }
+ return merge_dict(content, content_info)
diff --git a/lms/envs/aws.py b/lms/envs/aws.py
index bf7d85b91e..75ae712b88 100644
--- a/lms/envs/aws.py
+++ b/lms/envs/aws.py
@@ -68,4 +68,4 @@ if 'COURSE_ID' in ENV_TOKENS:
ASKBOT_URL = "courses/{0}/discussions/".format(ENV_TOKENS['COURSE_ID'])
COMMENTS_SERVICE_URL = ENV_TOKENS["COMMENTS_SERVICE_URL"]
-
+COMMENTS_SERVICE_KEY = ENV_TOKENS["COMMENTS_SERVICE_KEY"]
diff --git a/lms/envs/common.py b/lms/envs/common.py
index dc1d631046..99c68afbe3 100644
--- a/lms/envs/common.py
+++ b/lms/envs/common.py
@@ -38,6 +38,10 @@ ASKBOT_ENABLED = False
GENERATE_RANDOM_USER_CREDENTIALS = False
PERFSTATS = False
+DISCUSSION_SETTINGS = {
+ 'MAX_COMMENT_DEPTH': 2,
+}
+
# Features
MITX_FEATURES = {
'SAMPLE' : False,
diff --git a/lms/envs/dev.py b/lms/envs/dev.py
index d798815543..a76e6de262 100644
--- a/lms/envs/dev.py
+++ b/lms/envs/dev.py
@@ -92,6 +92,8 @@ SUBDOMAIN_BRANDING = {
'harvard': 'HarvardX',
}
+COMMENTS_SERVICE_KEY = "PUT_YOUR_API_KEY_HERE"
+
################################ LMS Migration #################################
MITX_FEATURES['ENABLE_LMS_MIGRATION'] = True
MITX_FEATURES['ACCESS_REQUIRE_STAFF_FOR_COURSE'] = False # require that user be in the staff_* group to be able to enroll
diff --git a/lms/lib/comment_client/settings.py b/lms/lib/comment_client/settings.py
index 75a107d0c9..4ae697adb5 100644
--- a/lms/lib/comment_client/settings.py
+++ b/lms/lib/comment_client/settings.py
@@ -7,4 +7,7 @@ else:
PREFIX = SERVICE_HOST + '/api/v1'
-API_KEY = "PUT_YOUR_API_KEY_HERE"
+if hasattr(settings, "COMMENTS_SERVICE_KEY"):
+ API_KEY = settings.COMMENTS_SERVICE_KEY
+else:
+ API_KEY = "PUT_YOUR_API_KEY_HERE"
diff --git a/lms/static/coffee/src/discussion/content.coffee b/lms/static/coffee/src/discussion/content.coffee
index d427a348b3..f7e7bcc5dc 100644
--- a/lms/static/coffee/src/discussion/content.coffee
+++ b/lms/static/coffee/src/discussion/content.coffee
@@ -1,410 +1,431 @@
-class @Content extends Backbone.Model
+if Backbone?
+ class @Content extends Backbone.Model
- template: -> DiscussionUtil.getTemplate('_content')
+ template: -> DiscussionUtil.getTemplate('_content')
- actions:
- editable: '.admin-edit'
- can_reply: '.discussion-reply'
- can_endorse: '.admin-endorse'
- can_delete: '.admin-delete'
- can_openclose: '.admin-openclose'
-
- urlMappers: {}
+ actions:
+ editable: '.admin-edit'
+ can_reply: '.discussion-reply'
+ can_endorse: '.admin-endorse'
+ can_delete: '.admin-delete'
+ can_openclose: '.admin-openclose'
+
+ urlMappers: {}
- urlFor: (name) ->
- @urlMappers[name].apply(@)
+ urlFor: (name) ->
+ @urlMappers[name].apply(@)
- can: (action) ->
- DiscussionUtil.getContentInfo @id, action
+ can: (action) ->
+ DiscussionUtil.getContentInfo @id, action
- updateInfo: (info) ->
- @set('ability', info.ability)
- @set('voted', info.voted)
- @set('subscribed', info.subscribed)
+ updateInfo: (info) ->
+ @set('ability', info.ability)
+ @set('voted', info.voted)
+ @set('subscribed', info.subscribed)
- addComment: (comment, options) ->
- options ||= {}
- if not options.silent
+ addComment: (comment, options) ->
+ options ||= {}
+ if not options.silent
+ thread = @get('thread')
+ comments_count = parseInt(thread.get('comments_count'))
+ thread.set('comments_count', comments_count + 1)
+ @get('children').push comment
+ model = new Comment $.extend {}, comment, { thread: @get('thread') }
+ @get('comments').add model
+ model
+
+ removeComment: (comment) ->
thread = @get('thread')
comments_count = parseInt(thread.get('comments_count'))
- thread.set('comments_count', comments_count + 1)
- @get('children').push comment
- model = new Comment $.extend {}, comment, { thread: @get('thread') }
- @get('comments').add model
- model
+ thread.set('comments_count', comments_count - 1 - comment.getCommentsCount())
- removeComment: (comment) ->
- thread = @get('thread')
- comments_count = parseInt(thread.get('comments_count'))
- thread.set('comments_count', comments_count - 1 - comment.getCommentsCount())
+ resetComments: (children) ->
+ @set 'children', []
+ @set 'comments', new Comments()
+ for comment in (children || [])
+ @addComment comment, { silent: true }
- resetComments: (children) ->
- @set 'children', []
- @set 'comments', new Comments()
- for comment in (children || [])
- @addComment comment, { silent: true }
-
- initialize: ->
- DiscussionUtil.addContent @id, @
- @resetComments(@get('children'))
-
-
-class @ContentView extends Backbone.View
-
- $: (selector) ->
- @$local.find(selector)
-
- 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)
-
- comments_count: (comments_count) ->
- @$(".comments-count").html(comments_count)
+ initialize: ->
+ DiscussionUtil.addContent @id, @
+ @resetComments(@get('children'))
- subscribed: (subscribed) ->
- if subscribed
- @$(".discussion-follow-thread").addClass("discussion-unfollow-thread").html("Unfollow")
+
+ class @ContentView extends Backbone.View
+
+ $: (selector) ->
+ @$local.find(selector)
+
+ 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)
+
+ comments_count: (comments_count) ->
+ @$(".comments-count").html(comments_count)
+
+ subscribed: (subscribed) ->
+ if subscribed
+ @$(".discussion-follow-thread").addClass("discussion-unfollow-thread").html("Unfollow")
+ else
+ @$(".discussion-follow-thread").removeClass("discussion-unfollow-thread").html("Follow")
+
+ ability: (ability) ->
+ for action, elemSelector of @model.actions
+ if not ability[action]
+ @$(elemSelector).parent().remove()
+
+ $discussionContent: ->
+ @_discussionContent ||= @$el.children(".discussion-content")
+
+ $showComments: ->
+ @_showComments ||= @$(".discussion-show-comments")
+
+ updateShowComments: ->
+ if @showed
+ @$showComments().html @$showComments().html().replace "Show", "Hide"
else
- @$(".discussion-follow-thread").removeClass("discussion-unfollow-thread").html("Follow")
+ @$showComments().html @$showComments().html().replace "Hide", "Show"
- ability: (ability) ->
- for action, elemSelector of @model.actions
- if not ability[action]
- @$(elemSelector).parent().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) ->
- @$el.children(".comments").hide()
- @showed = false
- @updateShowComments()
-
- showSingleThread: (event) ->
- if @retrieved()
- @$el.children(".comments").show()
- @showed = true
+ retrieved: ->
+ @$showComments().hasClass("retrieved")
+
+ hideSingleThread: (event) ->
+ @$el.children(".comments").hide()
+ @showed = false
@updateShowComments()
- else
- $elem = $.merge @$(".thread-title"), @$showComments()
- url = @model.urlFor('retrieve')
- DiscussionUtil.get $elem, url, {}, (response, textStatus) =>
+
+ showSingleThread: (event) ->
+ if @retrieved()
+ @$el.children(".comments").show()
@showed = true
@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
- @hideSingleThread(event)
- else
- @showSingleThread(event)
-
- initCommentViews: ->
- @$el.children(".comments").children(".comment").each (index, elem) =>
- model = @model.get('comments').find $(elem).attr("_id")
- if not model.view
- commentView = new CommentView el: elem, model: model
-
- reply: ->
- if @model.get('type') == 'thread'
- @showSingleThread()
- $replyView = @$(".discussion-reply-new")
- if $replyView.length
- $replyView.show()
- else
- view = {}
- view.id = @model.id
- view.showWatchCheckbox = not @model.get('thread').get('subscribed')
- html = Mustache.render DiscussionUtil.getTemplate('_reply'), view
- @$discussionContent().append html
- DiscussionUtil.makeWmdEditor @$el, $.proxy(@$, @), "reply-body"
- @$(".discussion-submit-post").click $.proxy(@submitReply, @)
- @$(".discussion-cancel-post").click $.proxy(@cancelReply, @)
- @$(".discussion-reply").hide()
- @$(".discussion-edit").hide()
-
- submitReply: (event) ->
- url = @model.urlFor('reply')
-
- body = DiscussionUtil.getWmdContent @$el, $.proxy(@$, @), "reply-body"
-
- anonymous = false || @$(".discussion-post-anonymously").is(":checked")
- autowatch = false || @$(".discussion-auto-watch").is(":checked")
-
- DiscussionUtil.safeAjax
- $elem: $(event.target)
- url: url
- type: "POST"
- dataType: 'json'
- data:
- body: body
- anonymous: anonymous
- auto_subscribe: autowatch
- error: DiscussionUtil.formErrorHandler @$(".discussion-errors")
- success: (response, textStatus) =>
- DiscussionUtil.clearFormErrors @$(".discussion-errors")
- $comment = $(response.html)
- @$el.children(".comments").prepend $comment
- DiscussionUtil.setWmdContent @$el, $.proxy(@$, @), "reply-body", ""
- comment = @model.addComment response.content
- commentView = new CommentView el: $comment[0], model: comment
- comment.updateInfo response.annotated_content_info
- @cancelReply()
-
- cancelReply: ->
- $replyView = @$(".discussion-reply-new")
- if $replyView.length
- $replyView.hide()
- @$(".discussion-reply").show()
- @$(".discussion-edit").show()
-
- unvote: (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)
- value = $elem.attr("value")
- if @model.get("voted") == value
- @unvote(event)
- else
- @vote(event, value)
-
- 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)
-
- toggleFollow: (event) ->
- $elem = $(event.target)
- subscribed = @model.get('subscribed')
- if subscribed
- url = @model.urlFor('unfollow')
- else
- url = @model.urlFor('follow')
- DiscussionUtil.post $elem, url, {}, (response, textStatus) =>
- @model.set('subscribed', not subscribed)
-
- 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: (event) ->
- @$(".discussion-content-wrapper").hide()
- $editView = @$(".discussion-content-edit")
- if $editView.length
- $editView.show()
- else
- 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()
else
- view.body = @$(".comment-raw-body").html()
- @$discussionContent().append Mustache.render DiscussionUtil.getTemplate("_edit_#{@model.get('type')}"), view
- Discussion.makeWmdEditor @$el, $.proxy(@$, @), "#{@model.get('type')}-body-edit"
- @$(".thread-tags-edit").tagsInput DiscussionUtil.tagsInputOptions()
- @$(".discussion-submit-update").unbind("click").click $.proxy(@submitEdit, @)
- @$(".discussion-cancel-update").unbind("click").click $.proxy(@cancelEdit, @)
+ $elem = $.merge @$(".thread-title"), @$showComments()
+ url = @model.urlFor('retrieve')
+ DiscussionUtil.safeAjax
+ $elem: $elem
+ $loading: $(event.target) if event
+ type: "GET"
+ url: url
+ success: (response, textStatus) =>
+ @showed = true
+ @updateShowComments()
+ @$showComments().addClass("retrieved")
+ @$el.children(".comments").replaceWith response.html
+ @model.resetComments response.content.children
+ @initCommentViews()
+ DiscussionUtil.bulkUpdateContentInfo response.annotated_content_info
- submitEdit: (event) ->
+ toggleSingleThread: (event) ->
+ if @showed
+ @hideSingleThread(event)
+ else
+ @showSingleThread(event)
+
+ initCommentViews: ->
+ @$el.children(".comments").children(".comment").each (index, elem) =>
+ model = @model.get('comments').find $(elem).attr("_id")
+ if not model.view
+ commentView = new CommentView el: elem, model: model
- url = @model.urlFor('update')
- data = {}
- if @model.get('type') == 'thread'
- data.title = @$(".thread-title-edit").val()
- data.body = DiscussionUtil.getWmdContent @$el, $.proxy(@$, @), "thread-body-edit"
- data.tags = @$(".thread-tags-edit").val()
- else
- data.body = DiscussionUtil.getWmdContent @$el, $.proxy(@$, @), "comment-body-edit"
- DiscussionUtil.safeAjax
- $elem: $(event.target)
- url: url
- type: "POST"
- dataType: 'json'
- data: data
- error: DiscussionUtil.formErrorHandler @$(".discussion-update-errors")
- success: (response, textStatus) =>
- DiscussionUtil.clearFormErrors @$(".discussion-update-errors")
- @$discussionContent().replaceWith(response.html)
- @model.set response.content
- @model.updateInfo response.annotated_content_info
+ reply: ->
+ if @model.get('type') == 'thread'
+ @showSingleThread()
+ $replyView = @$(".discussion-reply-new")
+ if $replyView.length
+ $replyView.show()
+ else
+ view = {}
+ view.id = @model.id
+ view.showWatchCheckbox = not @model.get('thread').get('subscribed')
+ html = Mustache.render DiscussionUtil.getTemplate('_reply'), view
+ @$discussionContent().append html
+ DiscussionUtil.makeWmdEditor @$el, $.proxy(@$, @), "reply-body"
+ @$(".discussion-submit-post").click $.proxy(@submitReply, @)
+ @$(".discussion-cancel-post").click $.proxy(@cancelReply, @)
+ @$(".discussion-reply").hide()
+ @$(".discussion-edit").hide()
- cancelEdit: (event) ->
- @$(".discussion-content-edit").hide()
- @$(".discussion-content-wrapper").show()
+ submitReply: (event) ->
+ url = @model.urlFor('reply')
- delete: (event) ->
- url = @model.urlFor('delete')
- if @model.get('type') == 'thread'
- c = confirm "Are you sure to delete thread \"#{@model.get('title')}\"?"
- else
- c = confirm "Are you sure to delete this comment? "
- if not c
- return
- $elem = $(event.target)
- DiscussionUtil.post $elem, url, {}, (response, textStatus) =>
- @$el.remove()
- @model.get('thread').removeComment(@model)
+ body = DiscussionUtil.getWmdContent @$el, $.proxy(@$, @), "reply-body"
+
+ anonymous = false || @$(".discussion-post-anonymously").is(":checked")
+ autowatch = false || @$(".discussion-auto-watch").is(":checked")
+
+ DiscussionUtil.safeAjax
+ $elem: $(event.target)
+ $loading: $(event.target) if event
+ url: url
+ type: "POST"
+ dataType: 'json'
+ data:
+ body: body
+ anonymous: anonymous
+ auto_subscribe: autowatch
+ error: DiscussionUtil.formErrorHandler @$(".discussion-errors")
+ success: (response, textStatus) =>
+ DiscussionUtil.clearFormErrors @$(".discussion-errors")
+ $comment = $(response.html)
+ @$el.children(".comments").prepend $comment
+ DiscussionUtil.setWmdContent @$el, $.proxy(@$, @), "reply-body", ""
+ comment = @model.addComment response.content
+ commentView = new CommentView el: $comment[0], model: comment
+ comment.updateInfo response.annotated_content_info
+ @cancelReply()
+
+ cancelReply: ->
+ $replyView = @$(".discussion-reply-new")
+ if $replyView.length
+ $replyView.hide()
+ @$(".discussion-reply").show()
+ @$(".discussion-edit").show()
+
+ unvote: (event) ->
+ url = @model.urlFor('unvote')
+ $elem = @$(".discussion-vote")
+ DiscussionUtil.safeAjax
+ $elem: $elem
+ url: url
+ type: "POST"
+ success: (response, textStatus) =>
+ @model.set('voted', '')
+ @model.set('votes_point', response.votes.point)
+
+ vote: (event, value) ->
+ url = @model.urlFor("#{value}vote")
+ $elem = @$(".discussion-vote")
+ DiscussionUtil.safeAjax
+ $elem: $elem
+ url: url
+ type: "POST"
+ success: (response, textStatus) =>
+ @model.set('voted', value)
+ @model.set('votes_point', response.votes.point)
+
+ toggleVote: (event) ->
+ $elem = $(event.target)
+ value = $elem.attr("value")
+ if @model.get("voted") == value
+ @unvote(event)
+ else
+ @vote(event, value)
+
+ 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)
+
+ toggleFollow: (event) ->
+ $elem = $(event.target)
+ subscribed = @model.get('subscribed')
+ if subscribed
+ url = @model.urlFor('unfollow')
+ else
+ url = @model.urlFor('follow')
+ DiscussionUtil.safeAjax
+ $elem: $elem
+ url: url
+ type: "POST"
+ success: (response, textStatus) =>
+ @model.set('subscribed', not subscribed)
+
+ toggleClosed: (event) ->
+ $elem = $(event.target)
+ url = @model.urlFor('close')
+ closed = @model.get('closed')
+ data = { closed: not closed }
+ DiscussionUtil.safeAjax
+ $elem: $elem
+ url: url
+ type: "POST"
+ data: data
+ success: (response, textStatus) =>
+ @model.set('closed', not closed)
+
+ edit: (event) ->
+ @$(".discussion-content-wrapper").hide()
+ $editView = @$(".discussion-content-edit")
+ if $editView.length
+ $editView.show()
+ else
+ 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()
+ else
+ view.body = @$(".comment-raw-body").html()
+ @$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()
+ @$(".discussion-submit-update").unbind("click").click $.proxy(@submitEdit, @)
+ @$(".discussion-cancel-update").unbind("click").click $.proxy(@cancelEdit, @)
+
+ submitEdit: (event) ->
+
+ url = @model.urlFor('update')
+ data = {}
+ if @model.get('type') == 'thread'
+ data.title = @$(".thread-title-edit").val()
+ data.body = DiscussionUtil.getWmdContent @$el, $.proxy(@$, @), "thread-body-edit"
+ data.tags = @$(".thread-tags-edit").val()
+ else
+ data.body = DiscussionUtil.getWmdContent @$el, $.proxy(@$, @), "comment-body-edit"
+ DiscussionUtil.safeAjax
+ $elem: $(event.target)
+ $loading: $(event.target) if event
+ url: url
+ type: "POST"
+ dataType: 'json'
+ data: data
+ error: DiscussionUtil.formErrorHandler @$(".discussion-update-errors")
+ success: (response, textStatus) =>
+ DiscussionUtil.clearFormErrors @$(".discussion-update-errors")
+ @$discussionContent().replaceWith(response.html)
+ @model.set response.content
+ @model.updateInfo response.annotated_content_info
+
+ cancelEdit: (event) ->
+ @$(".discussion-content-edit").hide()
+ @$(".discussion-content-wrapper").show()
+
+ delete: (event) ->
+ url = @model.urlFor('delete')
+ if @model.get('type') == 'thread'
+ c = confirm "Are you sure to delete thread \"#{@model.get('title')}\"?"
+ else
+ c = confirm "Are you sure to delete this comment? "
+ if not c
+ return
+ $elem = $(event.target)
+ DiscussionUtil.safeAjax
+ $elem: $elem
+ url: url
+ success: (response, textStatus) =>
+ @$el.remove()
+ @model.get('thread').removeComment(@model)
+
+ events:
+ "click .discussion-follow-thread": "toggleFollow"
+ "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": "toggleVote"
+ "click .discussion-vote-down": "toggleVote"
+ "click .admin-endorse": "toggleEndorse"
+ "click .admin-openclose": "toggleClosed"
+ "click .admin-edit": "edit"
+ "click .admin-delete": "delete"
+
+ initLocal: ->
+ @$local = @$el.children(".local")
+ @$delegateElement = @$local
+
+ initTitle: ->
+ $contentTitle = @$(".thread-title")
+ if $contentTitle.length
+ $contentTitle.html DiscussionUtil.unescapeHighlightTag DiscussionUtil.stripLatexHighlight $contentTitle.html()
+
+ initBody: ->
+ $contentBody = @$(".content-body")
+ $contentBody.html DiscussionUtil.postMathJaxProcessor DiscussionUtil.markdownWithHighlight $contentBody.html()
+ MathJax.Hub.Queue ["Typeset", MathJax.Hub, $contentBody.attr("id")]
+
+ initTimeago: ->
+ @$("span.timeago").timeago()
+
+ renderPartial: ->
+ for attr, value of @model.changedAttributes()
+ if @partial[attr]
+ @partial[attr].apply(@, [value])
+
+ initBindings: ->
+ @model.view = @
+ @model.bind('change', @renderPartial, @)
+
+ initialize: ->
+ @initBindings()
+ @initLocal()
+ @initTimeago()
+ @initTitle()
+ @initBody()
+ @initCommentViews()
- events:
- "click .discussion-follow-thread": "toggleFollow"
- "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": "toggleVote"
- "click .discussion-vote-down": "toggleVote"
- "click .admin-endorse": "toggleEndorse"
- "click .admin-openclose": "toggleClosed"
- "click .admin-edit": "edit"
- "click .admin-delete": "delete"
+ class @Thread extends @Content
+ urlMappers:
+ 'retrieve' : -> DiscussionUtil.urlFor('retrieve_single_thread', @discussion.id, @id)
+ 'reply' : -> DiscussionUtil.urlFor('create_comment', @id)
+ 'unvote' : -> DiscussionUtil.urlFor("undo_vote_for_#{@get('type')}", @id)
+ 'upvote' : -> DiscussionUtil.urlFor("upvote_#{@get('type')}", @id)
+ 'downvote' : -> DiscussionUtil.urlFor("downvote_#{@get('type')}", @id)
+ 'close' : -> DiscussionUtil.urlFor('openclose_thread', @id)
+ 'update' : -> DiscussionUtil.urlFor('update_thread', @id)
+ 'delete' : -> DiscussionUtil.urlFor('delete_thread', @id)
+ 'follow' : -> DiscussionUtil.urlFor('follow_thread', @id)
+ 'unfollow' : -> DiscussionUtil.urlFor('unfollow_thread', @id)
- initLocal: ->
- @$local = @$el.children(".local")
- @$delegateElement = @$local
+ initialize: ->
+ @set('thread', @)
+ super()
- initTitle: ->
- $contentTitle = @$(".thread-title")
- if $contentTitle.length
- $contentTitle.html DiscussionUtil.unescapeHighlightTag DiscussionUtil.stripLatexHighlight $contentTitle.html()
+ class @ThreadView extends @ContentView
- initBody: ->
- $contentBody = @$(".content-body")
- $contentBody.html DiscussionUtil.postMathJaxProcessor DiscussionUtil.markdownWithHighlight $contentBody.html()
- MathJax.Hub.Queue ["Typeset", MathJax.Hub, $contentBody.attr("id")]
+ 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)
+ 'update': -> DiscussionUtil.urlFor('update_comment', @id)
+ 'delete': -> DiscussionUtil.urlFor('delete_comment', @id)
- initTimeago: ->
- @$("span.timeago").timeago()
+ getCommentsCount: ->
+ count = 0
+ @get('comments').each (comment) ->
+ count += comment.getCommentsCount() + 1
+ count
- initPermalink: ->
- @$(".discussion-permanent-link").attr "href", @model.permalink()
+ class @CommentView extends @ContentView
- renderPartial: ->
- for attr, value of @model.changedAttributes()
- if @partial[attr]
- @partial[attr].apply(@, [value])
+ class @Comments extends Backbone.Collection
- initBindings: ->
- @model.view = @
- @model.bind('change', @renderPartial, @)
+ model: Comment
- initialize: ->
- @initBindings()
- @initLocal()
- @initTimeago()
- @initTitle()
- @initBody()
- @initCommentViews()
-
-class @Thread extends @Content
- urlMappers:
- 'retrieve' : -> DiscussionUtil.urlFor('retrieve_single_thread', @discussion.id, @id)
- 'reply' : -> DiscussionUtil.urlFor('create_comment', @id)
- 'unvote' : -> DiscussionUtil.urlFor("undo_vote_for_#{@get('type')}", @id)
- 'upvote' : -> DiscussionUtil.urlFor("upvote_#{@get('type')}", @id)
- 'downvote' : -> DiscussionUtil.urlFor("downvote_#{@get('type')}", @id)
- 'close' : -> DiscussionUtil.urlFor('openclose_thread', @id)
- 'update' : -> DiscussionUtil.urlFor('update_thread', @id)
- 'delete' : -> DiscussionUtil.urlFor('delete_thread', @id)
- 'follow' : -> DiscussionUtil.urlFor('follow_thread', @id)
- 'unfollow' : -> DiscussionUtil.urlFor('unfollow_thread', @id)
+ initialize: ->
+ @bind "add", (item) =>
+ item.collection = @
- 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)
- 'update': -> DiscussionUtil.urlFor('update_comment', @id)
- 'delete': -> DiscussionUtil.urlFor('delete_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)
-
- getCommentsCount: ->
- count = 0
- @get('comments').each (comment) ->
- count += comment.getCommentsCount() + 1
- count
-
-class @CommentView extends @ContentView
-
-class @Comments extends Backbone.Collection
-
- model: Comment
-
- initialize: ->
- @bind "add", (item) =>
- item.collection = @
-
- find: (id) ->
- _.first @where(id: id)
+ find: (id) ->
+ _.first @where(id: id)
diff --git a/lms/static/coffee/src/discussion/discussion.coffee b/lms/static/coffee/src/discussion/discussion.coffee
index 04eed9bf9c..d4b6a8a9c4 100644
--- a/lms/static/coffee/src/discussion/discussion.coffee
+++ b/lms/static/coffee/src/discussion/discussion.coffee
@@ -1,167 +1,178 @@
-class @Discussion extends Backbone.Collection
- model: Thread
+if Backbone?
+ class @Discussion extends Backbone.Collection
+ model: Thread
- initialize: ->
- DiscussionUtil.addDiscussion @id, @
- @bind "add", (item) =>
- item.discussion = @
+ initialize: ->
+ DiscussionUtil.addDiscussion @id, @
+ @bind "add", (item) =>
+ item.discussion = @
- find: (id) ->
- _.first @where(id: id)
+ find: (id) ->
+ _.first @where(id: id)
- addThread: (thread, options) ->
- options ||= {}
- model = new Thread thread
- @add model
- model
+ addThread: (thread, options) ->
+ options ||= {}
+ model = new Thread thread
+ @add model
+ model
-class @DiscussionView extends Backbone.View
+ class @DiscussionView extends Backbone.View
- $: (selector) ->
- @$local.find(selector)
+ $: (selector) ->
+ @$local.find(selector)
- initLocal: ->
- @$local = @$el.children(".local")
- @$delegateElement = @$local
+ initLocal: ->
+ @$local = @$el.children(".local")
+ @$delegateElement = @$local
- initialize: ->
- @initLocal()
- @model.id = @$el.attr("_id")
- @model.view = @
- @$el.children(".threads").children(".thread").each (index, elem) =>
- threadView = new ThreadView el: elem, model: @model.find $(elem).attr("_id")
- if @$el.hasClass("forum-discussion")
- $(".discussion-sidebar").find(".sidebar-new-post-button")
- .unbind('click').click $.proxy @newPost, @
- else if @$el.hasClass("inline-discussion")
- @newPost()
+ initialize: ->
+ @initLocal()
+ @model.id = @$el.attr("_id")
+ @model.view = @
+ @$el.children(".threads").children(".thread").each (index, elem) =>
+ threadView = new ThreadView el: elem, model: @model.find $(elem).attr("_id")
+ if @$el.hasClass("forum-discussion")
+ $(".discussion-sidebar").find(".sidebar-new-post-button")
+ .unbind('click').click $.proxy @newPost, @
+ else if @$el.hasClass("inline-discussion")
+ @newPost()
- reload: ($elem, url) ->
- if not url then return
- DiscussionUtil.get $elem, url, {}, (response, textStatus) =>
- $parent = @$el.parent()
- @$el.replaceWith(response.html)
- $discussion = $parent.find("section.discussion")
- @model.reset(response.discussionData, { silent: false })
- view = new DiscussionView el: $discussion[0], model: @model
- DiscussionUtil.bulkUpdateContentInfo(window.$$annotated_content_info)
+ reload: ($elem, url) ->
+ if not url then return
+ DiscussionUtil.safeAjax
+ $elem: $elem
+ $loading: $elem
+ 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 })
+ view = new DiscussionView el: $discussion[0], model: @model
+ DiscussionUtil.bulkUpdateContentInfo(window.$$annotated_content_info)
+ $("html, body").animate({ scrollTop: 0 }, 0)
- loadSimilarPost: (event) ->
- $title = @$(".new-post-title")
- $wrapper = @$(".new-post-similar-posts-wrapper")
- $similarPosts = @$(".new-post-similar-posts")
- prevText = $title.attr("prev-text")
- text = $title.val()
- if text == prevText
- if @$(".similar-post").length
- $wrapper.show()
- else if $.trim(text).length
- $elem = $(event.target)
- url = DiscussionUtil.urlFor 'search_similar_threads', @model.id
- data = { text: @$(".new-post-title").val() }
- DiscussionUtil.get $elem, url, data, (response, textStatus) =>
- $similarPosts.empty()
- if $.type(response) == "array" and response.length
+ loadSimilarPost: (event) ->
+ console.log "loading similar"
+ $title = @$(".new-post-title")
+ $wrapper = @$(".new-post-similar-posts-wrapper")
+ $similarPosts = @$(".new-post-similar-posts")
+ prevText = $title.attr("prev-text")
+ text = $title.val()
+ if text == prevText
+ if @$(".similar-post").length
$wrapper.show()
- for thread in response
- $similarPost = $("").addClass("similar-post")
- .html(thread["title"])
- .attr("href", "javascript:void(0)") #TODO
- .appendTo($similarPosts)
- else
- $wrapper.hide()
- else
- $wrapper.hide()
- $title.attr("prev-text", text)
+ else if $.trim(text).length
+ $elem = $(event.target)
+ url = DiscussionUtil.urlFor 'search_similar_threads', @model.id
+ data = { text: @$(".new-post-title").val() }
+ DiscussionUtil.safeAjax
+ $elem: $elem
+ url: url
+ data: data
+ dataType: 'json'
+ success: (response, textStatus) =>
+ $wrapper.html(response.html)
+ if $wrapper.find(".similar-post").length
+ $wrapper.show()
+ $wrapper.find(".hide-similar-posts").click =>
+ $wrapper.hide()
+ else
+ $wrapper.hide()
+ $title.attr("prev-text", text)
- newPost: ->
- if not @$(".wmd-panel").length
- view = { discussion_id: @model.id }
- @$el.children(".discussion-non-content").append Mustache.render DiscussionUtil.getTemplate("_new_post"), view
- $newPostBody = @$(".new-post-body")
- DiscussionUtil.makeWmdEditor @$el, $.proxy(@$, @), "new-post-body"
+ newPost: ->
+ if not @$(".wmd-panel").length
+ view = { discussion_id: @model.id }
+ @$el.children(".discussion-non-content").append Mustache.render DiscussionUtil.getTemplate("_new_post"), view
+ $newPostBody = @$(".new-post-body")
+ DiscussionUtil.makeWmdEditor @$el, $.proxy(@$, @), "new-post-body"
- $input = DiscussionUtil.getWmdInput @$el, $.proxy(@$, @), "new-post-body"
- $input.attr("placeholder", "post a new topic...")
- if @$el.hasClass("inline-discussion")
- $input.bind 'focus', (e) =>
+ $input = DiscussionUtil.getWmdInput @$el, $.proxy(@$, @), "new-post-body"
+ $input.attr("placeholder", "post a new topic...")
+ if @$el.hasClass("inline-discussion")
+ $input.bind 'focus', (e) =>
+ @$(".new-post-form").removeClass('collapsed')
+ else if @$el.hasClass("forum-discussion")
@$(".new-post-form").removeClass('collapsed')
- else if @$el.hasClass("forum-discussion")
- @$(".new-post-form").removeClass('collapsed')
- @$(".new-post-tags").tagsInput DiscussionUtil.tagsInputOptions()
+ @$(".new-post-tags").tagsInput DiscussionUtil.tagsInputOptions()
- @$(".new-post-title").blur $.proxy(@loadSimilarPost, @)
+ @$(".new-post-title").blur $.proxy(@loadSimilarPost, @)
- @$(".hide-similar-posts").click =>
- @$(".new-post-similar-posts-wrapper").hide()
+ @$(".hide-similar-posts").click =>
+ @$(".new-post-similar-posts-wrapper").hide()
- @$(".discussion-submit-post").click $.proxy(@submitNewPost, @)
- @$(".discussion-cancel-post").click $.proxy(@cancelNewPost, @)
-
-
- @$(".new-post-form").show()
-
- submitNewPost: (event) ->
- title = @$(".new-post-title").val()
- body = DiscussionUtil.getWmdContent @$el, $.proxy(@$, @), "new-post-body"
- tags = @$(".new-post-tags").val()
- anonymous = false || @$(".discussion-post-anonymously").is(":checked")
- autowatch = false || @$(".discussion-auto-watch").is(":checked")
- url = DiscussionUtil.urlFor('create_thread', @model.id)
- DiscussionUtil.safeAjax
- $elem: $(event.target)
- url: url
- type: "POST"
- dataType: 'json'
- data:
- title: title
- body: body
- tags: tags
- anonymous: anonymous
- auto_subscribe: autowatch
- error: DiscussionUtil.formErrorHandler(@$(".new-post-form-errors"))
- success: (response, textStatus) =>
- DiscussionUtil.clearFormErrors(@$(".new-post-form-errors"))
- $thread = $(response.html)
- @$el.children(".threads").prepend($thread)
-
- @$(".new-post-title").val("")
- DiscussionUtil.setWmdContent @$el, $.proxy(@$, @), "new-post-body", ""
- @$(".new-post-tags").val("")
- @$(".new-post-tags").importTags("")
-
- thread = @model.addThread response.content
- threadView = new ThreadView el: $thread[0], model: thread
- thread.updateInfo response.annotated_content_info
- @cancelNewPost()
+ @$(".discussion-submit-post").click $.proxy(@submitNewPost, @)
+ @$(".discussion-cancel-post").click $.proxy(@cancelNewPost, @)
- cancelNewPost: (event) ->
- if @$el.hasClass("inline-discussion")
- @$(".new-post-form").addClass("collapsed")
- else if @$el.hasClass("forum-discussion")
- @$(".new-post-form").hide()
+ @$(".new-post-form").show()
- search: (event) ->
- event.preventDefault()
- $elem = $(event.target)
- url = URI($elem.attr("action")).addSearch({text: @$(".search-input").val()})
- @reload($elem, url)
+ submitNewPost: (event) ->
+ title = @$(".new-post-title").val()
+ body = DiscussionUtil.getWmdContent @$el, $.proxy(@$, @), "new-post-body"
+ tags = @$(".new-post-tags").val()
+ anonymous = false || @$(".discussion-post-anonymously").is(":checked")
+ autowatch = false || @$(".discussion-auto-watch").is(":checked")
+ url = DiscussionUtil.urlFor('create_thread', @model.id)
+ DiscussionUtil.safeAjax
+ $elem: $(event.target)
+ $loading: $(event.target) if event
+ url: url
+ type: "POST"
+ dataType: 'json'
+ data:
+ title: title
+ body: body
+ tags: tags
+ anonymous: anonymous
+ auto_subscribe: autowatch
+ error: DiscussionUtil.formErrorHandler(@$(".new-post-form-errors"))
+ success: (response, textStatus) =>
+ DiscussionUtil.clearFormErrors(@$(".new-post-form-errors"))
+ $thread = $(response.html)
+ @$el.children(".threads").prepend($thread)
- sort: ->
- $elem = $(event.target)
- url = $elem.attr("sort-url")
- @reload($elem, url)
+ @$(".new-post-similar-posts").empty()
+ @$(".new-post-similar-posts-wrapper").hide()
+ @$(".new-post-title").val("").attr("prev-text", "")
+ DiscussionUtil.setWmdContent @$el, $.proxy(@$, @), "new-post-body", ""
+ @$(".new-post-tags").val("")
+ @$(".new-post-tags").importTags("")
- page: (event) ->
- $elem = $(event.target)
- url = $elem.attr("page-url")
- @reload($elem, url)
+ thread = @model.addThread response.content
+ threadView = new ThreadView el: $thread[0], model: thread
+ thread.updateInfo response.annotated_content_info
+ @cancelNewPost()
+
- events:
- "submit .search-wrapper>.discussion-search-form": "search"
- "click .discussion-search-link": "search"
- "click .discussion-sort-link": "sort"
- "click .discussion-page-link": "page"
+ cancelNewPost: (event) ->
+ if @$el.hasClass("inline-discussion")
+ @$(".new-post-form").addClass("collapsed")
+ else if @$el.hasClass("forum-discussion")
+ @$(".new-post-form").hide()
+
+ search: (event) ->
+ event.preventDefault()
+ $elem = $(event.target)
+ url = URI($elem.attr("action")).addSearch({text: @$(".search-input").val()})
+ @reload($elem, url)
+
+ sort: ->
+ $elem = $(event.target)
+ url = $elem.attr("sort-url")
+ @reload($elem, url)
+
+ page: (event) ->
+ $elem = $(event.target)
+ url = $elem.attr("page-url")
+ @reload($elem, url)
+
+ events:
+ "submit .search-wrapper>.discussion-search-form": "search"
+ "click .discussion-search-link": "search"
+ "click .discussion-sort-link": "sort"
+ "click .discussion-page-link": "page"
diff --git a/lms/static/coffee/src/discussion/discussion_module.coffee b/lms/static/coffee/src/discussion/discussion_module.coffee
index 38f44be1f1..4bcacc1474 100644
--- a/lms/static/coffee/src/discussion/discussion_module.coffee
+++ b/lms/static/coffee/src/discussion/discussion_module.coffee
@@ -1,32 +1,34 @@
-class @DiscussionModuleView extends Backbone.View
- events:
- "click .discussion-show": "toggleDiscussion"
- toggleDiscussion: (event) ->
- if @showed
- @$("section.discussion").hide()
- $(event.target).html("Show Discussion")
- @showed = false
- else
- if @retrieved
- @$("section.discussion").show()
- $(event.target).html("Hide Discussion")
- @showed = true
+if Backbone?
+ class @DiscussionModuleView extends Backbone.View
+ events:
+ "click .discussion-show": "toggleDiscussion"
+ toggleDiscussion: (event) ->
+ if @showed
+ @$("section.discussion").hide()
+ $(event.target).html("Show Discussion")
+ @showed = false
else
- $elem = $(event.target)
- discussion_id = $elem.attr("discussion_id")
- url = DiscussionUtil.urlFor 'retrieve_discussion', discussion_id
- Discussion.safeAjax
- $elem: $elem
- url: url
- type: "GET"
- dataType: 'json'
- success: (response, textStatus) =>
- @$el.append(response.html)
- $discussion = @$el.find("section.discussion")
- $(event.target).html("Hide Discussion")
- discussion = new Discussion()
- discussion.reset(response.discussionData, {silent: false})
- view = new DiscussionView(el: $discussion[0], model: discussion)
- DiscussionUtil.bulkUpdateContentInfo(window.$$annotated_content_info)
- @retrieved = true
- @showed = true
+ if @retrieved
+ @$("section.discussion").show()
+ $(event.target).html("Hide Discussion")
+ @showed = true
+ else
+ $elem = $(event.target)
+ discussion_id = $elem.attr("discussion_id")
+ url = DiscussionUtil.urlFor 'retrieve_discussion', discussion_id
+ DiscussionUtil.safeAjax
+ $elem: $elem
+ $loading: $elem
+ url: url
+ type: "GET"
+ dataType: 'json'
+ success: (response, textStatus) =>
+ @$el.append(response.html)
+ $discussion = @$el.find("section.discussion")
+ $(event.target).html("Hide Discussion")
+ discussion = new Discussion()
+ discussion.reset(response.discussionData, {silent: false})
+ view = new DiscussionView(el: $discussion[0], model: discussion)
+ DiscussionUtil.bulkUpdateContentInfo(window.$$annotated_content_info)
+ @retrieved = true
+ @showed = true
diff --git a/lms/static/coffee/src/discussion/main.coffee b/lms/static/coffee/src/discussion/main.coffee
index 2266e136ed..023345c5da 100644
--- a/lms/static/coffee/src/discussion/main.coffee
+++ b/lms/static/coffee/src/discussion/main.coffee
@@ -12,4 +12,10 @@ $ ->
discussion.reset(discussionData, {silent: false})
view = new DiscussionView(el: elem, model: discussion)
- DiscussionUtil.bulkUpdateContentInfo(window.$$annotated_content_info)
+ if window.$$annotated_content_info?
+ DiscussionUtil.bulkUpdateContentInfo(window.$$annotated_content_info)
+
+ $userProfile = $(".discussion-sidebar>.user-profile")
+ if $userProfile.length
+ console.log "initialize user profile"
+ view = new DiscussionUserProfileView(el: $userProfile[0])
diff --git a/lms/static/coffee/src/discussion/user_profile.coffee b/lms/static/coffee/src/discussion/user_profile.coffee
index 0cff708ae6..ed4f6ea988 100644
--- a/lms/static/coffee/src/discussion/user_profile.coffee
+++ b/lms/static/coffee/src/discussion/user_profile.coffee
@@ -1,34 +1,29 @@
-if not @Discussion?
- @Discussion = {}
+class @DiscussionUserProfileView extends Backbone.View
+ toggleModeratorStatus: (event) ->
+ confirmValue = confirm("Are you sure?")
+ if not confirmValue then return
+ $elem = $(event.target)
+ if $elem.hasClass("sidebar-promote-moderator-button")
+ isModerator = true
+ else if $elem.hasClass("sidebar-revoke-moderator-button")
+ isModerator = false
+ else
+ console.error "unrecognized moderator status"
+ return
+ url = DiscussionUtil.urlFor('update_moderator_status', $$profiled_user_id)
+ DiscussionUtil.safeAjax
+ $elem: $elem
+ url: url
+ type: "POST"
+ dataType: 'json'
+ data:
+ is_moderator: isModerator
+ error: (response, textStatus, e) ->
+ console.log e
+ success: (response, textStatus) =>
+ parent = @$el.parent()
+ @$el.replaceWith(response.html)
+ view = new DiscussionUserProfileView el: parent.children(".user-profile")
-Discussion = @Discussion
-
-@Discussion = $.extend @Discussion,
- initializeUserProfile: ($userProfile) ->
- $local = Discussion.generateLocal $userProfile
-
- handleUpdateModeratorStatus = (elem, isModerator) ->
- confirmValue = confirm("Are you sure?")
- if not confirmValue then return
- url = Discussion.urlFor('update_moderator_status', $$profiled_user_id)
- Discussion.safeAjax
- $elem: $(elem)
- url: url
- type: "POST"
- dataType: 'json'
- data:
- is_moderator: isModerator
- error: (response, textStatus, e) ->
- console.log e
- success: (response, textStatus) ->
- parent = $userProfile.parent()
- $userProfile.replaceWith(response.html)
- Discussion.initializeUserProfile parent.children(".user-profile")
-
- Discussion.bindLocalEvents $local,
- "click .sidebar-revoke-moderator-button": (event) ->
- handleUpdateModeratorStatus(this, false)
- "click .sidebar-promote-moderator-button": (event) ->
- handleUpdateModeratorStatus(this, true)
-
- initializeUserActiveDiscussion: ($discussion) ->
+ events:
+ "click .sidebar-toggle-moderator-button": "toggleModeratorStatus"
diff --git a/lms/static/coffee/src/discussion/utils.coffee b/lms/static/coffee/src/discussion/utils.coffee
index 0610ade1f8..e156b09a63 100644
--- a/lms/static/coffee/src/discussion/utils.coffee
+++ b/lms/static/coffee/src/discussion/utils.coffee
@@ -1,3 +1,10 @@
+$ ->
+ $.fn.extend
+ loading: ->
+ $(this).after("")
+ loaded: ->
+ $(this).parent().children(".discussion-loading").remove()
+
class @DiscussionUtil
@wmdEditors: {}
@@ -62,9 +69,16 @@ class @DiscussionUtil
$elem = params.$elem
if $elem.attr("disabled")
return
- $elem.attr("disabled", "disabled")
+ params["beforeSend"] = ->
+ $elem.attr("disabled", "disabled")
+ if params["$loading"]
+ console.log "loading"
+ params["$loading"].loading()
$.ajax(params).always ->
$elem.removeAttr("disabled")
+ if params["$loading"]
+ console.log "loaded"
+ params["$loading"].loaded()
@get: ($elem, url, data, success) ->
@safeAjax
diff --git a/lms/static/images/discussion/loading.gif b/lms/static/images/discussion/loading.gif
new file mode 100644
index 0000000000..85638328a4
Binary files /dev/null and b/lms/static/images/discussion/loading.gif differ
diff --git a/lms/static/sass/_discussion.scss b/lms/static/sass/_discussion.scss
index ad2b632bcf..717770f459 100644
--- a/lms/static/sass/_discussion.scss
+++ b/lms/static/sass/_discussion.scss
@@ -35,7 +35,13 @@ $tag-text-color: #5b614f;
}
}
-
+.discussion-loading {
+ background-image: url(../images/discussion/loading.gif);
+ width: 15px;
+ height: 15px;
+ margin-left: 2px;
+ display: inline-block;
+}
/*** Discussions ***/
@@ -49,8 +55,6 @@ $tag-text-color: #5b614f;
margin-top: 0;
}
-
-
/*** Sidebar ***/
.sidebar-module {
diff --git a/lms/static/sass/course.scss b/lms/static/sass/course.scss
index 824e84fade..6c63c17f1f 100644
--- a/lms/static/sass/course.scss
+++ b/lms/static/sass/course.scss
@@ -21,6 +21,12 @@
@import 'course/courseware/sidebar';
@import 'course/courseware/amplifier';
+
+// course-specific courseware (all styles in these files should be gated by a
+// course-specific class). This should be replaced with a better way of
+// providing course-specific styling.
+@import "course/courseware/courses/_cs188.scss";
+
// wiki
@import "course/wiki/basic-html";
@import "course/wiki/sidebar";
diff --git a/lms/static/sass/course/courseware/courses/_cs188.scss b/lms/static/sass/course/courseware/courses/_cs188.scss
new file mode 100644
index 0000000000..b069061e6b
--- /dev/null
+++ b/lms/static/sass/course/courseware/courses/_cs188.scss
@@ -0,0 +1,32 @@
+body.cs188 {
+
+ .course-content{
+
+ .project {
+ ul, ol {
+ margin-top: 3px;
+ list-style: disc;
+ ul, ol {
+ margin: 0px;
+ }
+ }
+ }
+
+ h3, h4 {
+ font-weight: bold;
+ a {
+ color: inherit;
+ }
+ }
+
+ h4 {
+ font-size: 1em;
+ }
+
+ p, .code_snippet {
+ margin-bottom: 1.416em;
+ }
+
+ }
+
+}
diff --git a/lms/templates/courseware/courseware.html b/lms/templates/courseware/courseware.html
index 141d05352c..2950aa827e 100644
--- a/lms/templates/courseware/courseware.html
+++ b/lms/templates/courseware/courseware.html
@@ -22,6 +22,7 @@
##
<%static:js group='courseware'/>
+ <%static:js group='discussion'/>
<%include file="../discussion/_js_body_dependencies.html" />
diff --git a/lms/templates/discussion/_content_renderer.html b/lms/templates/discussion/_content_renderer.html
index f81fdb612c..54371bf4dd 100644
--- a/lms/templates/discussion/_content_renderer.html
+++ b/lms/templates/discussion/_content_renderer.html
@@ -1,20 +1,20 @@
<%! import django_comment_client.helpers as helpers %>
-<%def name="render_content(content)">
- ${helpers.render_content(content)}
+<%def name="render_content(content, *args, **kwargs)">
+ ${helpers.render_content(content, *args, **kwargs)}
%def>
-<%def name="render_content_with_comments(content)">
+<%def name="render_content_with_comments(content, *args, **kwargs)">