Commit 36b35245 by Arjun Singh

Merge branch 'feature/tomg/new-discussions' of github.com:MITx/mitx into…

Merge branch 'feature/tomg/new-discussions' of github.com:MITx/mitx into feature/tomg/new-discussions
parents 242f523d a40c8fad
...@@ -38,11 +38,10 @@ def permitted(fn): ...@@ -38,11 +38,10 @@ def permitted(fn):
else: else:
content = None content = None
return content return content
if check_permissions_by_view(request.user, kwargs['course_id'], fetch_content(), request.view_name): if check_permissions_by_view(request.user, kwargs['course_id'], fetch_content(), request.view_name):
return fn(request, *args, **kwargs) return fn(request, *args, **kwargs)
else: else:
return JsonError("unauthorized") return JsonError("unauthorized", status=401)
return wrapper return wrapper
def ajax_content_response(request, course_id, content, template_name): def ajax_content_response(request, course_id, content, template_name):
......
...@@ -136,10 +136,16 @@ def inline_discussion(request, course_id, discussion_id): ...@@ -136,10 +136,16 @@ def inline_discussion(request, course_id, discussion_id):
# html = render_inline_discussion(request, course_id, threads, discussion_id=discussion_id, \ # html = render_inline_discussion(request, course_id, threads, discussion_id=discussion_id, \
# query_params=query_params) # query_params=query_params)
user_info = cc.User.from_django_user(request.user).to_dict() 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({ return utils.JsonResponse({
# 'html': html, # 'html': html,
'discussion_data': map(utils.safe_content, threads), 'discussion_data': map(utils.safe_content, threads),
'user_info': user_info, 'user_info': user_info,
'annotated_content_info': annotated_content_info
}) })
def render_search_bar(request, course_id, discussion_id=None, text=''): def render_search_bar(request, course_id, discussion_id=None, text=''):
......
...@@ -149,14 +149,14 @@ class JsonResponse(HttpResponse): ...@@ -149,14 +149,14 @@ class JsonResponse(HttpResponse):
mimetype='application/json; charset=utf8') mimetype='application/json; charset=utf8')
class JsonError(HttpResponse): class JsonError(HttpResponse):
def __init__(self, error_messages=[]): def __init__(self, error_messages=[], status=400):
if isinstance(error_messages, str): if isinstance(error_messages, str):
error_messages = [error_messages] error_messages = [error_messages]
content = simplejson.dumps({'errors': error_messages}, content = simplejson.dumps({'errors': error_messages},
indent=2, indent=2,
ensure_ascii=False) ensure_ascii=False)
super(JsonError, self).__init__(content, super(JsonError, self).__init__(content,
mimetype='application/json; charset=utf8', status=400) mimetype='application/json; charset=utf8', status=status)
class HtmlResponse(HttpResponse): class HtmlResponse(HttpResponse):
def __init__(self, html=''): def __init__(self, html=''):
......
...@@ -12,6 +12,7 @@ class Thread(models.Model): ...@@ -12,6 +12,7 @@ class Thread(models.Model):
'created_at', 'updated_at', 'comments_count', 'created_at', 'updated_at', 'comments_count',
'at_position_list', 'children', 'type', 'at_position_list', 'children', 'type',
'highlighted_title', 'highlighted_body', 'highlighted_title', 'highlighted_body',
'endorsed'
] ]
updatable_fields = [ updatable_fields = [
......
...@@ -7,7 +7,6 @@ if Backbone? ...@@ -7,7 +7,6 @@ if Backbone?
item.discussion = @ item.discussion = @
@comparator = @sortByDateRecentFirst @comparator = @sortByDateRecentFirst
@on "thread:remove", (thread) => @on "thread:remove", (thread) =>
console.log "remove triggered"
@remove(thread) @remove(thread)
find: (id) -> find: (id) ->
......
...@@ -2,6 +2,18 @@ if Backbone? ...@@ -2,6 +2,18 @@ if Backbone?
class @DiscussionModuleView extends Backbone.View class @DiscussionModuleView extends Backbone.View
events: events:
"click .discussion-show": "toggleDiscussion" "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) -> toggleDiscussion: (event) ->
if @showed if @showed
@$("section.discussion").hide() @$("section.discussion").hide()
...@@ -14,26 +26,39 @@ if Backbone? ...@@ -14,26 +26,39 @@ if Backbone?
@showed = true @showed = true
else else
$elem = $(event.target) $elem = $(event.target)
discussion_id = $elem.attr("discussion_id") discussionId = $elem.data("discussion-id")
url = DiscussionUtil.urlFor 'retrieve_discussion', discussion_id url = DiscussionUtil.urlFor 'retrieve_discussion', discussionId
DiscussionUtil.safeAjax DiscussionUtil.safeAjax
$elem: $elem $elem: $elem
$loading: $elem $loading: $elem
url: url url: url
type: "GET" type: "GET"
dataType: 'json' dataType: 'json'
success: (response, textStatus) => success: (response, textStatus, jqXHR) => @createDiscussion(event, response, textStatus, discussionId)
#@$el.append(response.html)
createDiscussion: (event, response, textStatus, discussionId) =>
window.user = new DiscussionUser(response.user_info) window.user = new DiscussionUser(response.user_info)
Content.loadContentInfos(response.annotated_content_info)
$(event.target).html("Hide Discussion") $(event.target).html("Hide Discussion")
discussion = new Discussion() @discussion = new Discussion()
discussion.reset(response.discussion_data, {silent: false}) @discussion.reset(response.discussion_data, {silent: false})
$discussion = $(Mustache.render $("script#_inline_discussion").html(), {'threads':response.discussion_data}) $discussion = $(Mustache.render $("script#_inline_discussion").html(), {'threads':response.discussion_data, 'discussionId': discussionId})
$(".discussion-module").append($discussion) $(".discussion-module").append($discussion)
discussion.each (thread) -> @newPostForm = $('.new-post-article')
element = $("article#thread_#{thread.id}") @threadviews = @discussion.map (thread) ->
dtv = new DiscussionThreadInlineView el: element, model: thread new DiscussionThreadInlineView el: @$("article#thread_#{thread.id}"), model: thread
dtv.render() _.each @threadviews, (dtv) -> dtv.render()
DiscussionUtil.bulkUpdateContentInfo(window.$$annotated_content_info) DiscussionUtil.bulkUpdateContentInfo(window.$$annotated_content_info)
@newPostView = new NewPostInlineView el: @$('.new-post-article'), collection: @discussion
@discussion.on "add", @addThread
@retrieved = true @retrieved = true
@showed = true @showed = true
addThread: (thread, collection, options) =>
# TODO: When doing pagination, this will need to repaginate
article = $("<article class='discussion-thread' id='thread_#{thread.id}'></article>")
@$('section.discussion > .threads').prepend(article)
threadView = new DiscussionThreadInlineView el: article, model: thread
threadView.render()
@threadviews.unshift threadView
...@@ -3,7 +3,7 @@ $ -> ...@@ -3,7 +3,7 @@ $ ->
window.$$contents = {} window.$$contents = {}
$.fn.extend $.fn.extend
loading: -> loading: ->
@$_loading = $("<span class='discussion-loading'></span>") @$_loading = $("<div class='loading-animation'></div>")
$(this).after(@$_loading) $(this).after(@$_loading)
loaded: -> loaded: ->
@$_loading.remove() @$_loading.remove()
...@@ -107,6 +107,9 @@ class @DiscussionUtil ...@@ -107,6 +107,9 @@ class @DiscussionUtil
[event, selector] = eventSelector.split(' ') [event, selector] = eventSelector.split(' ')
$local(selector).unbind(event)[event] handler $local(selector).unbind(event)[event] handler
@processTag: (text) ->
text.toLowerCase()
@tagsInputOptions: -> @tagsInputOptions: ->
autocomplete_url: @urlFor('tags_autocomplete') autocomplete_url: @urlFor('tags_autocomplete')
autocomplete: autocomplete:
...@@ -116,6 +119,7 @@ class @DiscussionUtil ...@@ -116,6 +119,7 @@ class @DiscussionUtil
width: '100%' width: '100%'
defaultText: "Tag your post: press enter after each tag" defaultText: "Tag your post: press enter after each tag"
removeWithBackspace: true removeWithBackspace: true
preprocessTag: @processTag
@formErrorHandler: (errorsField) -> @formErrorHandler: (errorsField) ->
(xhr, textStatus, error) -> (xhr, textStatus, error) ->
...@@ -123,7 +127,7 @@ class @DiscussionUtil ...@@ -123,7 +127,7 @@ class @DiscussionUtil
if response.errors? and response.errors.length > 0 if response.errors? and response.errors.length > 0
errorsField.empty() errorsField.empty()
for error in response.errors for error in response.errors
errorsField.append($("<li>").addClass("new-post-form-error").html(error)) errorsField.append($("<li>").addClass("new-post-form-error").html(error)).show()
@clearFormErrors: (errorsField) -> @clearFormErrors: (errorsField) ->
errorsField.empty() errorsField.empty()
......
...@@ -21,10 +21,6 @@ class @DiscussionThreadInlineView extends DiscussionContentView ...@@ -21,10 +21,6 @@ class @DiscussionThreadInlineView extends DiscussionContentView
@model.on "change", @updateModelDetails @model.on "change", @updateModelDetails
render: -> render: ->
#TODO: Debugging, remove when done
if not window.$disc
window.$disc = []
window.$disc.push(@)
if not @model.has('abbreviatedBody') if not @model.has('abbreviatedBody')
@abbreviateBody() @abbreviateBody()
@$el.html(Mustache.render(@template(), $.extend(@model.toJSON(),{expanded: @expanded}) )) @$el.html(Mustache.render(@template(), $.extend(@model.toJSON(),{expanded: @expanded}) ))
...@@ -38,8 +34,6 @@ class @DiscussionThreadInlineView extends DiscussionContentView ...@@ -38,8 +34,6 @@ class @DiscussionThreadInlineView extends DiscussionContentView
if @expanded if @expanded
@makeWmdEditor "reply-body" @makeWmdEditor "reply-body"
@renderResponses() @renderResponses()
# @highlight @$(".post-body")
# @highlight @$("h1")
@ @
renderDogear: -> renderDogear: ->
...@@ -58,12 +52,13 @@ class @DiscussionThreadInlineView extends DiscussionContentView ...@@ -58,12 +52,13 @@ class @DiscussionThreadInlineView extends DiscussionContentView
convertMath: -> convertMath: ->
element = @$(".post-body") 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]] MathJax.Hub.Queue ["Typeset", MathJax.Hub, element[0]]
renderResponses: -> renderResponses: ->
DiscussionUtil.safeAjax DiscussionUtil.safeAjax
url: "/courses/#{$$course_id}/discussion/forum/#{@model.get('commentable_id')}/threads/#{@model.id}" url: "/courses/#{$$course_id}/discussion/forum/#{@model.get('commentable_id')}/threads/#{@model.id}"
$loading: @$el
success: (data, textStatus, xhr) => success: (data, textStatus, xhr) =>
@$el.find(".loading").remove() @$el.find(".loading").remove()
Content.loadContentInfos(data['annotated_content_info']) Content.loadContentInfos(data['annotated_content_info'])
...@@ -192,16 +187,14 @@ class @DiscussionThreadInlineView extends DiscussionContentView ...@@ -192,16 +187,14 @@ class @DiscussionThreadInlineView extends DiscussionContentView
success: (response, textStatus) => success: (response, textStatus) =>
@model.set('endorsed', not endorsed) @model.set('endorsed', not endorsed)
highlight: (el) ->
el.html(el.html().replace(/&lt;mark&gt;/g, "<mark>").replace(/&lt;\/mark&gt;/g, "</mark>"))
abbreviateBody: -> abbreviateBody: ->
abbreviated = DiscussionUtil.abbreviateString @model.get('body'), 140 # Because twitter abbreviated = DiscussionUtil.abbreviateString @model.get('body'), 140
@model.set('abbreviatedBody', abbreviated) @model.set('abbreviatedBody', abbreviated)
expandPost: (event) -> expandPost: (event) ->
@expanded = true @expanded = true
@$el.find('.post-body').html(@model.get('body')) @$el.find('.post-body').html(@model.get('body'))
@convertMath()
@$el.find('.expand-post').hide() @$el.find('.expand-post').hide()
@$el.find('.collapse-post').show() @$el.find('.collapse-post').show()
@$el.find('.post-extended-content').show() @$el.find('.post-extended-content').show()
...@@ -212,6 +205,7 @@ class @DiscussionThreadInlineView extends DiscussionContentView ...@@ -212,6 +205,7 @@ class @DiscussionThreadInlineView extends DiscussionContentView
collapsePost: (event) -> collapsePost: (event) ->
@expanded = false @expanded = false
@$el.find('.post-body').html(@model.get('abbreviatedBody')) @$el.find('.post-body').html(@model.get('abbreviatedBody'))
@convertMath()
@$el.find('.collapse-post').hide() @$el.find('.collapse-post').hide()
@$el.find('.post-extended-content').hide() @$el.find('.post-extended-content').hide()
@$el.find('.expand-post').show() @$el.find('.expand-post').show()
...@@ -15,6 +15,7 @@ class @DiscussionThreadListView extends Backbone.View ...@@ -15,6 +15,7 @@ class @DiscussionThreadListView extends Backbone.View
@collection.on "add", @addAndSelectThread @collection.on "add", @addAndSelectThread
@sidebar_padding = 10 @sidebar_padding = 10
@sidebar_header_height = 87 @sidebar_header_height = 87
@boardName
reloadDisplayedCollection: (thread) => reloadDisplayedCollection: (thread) =>
thread_id = thread.get('id') thread_id = thread.get('id')
...@@ -41,8 +42,8 @@ class @DiscussionThreadListView extends Backbone.View ...@@ -41,8 +42,8 @@ class @DiscussionThreadListView extends Backbone.View
windowHeight = $(window).height(); windowHeight = $(window).height();
discussionBody = $(".discussion-article") discussionBody = $(".discussion-article")
discussionsBodyTop = if discussionBody[0] then discussionBody.offset().top; discussionsBodyTop = if discussionBody[0] then discussionBody.offset().top
discussionsBodyBottom = discussionsBodyTop + discussionBody.outerHeight(); discussionsBodyBottom = discussionsBodyTop + discussionBody.outerHeight()
sidebar = $(".sidebar") sidebar = $(".sidebar")
if scrollTop > discussionsBodyTop - @sidebar_padding if scrollTop > discussionsBodyTop - @sidebar_padding
...@@ -62,10 +63,11 @@ class @DiscussionThreadListView extends Backbone.View ...@@ -62,10 +63,11 @@ class @DiscussionThreadListView extends Backbone.View
amount = Math.max(topOffset - discussionBottomOffset, 0) amount = Math.max(topOffset - discussionBottomOffset, 0)
sidebarHeight = sidebarHeight - @sidebar_padding - amount 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 = @$('.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 # Because we want the behavior that when the body is clicked the menu is
...@@ -101,6 +103,8 @@ class @DiscussionThreadListView extends Backbone.View ...@@ -101,6 +103,8 @@ class @DiscussionThreadListView extends Backbone.View
content = $(_.template($("#thread-list-item-template").html())(thread.toJSON())) content = $(_.template($("#thread-list-item-template").html())(thread.toJSON()))
if thread.get('subscribed') if thread.get('subscribed')
content.addClass("followed") content.addClass("followed")
if thread.get('endorsed')
content.addClass("resolved")
@highlight(content) @highlight(content)
...@@ -137,28 +141,66 @@ class @DiscussionThreadListView extends Backbone.View ...@@ -137,28 +141,66 @@ class @DiscussionThreadListView extends Backbone.View
@$(".browse").toggleClass('is-dropped') @$(".browse").toggleClass('is-dropped')
if @$(".browse").hasClass('is-dropped') if @$(".browse").hasClass('is-dropped')
@$(".browse-topic-drop-menu-wrapper").show() @$(".browse-topic-drop-menu-wrapper").show()
$('body').bind 'click', @toggleTopicDrop $(".browse-topic-drop-search-input").focus()
$('body').bind 'keydown', @setActiveItem $("body").bind "click", @toggleTopicDrop
$("body").bind "keydown", @setActiveItem
else else
@$(".browse-topic-drop-menu-wrapper").hide() @$(".browse-topic-drop-menu-wrapper").hide()
$('body').unbind 'click', @toggleTopicDrop $("body").unbind "click", @toggleTopicDrop
$('body').unbind 'keydown', @setActiveItem $("body").unbind "keydown", @setActiveItem
setTopic: (event) -> setTopic: (event) ->
item = $(event.target).closest('a') item = $(event.target).closest('a')
boardName = item.find(".board-name").html() boardName = item.find(".board-name").html()
_.each item.parents('ul').not('.browse-topic-drop-menu'), (parent) -> _.each item.parents('ul').not('.browse-topic-drop-menu'), (parent) ->
boardName = $(parent).siblings('a').find('.board-name').html() + ' / ' + boardName boardName = $(parent).siblings('a').find('.board-name').html() + ' / ' + boardName
@$(".current-board").html(boardName) @$(".current-board").html(@fitName(boardName))
fontSize = 16 fontSize = 16
@$(".current-board").css('font-size', '16px') @$(".current-board").css('font-size', '16px')
while @$(".current-board").width() > (@$el.width() * .8) - 40 while @$(".current-board").width() > (@$el.width() * .8) - 40
fontSize-- fontSize--
if fontSize < 11 if fontSize < 11
break break
@$(".current-board").css('font-size', fontSize + 'px') @$(".current-board").css('font-size', fontSize + 'px')
setSelectedTopic: (name) ->
@$(".current-board").html(@fitName(name))
getNameWidth: (name) ->
test = $("<div>")
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) -> filterTopic: (event) ->
@setTopic(event) @setTopic(event)
item = $(event.target).closest('li') item = $(event.target).closest('li')
......
...@@ -49,7 +49,7 @@ class @DiscussionThreadView extends DiscussionContentView ...@@ -49,7 +49,7 @@ class @DiscussionThreadView extends DiscussionContentView
convertMath: -> convertMath: ->
element = @$(".post-body") 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]] MathJax.Hub.Queue ["Typeset", MathJax.Hub, element[0]]
renderResponses: -> renderResponses: ->
...@@ -66,12 +66,17 @@ class @DiscussionThreadView extends DiscussionContentView ...@@ -66,12 +66,17 @@ class @DiscussionThreadView extends DiscussionContentView
response.set('thread', @model) response.set('thread', @model)
view = new ThreadResponseView(model: response) view = new ThreadResponseView(model: response)
view.on "comment:add", @addComment view.on "comment:add", @addComment
view.on "comment:endorse", @endorseThread
view.render() view.render()
@$el.find(".responses").append(view.el) @$el.find(".responses").append(view.el)
addComment: => addComment: =>
@model.comment() @model.comment()
endorseThread: (endorsed) =>
is_endorsed = @$el.find(".is-endorsed").length
@model.set 'endorsed', is_endorsed
toggleVote: (event) -> toggleVote: (event) ->
event.preventDefault() event.preventDefault()
if window.user.voted(@model) if window.user.voted(@model)
......
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
...@@ -38,9 +38,10 @@ class @NewPostView extends Backbone.View ...@@ -38,9 +38,10 @@ class @NewPostView extends Backbone.View
@menuOpen = true @menuOpen = true
@dropdownButton.addClass('dropped') @dropdownButton.addClass('dropped')
@topicMenu.show() @topicMenu.show()
$(".form-topic-drop-search-input").focus()
$('body').bind 'keydown', @setActiveItem $("body").bind "keydown", @setActiveItem
$('body').bind 'click', @hideTopicDropdown $("body").bind "click", @hideTopicDropdown
# Set here because 1) the window might get resized and things could # 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 # change and 2) can't set in initialize because the button is hidden
...@@ -52,8 +53,8 @@ class @NewPostView extends Backbone.View ...@@ -52,8 +53,8 @@ class @NewPostView extends Backbone.View
@dropdownButton.removeClass('dropped') @dropdownButton.removeClass('dropped')
@topicMenu.hide() @topicMenu.hide()
$('body').unbind 'keydown', @setActiveItem $("body").unbind "keydown", @setActiveItem
$('body').unbind 'click', @hideTopicDropdown $("body").unbind "click", @hideTopicDropdown
setTopic: (event) -> setTopic: (event) ->
$target = $(event.target) $target = $(event.target)
...@@ -142,7 +143,7 @@ class @NewPostView extends Backbone.View ...@@ -142,7 +143,7 @@ class @NewPostView extends Backbone.View
DiscussionUtil.clearFormErrors(@$(".new-post-form-errors")) DiscussionUtil.clearFormErrors(@$(".new-post-form-errors"))
@$el.hide() @$el.hide()
@$(".new-post-title").val("").attr("prev-text", "") @$(".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").val("")
@$(".new-post-tags").importTags("") @$(".new-post-tags").importTags("")
@collection.add thread @collection.add thread
......
...@@ -2,6 +2,7 @@ class @ResponseCommentView extends DiscussionContentView ...@@ -2,6 +2,7 @@ class @ResponseCommentView extends DiscussionContentView
tagName: "li" tagName: "li"
template: _.template($("#response-comment-template").html()) template: _.template($("#response-comment-template").html())
initLocal: -> 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") @$local = @$el.find(".response-local")
@$delegateElement = @$local @$delegateElement = @$local
...@@ -14,8 +15,9 @@ class @ResponseCommentView extends DiscussionContentView ...@@ -14,8 +15,9 @@ class @ResponseCommentView extends DiscussionContentView
@convertMath() @convertMath()
@ @
convertMath: -> convertMath: ->
body = @$(".response-body") body = @$el.find(".response-body")
body.html DiscussionUtil.postMathJaxProcessor DiscussionUtil.markdownWithHighlight body.html() body.html DiscussionUtil.postMathJaxProcessor DiscussionUtil.markdownWithHighlight body.html()
# This removes paragraphs so that comments are more compact
body.children("p").each (index, elem) -> body.children("p").each (index, elem) ->
$(elem).replaceWith($(elem).html()) $(elem).replaceWith($(elem).html())
MathJax.Hub.Queue ["Typeset", MathJax.Hub, body[0]] MathJax.Hub.Queue ["Typeset", MathJax.Hub, body[0]]
...@@ -109,6 +109,7 @@ class @ThreadResponseView extends DiscussionContentView ...@@ -109,6 +109,7 @@ class @ThreadResponseView extends DiscussionContentView
endorsed = @model.get('endorsed') endorsed = @model.get('endorsed')
data = { endorsed: not endorsed } data = { endorsed: not endorsed }
@model.set('endorsed', not endorsed) @model.set('endorsed', not endorsed)
@trigger "comment:endorse", not endorsed
DiscussionUtil.safeAjax DiscussionUtil.safeAjax
$elem: $elem $elem: $elem
url: url url: url
......
...@@ -58,7 +58,7 @@ $(document).ready(function() { ...@@ -58,7 +58,7 @@ $(document).ready(function() {
$('.new-post-btn').bind('click', newPost); $('.new-post-btn').bind('click', newPost);
$('.new-post-cancel').bind('click', closeNewPost); $('.new-post-cancel').bind('click', closeNewPost);
$('[data-tooltip]').bind({ $body.delegate('[data-tooltip]', {
'mouseover': showTooltip, 'mouseover': showTooltip,
'mousemove': moveTooltip, 'mousemove': moveTooltip,
'mouseout': hideTooltip, 'mouseout': hideTooltip,
...@@ -66,14 +66,6 @@ $(document).ready(function() { ...@@ -66,14 +66,6 @@ $(document).ready(function() {
}); });
$body.delegate('.browse-topic-drop-search-input, .form-topic-drop-search-input', 'keyup', filterDrop); $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) { function filterDrop(e) {
...@@ -276,6 +268,7 @@ function setTopic(e) { ...@@ -276,6 +268,7 @@ function setTopic(e) {
function newPost(e) { function newPost(e) {
$newPost.slideDown(300); $newPost.slideDown(300);
$('.new-post-title').focus();
} }
function closeNewPost(e) { function closeNewPost(e) {
......
...@@ -85,6 +85,11 @@ ...@@ -85,6 +85,11 @@
value = jQuery.trim(value); 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) { if (options.unique) {
var skipTag = $(this).tagExist(value); var skipTag = $(this).tagExist(value);
if(skipTag == true) { if(skipTag == true) {
...@@ -211,11 +216,12 @@ ...@@ -211,11 +216,12 @@
delimiter[id] = data.delimiter; 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] = new Array();
tags_callbacks[id]['onAddTag'] = settings.onAddTag; tags_callbacks[id]['onAddTag'] = settings.onAddTag;
tags_callbacks[id]['onRemoveTag'] = settings.onRemoveTag; tags_callbacks[id]['onRemoveTag'] = settings.onRemoveTag;
tags_callbacks[id]['onChange'] = settings.onChange; tags_callbacks[id]['onChange'] = settings.onChange;
tags_callbacks[id]['preprocessTag'] = settings.preprocessTag;
} }
var markup = '<div id="'+id+'_tagsinput" class="tagsinput"><div id="'+id+'_addTag">'; var markup = '<div id="'+id+'_tagsinput" class="tagsinput"><div id="'+id+'_addTag">';
......
...@@ -211,6 +211,28 @@ ...@@ -211,6 +211,28 @@
body.discussion { body.discussion {
.new-post-form-errors {
display: none;
background: $error-red;
padding: 0;
border: 1px solid #333;
list-style: none;
color: #fff;
line-height: 1.6;
border-radius: 3px;
@include box-shadow(0 1px 2px rgba(0, 0, 0, 0.3) inset, 0 1px 0 rgba(255, 255, 255, .2));
li {
padding: 10px 20px 12px 45px;
border-bottom: 1px solid #dc4949;
background: url(../images/white-error-icon.png) no-repeat 15px 14px;
&:last-child {
border-bottom: none;
}
}
}
.course-tabs .right { .course-tabs .right {
float: right; float: right;
...@@ -393,6 +415,22 @@ body.discussion { ...@@ -393,6 +415,22 @@ body.discussion {
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3) inset; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3) inset;
} }
.tagsinput {
padding: 10px;
box-sizing: border-box;
border: 1px solid #333;
border-radius: 3px;
background: #fff;
font-family: 'Monaco', monospace;
font-size: 13px;
line-height: 1.6;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3) inset;
span.tag {
margin-bottom: 0;
}
}
.new-post-body .wmd-preview { .new-post-body .wmd-preview {
@include discussion-wmd-preview; @include discussion-wmd-preview;
position: relative; position: relative;
...@@ -713,7 +751,9 @@ body.discussion { ...@@ -713,7 +751,9 @@ body.discussion {
width: 100%; width: 100%;
background: #737373; background: #737373;
border: 1px solid #4b4b4b; border: 1px solid #4b4b4b;
border-left: none;
border-radius: 0 0 3px 3px; border-radius: 0 0 3px 3px;
@include box-shadow(1px 0 0 #4b4b4b inset);
.browse-topic-drop-menu { .browse-topic-drop-menu {
max-height: 400px; max-height: 400px;
...@@ -906,7 +946,7 @@ body.discussion { ...@@ -906,7 +946,7 @@ body.discussion {
position: relative; position: relative;
display: block; display: block;
height: 36px; height: 36px;
padding: 0 10px; padding: 0 10px 0 18px;
margin-bottom: 1px; margin-bottom: 1px;
margin-right: -1px; margin-right: -1px;
@include linear-gradient(top, rgba(255, 255, 255, .7), rgba(255, 255, 255, 0)); @include linear-gradient(top, rgba(255, 255, 255, .7), rgba(255, 255, 255, 0));
...@@ -917,6 +957,35 @@ body.discussion { ...@@ -917,6 +957,35 @@ body.discussion {
background-color: #eee; background-color: #eee;
} }
&.staff-post.staff-response {
.staff-post-icon {
top: 5px;
}
.staff-response-icon {
top: 18px;
}
}
.staff-post-icon,
.staff-response-icon {
position: absolute;
top: 11px;
left: 3px;
width: 13px;
height: 13px;
background: url(../images/staff-icons.png) no-repeat;
}
.staff-post-icon {
left: 2px;
background-position: 0 0;
}
.staff-response-icon {
background-position: -13px 0;
}
.title { .title {
font-size: 13px; font-size: 13px;
font-weight: 700; font-weight: 700;
...@@ -958,6 +1027,14 @@ body.discussion { ...@@ -958,6 +1027,14 @@ body.discussion {
color: #333; color: #333;
} }
.staff-post-icon {
background-position: 0 -13px;
}
.staff-response-icon {
background-position: -13px -13px;
}
.votes-count, .votes-count,
.comments-count { .comments-count {
@include linear-gradient(top, #3994c7, #4da7d3); @include linear-gradient(top, #3994c7, #4da7d3);
...@@ -1363,38 +1440,6 @@ body.discussion { ...@@ -1363,38 +1440,6 @@ body.discussion {
.tooltip {
position: absolute;
top: 0;
left: 0;
z-index: 99999;
padding: 0 10px;
border-radius: 3px;
background: rgba(0, 0, 0, .85);
font-size: 11px;
font-weight: 400;
line-height: 26px;
color: #fff;
pointer-events: none;
opacity: 0;
@include transition(opacity .1s);
&:after {
content: '▾';
display: block;
position: absolute;
bottom: -14px;
left: 50%;
margin-left: -7px;
font-size: 20px;
color: rgba(0, 0, 0, .85);
}
}
.main-article.new { .main-article.new {
display: none; display: none;
padding: 50px; padding: 50px;
...@@ -1472,14 +1517,10 @@ body.discussion { ...@@ -1472,14 +1517,10 @@ body.discussion {
} }
.discussion-module { .discussion-module {
@extend .discussion-body @extend .discussion-body;
}
/* For some reason I have to do this to get the SCSS to compile, can't stick it under the above .discussion-module */
.discussion-module {
section.discussion { section.discussion {
/* Course content p has a default margin-bottom of 1.416, this is just to reset that */ /* Course content p has a default margin-bottom of 1.416em, this is just to reset that */
.discussion-thread { .discussion-thread {
padding: 0.5em; padding: 0.5em;
...@@ -1535,5 +1576,161 @@ body.discussion { ...@@ -1535,5 +1576,161 @@ body.discussion {
} }
} }
.new-post-article {
display: none;
margin-top: 20px;
.inner-wrapper {
max-width: 1180px;
min-width: 760px;
margin: auto;
}
.new-post-form {
width: 100%;
margin-bottom: 20px;
border-radius: 3px;
background: rgba(0, 0, 0, .55);
color: #fff;
box-shadow: 0 1px 2px rgba(0, 0, 0, .5) inset, 0 1px 0 rgba(255, 255, 255, .5);
@include clearfix;
.form-row {
margin-bottom: 20px;
}
.new-post-body .wmd-input {
@include discussion-wmd-input;
position: relative;
width: 100%;
height: 200px;
z-index: 1;
padding: 10px;
box-sizing: border-box;
border: 1px solid #333;
border-radius: 3px 3px 0 0;
background: #fff;
font-family: 'Monaco', monospace;
font-size: 13px;
line-height: 1.6;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3) inset;
}
.new-post-body .wmd-preview {
@include discussion-wmd-preview;
position: relative;
width: 100%;
//height: 50px;
margin-top: -1px;
padding: 25px 20px 10px 20px;
box-sizing: border-box;
border: 1px solid #333;
border-radius: 0 0 3px 3px;
background: #e6e6e6;
color: #333;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3) inset;
}
.new-post-preview-label {
position: absolute;
top: 4px;
left: 4px;
font-size: 11px;
color: #aaa;
text-transform: uppercase;
}
.new-post-title,
.new-post-tags {
width: 100%;
height: 40px;
padding: 0 10px;
box-sizing: border-box;
border-radius: 3px;
border: 1px solid #333;
font-size: 16px;
font-family: 'Open Sans', sans-serif;
color: #333;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3) inset;
}
.new-post-title {
font-weight: 700;
}
.submit {
@include blue-button;
float: left;
height: 37px;
margin-top: 10px;
padding-bottom: 2px;
border-color: #333;
&:hover {
border-color: #222;
}
}
.new-post-cancel {
@include white-button;
float: left;
margin: 10px 0 0 15px;
}
.options {
margin-top: 40px;
label {
display: inline;
margin-left: 8px;
font-size: 15px;
color: #fff;
text-shadow: none;
}
}
.wmd-button {
background: none;
}
.wmd-button span {
background: url(../images/new-post-icons-full.png) no-repeat;
}
}
.thread-tags {
margin-top: 20px;
}
.thread-tag {
padding: 3px 10px 6px;
border-radius: 3px;
color: #333;
background: #c5eeff;
border: 1px solid #90c4d7;
font-size: 13px;
}
.thread-title {
display: block;
margin-bottom: 20px;
font-size: 21px;
color: #333;
font-weight: 700;
}
}
.new-post-btn {
@include blue-button;
font-size: 13px;
margin-right: 4px;
}
.new-post-icon {
display: block;
float: left;
width: 16px;
height: 17px;
margin: 8px 7px 0 0;
background: url(../images/new-post-icon.png) no-repeat;
}
} }
...@@ -102,3 +102,32 @@ img { ...@@ -102,3 +102,32 @@ img {
background: #444; background: #444;
color: #fff; color: #fff;
} }
.tooltip {
position: absolute;
top: 0;
left: 0;
z-index: 99999;
padding: 0 10px;
border-radius: 3px;
background: rgba(0, 0, 0, .85);
font-size: 11px;
font-weight: 400;
line-height: 26px;
color: #fff;
pointer-events: none;
opacity: 0;
@include transition(opacity .1s);
&:after {
content: '▾';
display: block;
position: absolute;
bottom: -14px;
left: 50%;
margin-left: -7px;
font-size: 20px;
color: rgba(0, 0, 0, .85);
}
}
\ No newline at end of file
<%include file="_underscore_templates.html" /> <%include file="_underscore_templates.html" />
<div class="discussion-module"> <div class="discussion-module">
<a class="discussion-show control-button" href="javascript:void(0)" discussion_id="${discussion_id | h}">Show Discussion</a> <a class="discussion-show control-button" href="javascript:void(0)" data-discussion-id="${discussion_id | h}">Show Discussion</a>
</div> </div>
<article class="new-post-article">
<div class="inner-wrapper">
<div class="new-post-form-errors">
</div>
<form class="new-post-form">
<div class="left-column">
<div class="options">
<input type="checkbox" name="follow" class="discussion-follow" class="discussion-follow" id="new-post-follow" checked><label for="new-post-follow">follow this post</label>
<br>
<input type="checkbox" name="anonymous" class="discussion-anonymous" id="new-post-anonymous"><label for="new-post-anonymous">post anonymously</label>
</div>
</div>
<div class="right-column">
<div class="form-row">
<input type="text" class="new-post-title" name="title" placeholder="Title">
</div>
<div class="form-row">
<div class="new-post-body" name="body" placeholder="Enter your question or comment&hellip;"></div>
<!---<div class="new-post-preview"><span class="new-post-preview-label">Preview</span></div>-->
</div>
<div class="form-row">
<input type="text" class="new-post-tags" name="tags" placeholder="Tags">
</div>
<input type="submit" class="submit" value="Add post">
<a href="#" class="new-post-cancel">Cancel</a>
</div>
</form>
</div>
</article>
...@@ -23,8 +23,6 @@ ...@@ -23,8 +23,6 @@
<article class="new-post-article"> <article class="new-post-article">
<div class="inner-wrapper"> <div class="inner-wrapper">
<div class="new-post-form-errors">
</div>
<form class="new-post-form"> <form class="new-post-form">
<div class="left-column"> <div class="left-column">
<label>Create new post about:</label> <label>Create new post about:</label>
...@@ -46,6 +44,7 @@ ...@@ -46,6 +44,7 @@
</div> </div>
</div> </div>
<div class="right-column"> <div class="right-column">
<ul class="new-post-form-errors"></ul>
<div class="form-row"> <div class="form-row">
<input type="text" class="new-post-title" name="title" placeholder="Title"> <input type="text" class="new-post-title" name="title" placeholder="Title">
</div> </div>
......
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
<div class="discussion-body"> <div class="discussion-body">
<div class="sidebar"></div> <div class="sidebar"></div>
<div class="discussion-column"> <div class="discussion-column">
<div class="blank-slate"> <div class="discussion-article blank-slate">
<h1>${course.title} discussions</h1> <h1>${course.title} discussions</h1>
</div> </div>
......
<section class="discussion"> <section class="discussion" data-discussion-id="{{discussionId}}">
<a href="#" class="new-post-btn"><span class="new-post-icon"></span>New Post</a>
<article class="new-post-article">
<span class="topic" data-discussion-id="{{discussionId}}" />
<div class="inner-wrapper">
<div class="new-post-form-errors">
</div>
<form class="new-post-form">
<div class="right-column">
<div class="form-row">
<input type="text" class="new-post-title" name="title" placeholder="Title">
</div>
<div class="form-row">
<div class="new-post-body" name="body" placeholder="Enter your question or comment&hellip;"></div>
<!---<div class="new-post-preview"><span class="new-post-preview-label">Preview</span></div>-->
</div>
<div class="form-row">
<input type="text" class="new-post-tags" name="tags" placeholder="Tags">
</div>
<input type="submit" class="submit" value="Add post">
<a href="#" class="new-post-cancel">Cancel</a>
<div class="options">
<input type="checkbox" name="follow" class="discussion-follow" class="discussion-follow" id="new-post-follow" checked><label for="new-post-follow">follow this post</label>
<br>
<input type="checkbox" name="anonymous" class="discussion-anonymous" id="new-post-anonymous"><label for="new-post-anonymous">post anonymously</label>
</div>
</div>
</form>
</div>
</article>
<section class="threads">
{{#threads}} {{#threads}}
<article class="discussion-thread" id="thread_{{id}}"> <article class="discussion-thread" id="thread_{{id}}">
</article> </article>
{{/threads}} {{/threads}}
</section>
</section> </section>
<article class="discussion-article" data-id="{{id}}"> <article class="discussion-article" data-id="{{id}}">
<div class="local"><a href="#" class="dogear action-follow"></a></div> <div class="local"><a href="javascript:void(0)" class="dogear action-follow"></a></div>
<div class="discussion-post local"> <div class="discussion-post local">
<header> <header>
<a href="#" class="vote-btn discussion-vote discussion-vote-up"><span class="plus-icon">+</span> <span class='votes-count-number'>{{votes.up_count}}</span></a> <a href="#" class="vote-btn discussion-vote discussion-vote-up" data-role="discussion-vote"><span class="plus-icon">+</span> <span class='votes-count-number'>{{votes.up_count}}</span></a>
<h1>{{title}}</h1> <h1>{{title}}</h1>
<p class="posted-details"> <p class="posted-details">
<span class="timeago" title="{{created_at}}">sometime</span> by <span class="timeago" title="{{created_at}}">{{created_at}}</span> by
<a href="{{user_url}}">{{username}}</a> <a href="{{user_url}}">{{username}}</a>
<span class="post-status-closed top-post-status" style="display: none">
&bull; This thread is closed.
</span>
</p> </p>
<div class="local post-tools"> <div class="local post-tools">
<a href="javascript:void(0)" class="expand-post">Expand...</a> <a href="javascript:void(0)" class="expand-post">Expand...</a>
<a href="javascript:void(0)" class="collapse-post">Collapse...</a> <a href="javascript:void(0)" class="collapse-post">Collapse...</a>
</div> </div>
</header> </header>
<div class="post-body"> <div class="post-body">{{abbreviatedBody}}</div>
{{abbreviatedBody}} <ul class="moderator-actions post-extended-content">
</div> <li style="display: none"><a class="action-edit" href="javascript:void(0)"><span class="edit-icon"></span> Edit</a></li>
<li style="display: none"><a class="action-delete" href="javascript:void(0)"><span class="delete-icon"></span> Delete</a></li>
<li style="display: none"><a class="action-openclose" href="javascript:void(0)"><span class="edit-icon"></span> Close</a></li>
</ul>
</div> </div>
<ol class="responses post-extended-content"> <ol class="responses post-extended-content">
<li class="loading"><div class="loading-animation"></div></li> <li class="loading"><div class="loading-animation"></div></li>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment