diff --git a/lms/djangoapps/django_comment_client/base/views.py b/lms/djangoapps/django_comment_client/base/views.py
index 68250a035e..2fee03c5bd 100644
--- a/lms/djangoapps/django_comment_client/base/views.py
+++ b/lms/djangoapps/django_comment_client/base/views.py
@@ -38,11 +38,10 @@ def permitted(fn):
else:
content = None
return content
-
if check_permissions_by_view(request.user, kwargs['course_id'], fetch_content(), request.view_name):
return fn(request, *args, **kwargs)
else:
- return JsonError("unauthorized")
+ return JsonError("unauthorized", status=401)
return wrapper
def ajax_content_response(request, course_id, content, template_name):
@@ -214,7 +213,7 @@ def undo_vote_for_thread(request, course_id, thread_id):
thread = cc.Thread.find(thread_id)
user.unvote(thread)
return JsonResponse(utils.safe_content(thread.to_dict()))
-
+
@require_POST
@login_required
@@ -288,7 +287,7 @@ def update_moderator_status(request, course_id, user_id):
course = get_course_with_access(request.user, course_id, 'load')
discussion_user = cc.User(id=user_id, course_id=course_id)
context = {
- 'course': course,
+ 'course': course,
'course_id': course_id,
'user': request.user,
'django_user': user,
@@ -327,7 +326,7 @@ def tags_autocomplete(request, course_id):
@require_POST
@login_required
@csrf.csrf_exempt
-def upload(request, course_id):#ajax upload file to a question or answer
+def upload(request, course_id):#ajax upload file to a question or answer
"""view that handles file upload via Ajax
"""
@@ -337,7 +336,7 @@ def upload(request, course_id):#ajax upload file to a question or answer
new_file_name = ''
try:
# TODO authorization
- #may raise exceptions.PermissionDenied
+ #may raise exceptions.PermissionDenied
#if request.user.is_anonymous():
# msg = _('Sorry, anonymous users cannot upload files')
# raise exceptions.PermissionDenied(msg)
@@ -357,7 +356,7 @@ def upload(request, course_id):#ajax upload file to a question or answer
new_file_name = str(
time.time()
).replace(
- '.',
+ '.',
str(random.randint(0,100000))
) + file_extension
@@ -386,7 +385,7 @@ def upload(request, course_id):#ajax upload file to a question or answer
parsed_url = urlparse.urlparse(file_url)
file_url = urlparse.urlunparse(
urlparse.ParseResult(
- parsed_url.scheme,
+ parsed_url.scheme,
parsed_url.netloc,
parsed_url.path,
'', '', ''
diff --git a/lms/djangoapps/django_comment_client/forum/views.py b/lms/djangoapps/django_comment_client/forum/views.py
index af896f6f80..c21bfcbd6f 100644
--- a/lms/djangoapps/django_comment_client/forum/views.py
+++ b/lms/djangoapps/django_comment_client/forum/views.py
@@ -136,10 +136,16 @@ def inline_discussion(request, course_id, discussion_id):
# html = render_inline_discussion(request, course_id, threads, discussion_id=discussion_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), {})
return utils.JsonResponse({
# 'html': html,
'discussion_data': map(utils.safe_content, threads),
'user_info': user_info,
+ 'annotated_content_info': annotated_content_info
})
def render_search_bar(request, course_id, discussion_id=None, text=''):
diff --git a/lms/djangoapps/django_comment_client/utils.py b/lms/djangoapps/django_comment_client/utils.py
index b0a570caad..1c99cdc72b 100644
--- a/lms/djangoapps/django_comment_client/utils.py
+++ b/lms/djangoapps/django_comment_client/utils.py
@@ -100,24 +100,24 @@ def initialize_discussion_info(course):
unexpanded_category_map[category].append({"title": title, "id": id,
"sort_key": sort_key})
- category_map = {"entries": defaultdict(dict), "subcategories": defaultdict(dict)}
+ category_map = {"entries": defaultdict(dict), "subcategories": defaultdict(dict)}
for category_path, entries in unexpanded_category_map.items():
node = category_map["subcategories"]
path = [x.strip() for x in category_path.split("/")]
for level in path[:-1]:
if level not in node:
- node[level] = {"subcategories": defaultdict(dict),
+ node[level] = {"subcategories": defaultdict(dict),
"entries": defaultdict(dict),
- "sort_key": level}
+ "sort_key": level}
node = node[level]["subcategories"]
level = path[-1]
if level not in node:
- node[level] = {"subcategories": defaultdict(dict),
- "entries": defaultdict(dict),
+ node[level] = {"subcategories": defaultdict(dict),
+ "entries": defaultdict(dict),
"sort_key": level}
for entry in entries:
- node[level]["entries"][entry["title"]] = {"id": entry["id"],
+ node[level]["entries"][entry["title"]] = {"id": entry["id"],
"sort_key": entry["sort_key"]}
for topic, entry in course.metadata.get('discussion_topics', {}).items():
@@ -134,7 +134,7 @@ def initialize_discussion_info(course):
def get_courseware_context(content, course):
id_map = get_discussion_id_map(course)
- id = content['commentable_id']
+ id = content['commentable_id']
content_info = None
if id in id_map:
location = id_map[id]["location"].url()
@@ -149,21 +149,21 @@ class JsonResponse(HttpResponse):
mimetype='application/json; charset=utf8')
class JsonError(HttpResponse):
- def __init__(self, error_messages=[]):
+ def __init__(self, error_messages=[], status=400):
if isinstance(error_messages, str):
error_messages = [error_messages]
content = simplejson.dumps({'errors': error_messages},
indent=2,
ensure_ascii=False)
super(JsonError, self).__init__(content,
- mimetype='application/json; charset=utf8', status=400)
+ mimetype='application/json; charset=utf8', status=status)
class HtmlResponse(HttpResponse):
def __init__(self, html=''):
super(HtmlResponse, self).__init__(html, content_type='text/plain')
-class ViewNameMiddleware(object):
- def process_view(self, request, view_func, view_args, view_kwargs):
+class ViewNameMiddleware(object):
+ def process_view(self, request, view_func, view_args, view_kwargs):
request.view_name = view_func.__name__
class QueryCountDebugMiddleware(object):
diff --git a/lms/lib/comment_client/thread.py b/lms/lib/comment_client/thread.py
index 1f6d081f7f..e4ada77499 100644
--- a/lms/lib/comment_client/thread.py
+++ b/lms/lib/comment_client/thread.py
@@ -12,6 +12,7 @@ class Thread(models.Model):
'created_at', 'updated_at', 'comments_count',
'at_position_list', 'children', 'type',
'highlighted_title', 'highlighted_body',
+ 'endorsed'
]
updatable_fields = [
diff --git a/lms/static/coffee/src/discussion/discussion.coffee b/lms/static/coffee/src/discussion/discussion.coffee
index 002593855c..2944449f61 100644
--- a/lms/static/coffee/src/discussion/discussion.coffee
+++ b/lms/static/coffee/src/discussion/discussion.coffee
@@ -7,7 +7,6 @@ if Backbone?
item.discussion = @
@comparator = @sortByDateRecentFirst
@on "thread:remove", (thread) =>
- console.log "remove triggered"
@remove(thread)
find: (id) ->
@@ -24,8 +23,8 @@ if Backbone?
sortByDateRecentFirst: (thread) ->
-(new Date(thread.get("created_at")).getTime())
- #return String.fromCharCode.apply(String,
- # _.map(thread.get("created_at").split(""),
+ #return String.fromCharCode.apply(String,
+ # _.map(thread.get("created_at").split(""),
# ((c) -> return 0xffff - c.charChodeAt()))
#)
@@ -134,7 +133,7 @@ if Backbone?
@$(".discussion-submit-post").click $.proxy(@submitNewPost, @)
@$(".discussion-cancel-post").click $.proxy(@cancelNewPost, @)
-
+
@$el.children(".blank").hide()
@$(".new-post-form").show()
@@ -177,7 +176,7 @@ if Backbone?
threadView = new ThreadView el: $thread[0], model: thread
thread.updateInfo response.annotated_content_info
@cancelNewPost()
-
+
cancelNewPost: (event) ->
if @$el.hasClass("inline-discussion")
diff --git a/lms/static/coffee/src/discussion/discussion_module.coffee b/lms/static/coffee/src/discussion/discussion_module.coffee
deleted file mode 100644
index 360a3a2535..0000000000
--- a/lms/static/coffee/src/discussion/discussion_module.coffee
+++ /dev/null
@@ -1,39 +0,0 @@
-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
- 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)
- window.user = new DiscussionUser(response.user_info)
- $(event.target).html("Hide Discussion")
- discussion = new Discussion()
- discussion.reset(response.discussion_data, {silent: false})
- $discussion = $(Mustache.render $("script#_inline_discussion").html(), {'threads':response.discussion_data})
- $(".discussion-module").append($discussion)
- discussion.each (thread) ->
- element = $("article#thread_#{thread.id}")
- dtv = new DiscussionThreadInlineView el: element, model: thread
- dtv.render()
- DiscussionUtil.bulkUpdateContentInfo(window.$$annotated_content_info)
- @retrieved = true
- @showed = true
diff --git a/lms/static/coffee/src/discussion/discussion_module_view.coffee b/lms/static/coffee/src/discussion/discussion_module_view.coffee
new file mode 100644
index 0000000000..06ed320623
--- /dev/null
+++ b/lms/static/coffee/src/discussion/discussion_module_view.coffee
@@ -0,0 +1,64 @@
+if Backbone?
+ class @DiscussionModuleView extends Backbone.View
+ events:
+ "click .discussion-show": "toggleDiscussion"
+ "click .new-post-btn": "toggleNewPost"
+ "click .new-post-cancel": "hideNewPost"
+ initialize: ->
+
+ toggleNewPost: (event) ->
+ if @newPostForm.is(':hidden')
+ @newPostForm.slideDown(300)
+ else
+ @newPostForm.slideUp(300)
+ hideNewPost: (event) ->
+ @newPostForm.slideUp(300)
+
+ 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
+ else
+ $elem = $(event.target)
+ discussionId = $elem.data("discussion-id")
+ url = DiscussionUtil.urlFor 'retrieve_discussion', discussionId
+ DiscussionUtil.safeAjax
+ $elem: $elem
+ $loading: $elem
+ url: url
+ type: "GET"
+ dataType: 'json'
+ success: (response, textStatus, jqXHR) => @createDiscussion(event, response, textStatus, discussionId)
+
+ createDiscussion: (event, response, textStatus, discussionId) =>
+ window.user = new DiscussionUser(response.user_info)
+ Content.loadContentInfos(response.annotated_content_info)
+ $(event.target).html("Hide Discussion")
+ @discussion = new Discussion()
+ @discussion.reset(response.discussion_data, {silent: false})
+ $discussion = $(Mustache.render $("script#_inline_discussion").html(), {'threads':response.discussion_data, 'discussionId': discussionId})
+ $(".discussion-module").append($discussion)
+ @newPostForm = $('.new-post-article')
+ @threadviews = @discussion.map (thread) ->
+ new DiscussionThreadInlineView el: @$("article#thread_#{thread.id}"), model: thread
+ _.each @threadviews, (dtv) -> dtv.render()
+ DiscussionUtil.bulkUpdateContentInfo(window.$$annotated_content_info)
+ @newPostView = new NewPostInlineView el: @$('.new-post-article'), collection: @discussion
+ @discussion.on "add", @addThread
+ @retrieved = true
+ @showed = true
+
+ addThread: (thread, collection, options) =>
+ # TODO: When doing pagination, this will need to repaginate
+ article = $("")
+ @$('section.discussion > .threads').prepend(article)
+ threadView = new DiscussionThreadInlineView el: article, model: thread
+ threadView.render()
+ @threadviews.unshift threadView
+
diff --git a/lms/static/coffee/src/discussion/utils.coffee b/lms/static/coffee/src/discussion/utils.coffee
index 392aa04538..7c388d6d20 100644
--- a/lms/static/coffee/src/discussion/utils.coffee
+++ b/lms/static/coffee/src/discussion/utils.coffee
@@ -3,7 +3,7 @@ $ ->
window.$$contents = {}
$.fn.extend
loading: ->
- @$_loading = $("")
+ @$_loading = $("
")
$(this).after(@$_loading)
loaded: ->
@$_loading.remove()
@@ -107,6 +107,9 @@ class @DiscussionUtil
[event, selector] = eventSelector.split(' ')
$local(selector).unbind(event)[event] handler
+ @processTag: (text) ->
+ text.toLowerCase()
+
@tagsInputOptions: ->
autocomplete_url: @urlFor('tags_autocomplete')
autocomplete:
@@ -116,6 +119,7 @@ class @DiscussionUtil
width: '100%'
defaultText: "Tag your post: press enter after each tag"
removeWithBackspace: true
+ preprocessTag: @processTag
@formErrorHandler: (errorsField) ->
(xhr, textStatus, error) ->
@@ -123,7 +127,7 @@ class @DiscussionUtil
if response.errors? and response.errors.length > 0
errorsField.empty()
for error in response.errors
- errorsField.append($("").addClass("new-post-form-error").html(error))
+ errorsField.append($("").addClass("new-post-form-error").html(error)).show()
@clearFormErrors: (errorsField) ->
errorsField.empty()
diff --git a/lms/static/coffee/src/discussion/views/discussion_thread_inline_view.coffee b/lms/static/coffee/src/discussion/views/discussion_thread_inline_view.coffee
index 30f0ff8cfc..76037d7341 100644
--- a/lms/static/coffee/src/discussion/views/discussion_thread_inline_view.coffee
+++ b/lms/static/coffee/src/discussion/views/discussion_thread_inline_view.coffee
@@ -21,10 +21,6 @@ class @DiscussionThreadInlineView extends DiscussionContentView
@model.on "change", @updateModelDetails
render: ->
- #TODO: Debugging, remove when done
- if not window.$disc
- window.$disc = []
- window.$disc.push(@)
if not @model.has('abbreviatedBody')
@abbreviateBody()
@$el.html(Mustache.render(@template(), $.extend(@model.toJSON(),{expanded: @expanded}) ))
@@ -38,8 +34,6 @@ class @DiscussionThreadInlineView extends DiscussionContentView
if @expanded
@makeWmdEditor "reply-body"
@renderResponses()
-# @highlight @$(".post-body")
-# @highlight @$("h1")
@
renderDogear: ->
@@ -58,12 +52,13 @@ class @DiscussionThreadInlineView extends DiscussionContentView
convertMath: ->
element = @$(".post-body")
- element.html DiscussionUtil.postMathJaxProcessor(element.html())
+ element.html DiscussionUtil.postMathJaxProcessor DiscussionUtil.markdownWithHighlight element.html()
MathJax.Hub.Queue ["Typeset", MathJax.Hub, element[0]]
renderResponses: ->
DiscussionUtil.safeAjax
url: "/courses/#{$$course_id}/discussion/forum/#{@model.get('commentable_id')}/threads/#{@model.id}"
+ $loading: @$el
success: (data, textStatus, xhr) =>
@$el.find(".loading").remove()
Content.loadContentInfos(data['annotated_content_info'])
@@ -192,16 +187,14 @@ class @DiscussionThreadInlineView extends DiscussionContentView
success: (response, textStatus) =>
@model.set('endorsed', not endorsed)
- highlight: (el) ->
- el.html(el.html().replace(/<mark>/g, "").replace(/<\/mark>/g, ""))
-
abbreviateBody: ->
- abbreviated = DiscussionUtil.abbreviateString @model.get('body'), 140 # Because twitter
+ abbreviated = DiscussionUtil.abbreviateString @model.get('body'), 140
@model.set('abbreviatedBody', abbreviated)
expandPost: (event) ->
@expanded = true
@$el.find('.post-body').html(@model.get('body'))
+ @convertMath()
@$el.find('.expand-post').hide()
@$el.find('.collapse-post').show()
@$el.find('.post-extended-content').show()
@@ -212,6 +205,7 @@ class @DiscussionThreadInlineView extends DiscussionContentView
collapsePost: (event) ->
@expanded = false
@$el.find('.post-body').html(@model.get('abbreviatedBody'))
+ @convertMath()
@$el.find('.collapse-post').hide()
@$el.find('.post-extended-content').hide()
@$el.find('.expand-post').show()
diff --git a/lms/static/coffee/src/discussion/views/discussion_thread_list_view.coffee b/lms/static/coffee/src/discussion/views/discussion_thread_list_view.coffee
index 6b3a17b4c4..08f9fb754f 100644
--- a/lms/static/coffee/src/discussion/views/discussion_thread_list_view.coffee
+++ b/lms/static/coffee/src/discussion/views/discussion_thread_list_view.coffee
@@ -15,6 +15,7 @@ class @DiscussionThreadListView extends Backbone.View
@collection.on "add", @addAndSelectThread
@sidebar_padding = 10
@sidebar_header_height = 87
+ @boardName
reloadDisplayedCollection: (thread) =>
thread_id = thread.get('id')
@@ -41,8 +42,8 @@ class @DiscussionThreadListView extends Backbone.View
windowHeight = $(window).height();
discussionBody = $(".discussion-article")
- discussionsBodyTop = if discussionBody[0] then discussionBody.offset().top;
- discussionsBodyBottom = discussionsBodyTop + discussionBody.outerHeight();
+ discussionsBodyTop = if discussionBody[0] then discussionBody.offset().top
+ discussionsBodyBottom = discussionsBodyTop + discussionBody.outerHeight()
sidebar = $(".sidebar")
if scrollTop > discussionsBodyTop - @sidebar_padding
@@ -62,10 +63,11 @@ class @DiscussionThreadListView extends Backbone.View
amount = Math.max(topOffset - discussionBottomOffset, 0)
sidebarHeight = sidebarHeight - @sidebar_padding - amount
- sidebar.css 'height', Math.min(Math.max(sidebarHeight, 400), discussionBody.outerHeight())
+ sidebarHeight = Math.min(Math.max(sidebarHeight, 400), discussionBody.outerHeight())
+ sidebar.css 'height', sidebarHeight
postListWrapper = @$('.post-list-wrapper')
- postListWrapper.css('height', (sidebarHeight - @sidebar_header_height - 4) + 'px');
+ postListWrapper.css('height', (sidebarHeight - @sidebar_header_height - 4) + 'px')
# Because we want the behavior that when the body is clicked the menu is
@@ -101,6 +103,8 @@ class @DiscussionThreadListView extends Backbone.View
content = $(_.template($("#thread-list-item-template").html())(thread.toJSON()))
if thread.get('subscribed')
content.addClass("followed")
+ if thread.get('endorsed')
+ content.addClass("resolved")
@highlight(content)
@@ -137,28 +141,66 @@ class @DiscussionThreadListView extends Backbone.View
@$(".browse").toggleClass('is-dropped')
if @$(".browse").hasClass('is-dropped')
@$(".browse-topic-drop-menu-wrapper").show()
- $('body').bind 'click', @toggleTopicDrop
- $('body').bind 'keydown', @setActiveItem
+ $(".browse-topic-drop-search-input").focus()
+ $("body").bind "click", @toggleTopicDrop
+ $("body").bind "keydown", @setActiveItem
else
@$(".browse-topic-drop-menu-wrapper").hide()
- $('body').unbind 'click', @toggleTopicDrop
- $('body').unbind 'keydown', @setActiveItem
+ $("body").unbind "click", @toggleTopicDrop
+ $("body").unbind "keydown", @setActiveItem
setTopic: (event) ->
item = $(event.target).closest('a')
boardName = item.find(".board-name").html()
_.each item.parents('ul').not('.browse-topic-drop-menu'), (parent) ->
boardName = $(parent).siblings('a').find('.board-name').html() + ' / ' + boardName
- @$(".current-board").html(boardName)
+ @$(".current-board").html(@fitName(boardName))
fontSize = 16
@$(".current-board").css('font-size', '16px')
-
while @$(".current-board").width() > (@$el.width() * .8) - 40
fontSize--
if fontSize < 11
break
@$(".current-board").css('font-size', fontSize + 'px')
+ setSelectedTopic: (name) ->
+ @$(".current-board").html(@fitName(name))
+
+ getNameWidth: (name) ->
+ test = $("")
+ test.css
+ "font-size": @$(".current-board").css('font-size')
+ opacity: 0
+ position: 'absolute'
+ left: -1000
+ top: -1000
+ $("body").append(test)
+ test.html(name)
+ width = test.width()
+ test.remove()
+ return width
+
+ fitName: (name) ->
+ width = @getNameWidth(name)
+ if width < @maxNameWidth
+ return name
+ path = (x.replace /^\s+|\s+$/g, "" for x in name.split("/"))
+ while path.length > 1
+ path.shift()
+ partialName = "... / " + path.join(" / ")
+ if @getNameWidth(partialName) < @maxNameWidth
+ return partialName
+
+ rawName = path[0]
+
+ name = "... / " + rawName
+
+ while @getNameWidth(name) > @maxNameWidth
+ rawName = rawName[0...rawName.length-1]
+ name = "... / " + rawName + " ..."
+
+ return name
+
filterTopic: (event) ->
@setTopic(event)
item = $(event.target).closest('li')
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 84c99796ee..8f1054a310 100644
--- a/lms/static/coffee/src/discussion/views/discussion_thread_view.coffee
+++ b/lms/static/coffee/src/discussion/views/discussion_thread_view.coffee
@@ -49,7 +49,7 @@ class @DiscussionThreadView extends DiscussionContentView
convertMath: ->
element = @$(".post-body")
- element.html DiscussionUtil.postMathJaxProcessor(element.html())
+ element.html DiscussionUtil.postMathJaxProcessor DiscussionUtil.markdownWithHighlight element.html()
MathJax.Hub.Queue ["Typeset", MathJax.Hub, element[0]]
renderResponses: ->
@@ -66,12 +66,17 @@ class @DiscussionThreadView extends DiscussionContentView
response.set('thread', @model)
view = new ThreadResponseView(model: response)
view.on "comment:add", @addComment
+ view.on "comment:endorse", @endorseThread
view.render()
@$el.find(".responses").append(view.el)
addComment: =>
@model.comment()
+ endorseThread: (endorsed) =>
+ is_endorsed = @$el.find(".is-endorsed").length
+ @model.set 'endorsed', is_endorsed
+
toggleVote: (event) ->
event.preventDefault()
if window.user.voted(@model)
diff --git a/lms/static/coffee/src/discussion/views/new_post_inline_vew.coffee b/lms/static/coffee/src/discussion/views/new_post_inline_vew.coffee
new file mode 100644
index 0000000000..f3b04d14ce
--- /dev/null
+++ b/lms/static/coffee/src/discussion/views/new_post_inline_vew.coffee
@@ -0,0 +1,55 @@
+class @NewPostInlineView extends Backbone.View
+
+ initialize: () ->
+
+ @topicId = @$(".topic").first().data("discussion-id")
+
+ @maxNameWidth = 100
+
+ DiscussionUtil.makeWmdEditor @$el, $.proxy(@$, @), "new-post-body"
+ @$(".new-post-tags").tagsInput DiscussionUtil.tagsInputOptions()
+
+ events:
+ "submit .new-post-form": "createPost"
+
+ # Because we want the behavior that when the body is clicked the menu is
+ # closed, we need to ignore clicks in the search field and stop propagation.
+ # Without this, clicking the search field would also close the menu.
+ ignoreClick: (event) ->
+ event.stopPropagation()
+
+ createPost: (event) ->
+ event.preventDefault()
+ title = @$(".new-post-title").val()
+ body = @$(".new-post-body").find(".wmd-input").val()
+ tags = @$(".new-post-tags").val()
+
+ anonymous = false || @$("input.discussion-anonymous").is(":checked")
+ follow = false || @$("input.discussion-follow").is(":checked")
+
+ url = DiscussionUtil.urlFor('create_thread', @topicId)
+
+ DiscussionUtil.safeAjax
+ $elem: $(event.target)
+ $loading: $(event.target) if event
+ url: url
+ type: "POST"
+ dataType: 'json'
+ async: false # TODO when the rest of the stuff below is made to work properly..
+ data:
+ title: title
+ body: body
+ tags: tags
+ anonymous: anonymous
+ auto_subscribe: follow
+ error: DiscussionUtil.formErrorHandler(@$(".new-post-form-errors"))
+ success: (response, textStatus) =>
+ # TODO: Move this out of the callback, this makes it feel sluggish
+ thread = new Thread response['content']
+ DiscussionUtil.clearFormErrors(@$(".new-post-form-errors"))
+ @$el.hide()
+ @$(".new-post-title").val("").attr("prev-text", "")
+ @$(".new-post-body textarea").val("").attr("prev-text", "")
+ @$(".new-post-tags").val("")
+ @$(".new-post-tags").importTags("")
+ @collection.add thread
diff --git a/lms/static/coffee/src/discussion/views/new_post_view.coffee b/lms/static/coffee/src/discussion/views/new_post_view.coffee
index e1a859b62a..927aa76536 100644
--- a/lms/static/coffee/src/discussion/views/new_post_view.coffee
+++ b/lms/static/coffee/src/discussion/views/new_post_view.coffee
@@ -38,9 +38,10 @@ class @NewPostView extends Backbone.View
@menuOpen = true
@dropdownButton.addClass('dropped')
@topicMenu.show()
+ $(".form-topic-drop-search-input").focus()
- $('body').bind 'keydown', @setActiveItem
- $('body').bind 'click', @hideTopicDropdown
+ $("body").bind "keydown", @setActiveItem
+ $("body").bind "click", @hideTopicDropdown
# Set here because 1) the window might get resized and things could
# change and 2) can't set in initialize because the button is hidden
@@ -52,8 +53,8 @@ class @NewPostView extends Backbone.View
@dropdownButton.removeClass('dropped')
@topicMenu.hide()
- $('body').unbind 'keydown', @setActiveItem
- $('body').unbind 'click', @hideTopicDropdown
+ $("body").unbind "keydown", @setActiveItem
+ $("body").unbind "click", @hideTopicDropdown
setTopic: (event) ->
$target = $(event.target)
@@ -142,7 +143,7 @@ class @NewPostView extends Backbone.View
DiscussionUtil.clearFormErrors(@$(".new-post-form-errors"))
@$el.hide()
@$(".new-post-title").val("").attr("prev-text", "")
- @$(".new-post-body").val("").attr("prev-text", "")
+ @$(".new-post-body textarea").val("").attr("prev-text", "")
@$(".new-post-tags").val("")
@$(".new-post-tags").importTags("")
@collection.add thread
@@ -168,7 +169,7 @@ class @NewPostView extends Backbone.View
itemTop = $(items[index]).parent().offset().top
scrollTop = $(".topic_menu").scrollTop()
- itemFromTop = $(".topic_menu").offset().top - itemTop
+ itemFromTop = $(".topic_menu").offset().top - itemTop
scrollTarget = Math.min(scrollTop - itemFromTop, scrollTop)
scrollTarget = Math.max(scrollTop - itemFromTop - $(".topic_menu").height() + $(items[index]).height() + 20, scrollTarget)
$(".topic_menu").scrollTop(scrollTarget)
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 3131912ced..262617eb84 100644
--- a/lms/static/coffee/src/discussion/views/response_comment_view.coffee
+++ b/lms/static/coffee/src/discussion/views/response_comment_view.coffee
@@ -2,6 +2,7 @@ class @ResponseCommentView extends DiscussionContentView
tagName: "li"
template: _.template($("#response-comment-template").html())
initLocal: ->
+ # TODO .response-local is the parent of the comments so @$local is null, not sure what was intended here...
@$local = @$el.find(".response-local")
@$delegateElement = @$local
@@ -14,8 +15,9 @@ class @ResponseCommentView extends DiscussionContentView
@convertMath()
@
convertMath: ->
- body = @$(".response-body")
+ body = @$el.find(".response-body")
body.html DiscussionUtil.postMathJaxProcessor DiscussionUtil.markdownWithHighlight body.html()
+ # This removes paragraphs so that comments are more compact
body.children("p").each (index, elem) ->
$(elem).replaceWith($(elem).html())
MathJax.Hub.Queue ["Typeset", MathJax.Hub, body[0]]
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 fc7d9d56e4..86deb82e6d 100644
--- a/lms/static/coffee/src/discussion/views/thread_response_view.coffee
+++ b/lms/static/coffee/src/discussion/views/thread_response_view.coffee
@@ -109,6 +109,7 @@ class @ThreadResponseView extends DiscussionContentView
endorsed = @model.get('endorsed')
data = { endorsed: not endorsed }
@model.set('endorsed', not endorsed)
+ @trigger "comment:endorse", not endorsed
DiscussionUtil.safeAjax
$elem: $elem
url: url
diff --git a/lms/static/images/staff-icons.png b/lms/static/images/staff-icons.png
new file mode 100644
index 0000000000..7efb9a8cd1
Binary files /dev/null and b/lms/static/images/staff-icons.png differ
diff --git a/lms/static/images/white-error-icon.png b/lms/static/images/white-error-icon.png
new file mode 100644
index 0000000000..6204f44513
Binary files /dev/null and b/lms/static/images/white-error-icon.png differ
diff --git a/lms/static/js/discussions-temp.js b/lms/static/js/discussions-temp.js
index b0ce231eb5..dd6af6ef14 100644
--- a/lms/static/js/discussions-temp.js
+++ b/lms/static/js/discussions-temp.js
@@ -58,7 +58,7 @@ $(document).ready(function() {
$('.new-post-btn').bind('click', newPost);
$('.new-post-cancel').bind('click', closeNewPost);
- $('[data-tooltip]').bind({
+ $body.delegate('[data-tooltip]', {
'mouseover': showTooltip,
'mousemove': moveTooltip,
'mouseout': hideTooltip,
@@ -66,14 +66,6 @@ $(document).ready(function() {
});
$body.delegate('.browse-topic-drop-search-input, .form-topic-drop-search-input', 'keyup', filterDrop);
-
-// $(window).bind('resize', updateSidebar);
-// $(window).bind('scroll', updateSidebar);
-// $('.discussion-column').bind("input", function (e) {
-// console.log("resized");
-// updateSidebar();
-// })
-// updateSidebar();
});
function filterDrop(e) {
@@ -276,6 +268,7 @@ function setTopic(e) {
function newPost(e) {
$newPost.slideDown(300);
+ $('.new-post-title').focus();
}
function closeNewPost(e) {
diff --git a/lms/static/js/jquery.tagsinput.js b/lms/static/js/jquery.tagsinput.js
index b05a87ca99..563567ac46 100644
--- a/lms/static/js/jquery.tagsinput.js
+++ b/lms/static/js/jquery.tagsinput.js
@@ -1,12 +1,12 @@
/*
jQuery Tags Input Plugin 1.3.3
-
+
Copyright (c) 2011 XOXCO, Inc
-
+
Documentation for this plugin lives here:
http://xoxco.com/clickable/jquery-tags-input
-
+
Licensed under the MIT license:
http://www.opensource.org/licenses/mit-license.php
@@ -24,9 +24,9 @@
val = '',
input = $(this),
testSubject = $('#'+$(this).data('tester_id'));
-
+
if (val === (val = input.val())) {return;}
-
+
// Enter new content into testSubject
var escaped = val.replace(/&/g, '&').replace(/\s/g,' ').replace(//g, '>');
testSubject.html(escaped);
@@ -36,7 +36,7 @@
currentWidth = input.width(),
isValidWidthChange = (newWidth < currentWidth && newWidth >= minWidth)
|| (newWidth > minWidth && newWidth < maxWidth);
-
+
// Animate width
if (isValidWidthChange) {
input.width(newWidth);
@@ -72,19 +72,24 @@
input.data('tester_id', testerId);
input.css('width', minWidth);
};
-
+
$.fn.addTag = function(value,options) {
options = jQuery.extend({focus:false,callback:true},options);
- this.each(function() {
+ this.each(function() {
var id = $(this).attr('id');
var tagslist = $(this).val().split(delimiter[id]);
- if (tagslist[0] == '') {
+ if (tagslist[0] == '') {
tagslist = new Array();
}
value = jQuery.trim(value);
-
+
+ if (options.callback && tags_callbacks[id] && tags_callbacks[id]['preprocessTag']) {
+ var f = tags_callbacks[id]['preprocessTag'];
+ value = f.call(this, value);
+ }
+
if (options.unique) {
var skipTag = $(this).tagExist(value);
if(skipTag == true) {
@@ -92,10 +97,10 @@
$('#'+id+'_tag').addClass('not_valid');
}
} else {
- var skipTag = false;
+ var skipTag = false;
}
-
- if (value !='' && skipTag != true) {
+
+ if (value !='' && skipTag != true) {
$('
').addClass('tag').append(
$('').text(value).append(' '),
$('', {
@@ -108,16 +113,16 @@
).insertBefore('#' + id + '_addTag');
tagslist.push(value);
-
+
$('#'+id+'_tag').val('');
if (options.focus) {
$('#'+id+'_tag').focus();
- } else {
+ } else {
$('#'+id+'_tag').blur();
}
-
+
$.fn.tagsInput.updateTagsField(this,tagslist);
-
+
if (options.callback && tags_callbacks[id] && tags_callbacks[id]['onAddTag']) {
var f = tags_callbacks[id]['onAddTag'];
f.call(this, value);
@@ -127,29 +132,29 @@
var i = tagslist.length;
var f = tags_callbacks[id]['onChange'];
f.call(this, $(this), tagslist[i-1]);
- }
+ }
}
-
- });
-
+
+ });
+
return false;
};
-
- $.fn.removeTag = function(value) {
+
+ $.fn.removeTag = function(value) {
value = unescape(value);
- this.each(function() {
+ this.each(function() {
var id = $(this).attr('id');
-
+
var old = $(this).val().split(delimiter[id]);
-
+
$('#'+id+'_tagsinput .tag').remove();
str = '';
- for (i=0; i< old.length; i++) {
- if (old[i]!=value) {
+ for (i=0; i< old.length; i++) {
+ if (old[i]!=value) {
str = str + delimiter[id] +old[i];
}
}
-
+
$.fn.tagsInput.importTags(this,str);
if (tags_callbacks[id] && tags_callbacks[id]['onRemoveTag']) {
@@ -157,24 +162,24 @@
f.call(this, value);
}
});
-
+
return false;
};
-
+
$.fn.tagExist = function(val) {
var id = $(this).attr('id');
var tagslist = $(this).val().split(delimiter[id]);
return (jQuery.inArray(val, tagslist) >= 0); //true when tag exists, false when not
};
-
+
// clear all existing tags and import new ones from a string
$.fn.importTags = function(str) {
id = $(this).attr('id');
$('#'+id+'_tagsinput .tag').remove();
$.fn.tagsInput.importTags(this,str);
}
-
- $.fn.tagsInput = function(options) {
+
+ $.fn.tagsInput = function(options) {
var settings = jQuery.extend({
interactive:true,
defaultText:'add a tag',
@@ -192,15 +197,15 @@
inputPadding: 6*2
},options);
- this.each(function() {
- if (settings.hide) {
- $(this).hide();
+ this.each(function() {
+ if (settings.hide) {
+ $(this).hide();
}
var id = $(this).attr('id');
if (!id || delimiter[$(this).attr('id')]) {
id = $(this).attr('id', 'tags' + new Date().getTime()).attr('id');
}
-
+
var data = jQuery.extend({
pid:id,
real_input: '#'+id,
@@ -208,57 +213,58 @@
input_wrapper: '#'+id+'_addTag',
fake_input: '#'+id+'_tag'
},settings);
-
+
delimiter[id] = data.delimiter;
-
- if (settings.onAddTag || settings.onRemoveTag || settings.onChange) {
+
+ if (settings.onAddTag || settings.onRemoveTag || settings.onChange || settings.preprocessTag) {
tags_callbacks[id] = new Array();
tags_callbacks[id]['onAddTag'] = settings.onAddTag;
tags_callbacks[id]['onRemoveTag'] = settings.onRemoveTag;
tags_callbacks[id]['onChange'] = settings.onChange;
+ tags_callbacks[id]['preprocessTag'] = settings.preprocessTag;
}
-
+
var markup = '';
-
+
$(markup).insertAfter(this);
-
+
$(data.holder).css('width',settings.width);
$(data.holder).css('min-height',settings.height);
$(data.holder).css('height','100%');
-
- if ($(data.real_input).val()!='') {
+
+ if ($(data.real_input).val()!='') {
$.fn.tagsInput.importTags($(data.real_input),$(data.real_input).val());
- }
- if (settings.interactive) {
+ }
+ if (settings.interactive) {
$(data.fake_input).val($(data.fake_input).attr('data-default'));
$(data.fake_input).css('color',settings.placeholderColor);
$(data.fake_input).resetAutosize(settings);
-
+
$(data.fake_input).doAutosize(settings);
$(data.holder).bind('click',data,function(event) {
$(event.data.fake_input).focus();
});
-
+
$(data.fake_input).bind('focus',data,function(event) {
- if ($(event.data.fake_input).val()==$(event.data.fake_input).attr('data-default')) {
+ if ($(event.data.fake_input).val()==$(event.data.fake_input).attr('data-default')) {
$(event.data.fake_input).val('');
}
- $(event.data.fake_input).css('color','#000000');
+ $(event.data.fake_input).css('color','#000000');
});
-
+
if (settings.autocomplete_url != undefined) {
autocomplete_options = {source: settings.autocomplete_url};
- for (attrname in settings.autocomplete) {
- autocomplete_options[attrname] = settings.autocomplete[attrname];
+ for (attrname in settings.autocomplete) {
+ autocomplete_options[attrname] = settings.autocomplete[attrname];
}
-
+
if (jQuery.Autocompleter !== undefined) {
onSelectCallback = settings.autocomplete.onItemSelect;
settings.autocomplete.onItemSelect = function() {
@@ -278,18 +284,18 @@
$(data.fake_input).autocomplete(autocomplete_options);
$(data.fake_input).bind('autocompleteselect',data,function(event,ui) {
$(event.data.real_input).addTag(ui.item.value,{focus:true,unique:(settings.unique)});
-
+
return false;
});
}
-
-
+
+
} else {
// if a user tabs out of the field, create a new tag
// this is only available if autocomplete is not used.
- $(data.fake_input).bind('blur',data,function(event) {
+ $(data.fake_input).bind('blur',data,function(event) {
var d = $(this).attr('data-default');
- if ($(event.data.fake_input).val()!='' && $(event.data.fake_input).val()!=d) {
+ if ($(event.data.fake_input).val()!='' && $(event.data.fake_input).val()!=d) {
if( (event.data.minChars <= $(event.data.fake_input).val().length) && (!event.data.maxChars || (event.data.maxChars >= $(event.data.fake_input).val().length)) )
$(event.data.real_input).addTag($(event.data.fake_input).val(),{focus:true,unique:(settings.unique)});
} else {
@@ -298,7 +304,7 @@
}
return false;
});
-
+
}
// if user types a comma, create a new tag
$(data.fake_input).bind('keypress',data,function(event) {
@@ -326,7 +332,7 @@
}
});
$(data.fake_input).blur();
-
+
//Removes the not_valid class when user changes the value of the fake input
if(data.unique) {
$(data.fake_input).keydown(function(event){
@@ -337,21 +343,21 @@
}
} // if settings.interactive
});
-
+
return this;
-
+
};
-
- $.fn.tagsInput.updateTagsField = function(obj,tagslist) {
+
+ $.fn.tagsInput.updateTagsField = function(obj,tagslist) {
var id = $(obj).attr('id');
$(obj).val(tagslist.join(delimiter[id]));
};
-
- $.fn.tagsInput.importTags = function(obj,val) {
+
+ $.fn.tagsInput.importTags = function(obj,val) {
$(obj).val('');
var id = $(obj).attr('id');
var tags = val.split(delimiter[id]);
- for (i=0; i
diff --git a/lms/templates/discussion/_inline_new_post.html b/lms/templates/discussion/_inline_new_post.html
new file mode 100644
index 0000000000..7bc17d4467
--- /dev/null
+++ b/lms/templates/discussion/_inline_new_post.html
@@ -0,0 +1,29 @@
+
+
+
diff --git a/lms/templates/discussion/_new_post.html b/lms/templates/discussion/_new_post.html
index be09f61c79..e2ace9413c 100644
--- a/lms/templates/discussion/_new_post.html
+++ b/lms/templates/discussion/_new_post.html
@@ -22,9 +22,7 @@
%def>
-
-
-
+
+
diff --git a/lms/templates/discussion/index.html b/lms/templates/discussion/index.html
index 40284638f3..53cbe2d9ed 100644
--- a/lms/templates/discussion/index.html
+++ b/lms/templates/discussion/index.html
@@ -27,7 +27,7 @@
-
+
${course.title} discussions
diff --git a/lms/templates/discussion/mustache/_inline_discussion.mustache b/lms/templates/discussion/mustache/_inline_discussion.mustache
index f82b3810e5..8d55f9949b 100644
--- a/lms/templates/discussion/mustache/_inline_discussion.mustache
+++ b/lms/templates/discussion/mustache/_inline_discussion.mustache
@@ -1,6 +1,39 @@
-
- {{#threads}}
-
+
+ New Post
+
+
+
+
- {{/threads}}
+
+
+ {{#threads}}
+
+
+ {{/threads}}
+
diff --git a/lms/templates/discussion/mustache/_inline_thread.mustache b/lms/templates/discussion/mustache/_inline_thread.mustache
index cd33f2130e..b45f3ff47b 100644
--- a/lms/templates/discussion/mustache/_inline_thread.mustache
+++ b/lms/templates/discussion/mustache/_inline_thread.mustache
@@ -1,21 +1,27 @@
-
+
-
- {{abbreviatedBody}}
-
+
{{abbreviatedBody}}
+