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):
...@@ -214,7 +213,7 @@ def undo_vote_for_thread(request, course_id, thread_id): ...@@ -214,7 +213,7 @@ def undo_vote_for_thread(request, course_id, thread_id):
thread = cc.Thread.find(thread_id) thread = cc.Thread.find(thread_id)
user.unvote(thread) user.unvote(thread)
return JsonResponse(utils.safe_content(thread.to_dict())) return JsonResponse(utils.safe_content(thread.to_dict()))
@require_POST @require_POST
@login_required @login_required
...@@ -288,7 +287,7 @@ def update_moderator_status(request, course_id, user_id): ...@@ -288,7 +287,7 @@ def update_moderator_status(request, course_id, user_id):
course = get_course_with_access(request.user, course_id, 'load') course = get_course_with_access(request.user, course_id, 'load')
discussion_user = cc.User(id=user_id, course_id=course_id) discussion_user = cc.User(id=user_id, course_id=course_id)
context = { context = {
'course': course, 'course': course,
'course_id': course_id, 'course_id': course_id,
'user': request.user, 'user': request.user,
'django_user': user, 'django_user': user,
...@@ -327,7 +326,7 @@ def tags_autocomplete(request, course_id): ...@@ -327,7 +326,7 @@ def tags_autocomplete(request, course_id):
@require_POST @require_POST
@login_required @login_required
@csrf.csrf_exempt @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 """view that handles file upload via Ajax
""" """
...@@ -337,7 +336,7 @@ def upload(request, course_id):#ajax upload file to a question or answer ...@@ -337,7 +336,7 @@ def upload(request, course_id):#ajax upload file to a question or answer
new_file_name = '' new_file_name = ''
try: try:
# TODO authorization # TODO authorization
#may raise exceptions.PermissionDenied #may raise exceptions.PermissionDenied
#if request.user.is_anonymous(): #if request.user.is_anonymous():
# msg = _('Sorry, anonymous users cannot upload files') # msg = _('Sorry, anonymous users cannot upload files')
# raise exceptions.PermissionDenied(msg) # raise exceptions.PermissionDenied(msg)
...@@ -357,7 +356,7 @@ def upload(request, course_id):#ajax upload file to a question or answer ...@@ -357,7 +356,7 @@ def upload(request, course_id):#ajax upload file to a question or answer
new_file_name = str( new_file_name = str(
time.time() time.time()
).replace( ).replace(
'.', '.',
str(random.randint(0,100000)) str(random.randint(0,100000))
) + file_extension ) + file_extension
...@@ -386,7 +385,7 @@ def upload(request, course_id):#ajax upload file to a question or answer ...@@ -386,7 +385,7 @@ def upload(request, course_id):#ajax upload file to a question or answer
parsed_url = urlparse.urlparse(file_url) parsed_url = urlparse.urlparse(file_url)
file_url = urlparse.urlunparse( file_url = urlparse.urlunparse(
urlparse.ParseResult( urlparse.ParseResult(
parsed_url.scheme, parsed_url.scheme,
parsed_url.netloc, parsed_url.netloc,
parsed_url.path, parsed_url.path,
'', '', '' '', '', ''
......
...@@ -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=''):
......
...@@ -100,24 +100,24 @@ def initialize_discussion_info(course): ...@@ -100,24 +100,24 @@ def initialize_discussion_info(course):
unexpanded_category_map[category].append({"title": title, "id": id, unexpanded_category_map[category].append({"title": title, "id": id,
"sort_key": sort_key}) "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(): for category_path, entries in unexpanded_category_map.items():
node = category_map["subcategories"] node = category_map["subcategories"]
path = [x.strip() for x in category_path.split("/")] path = [x.strip() for x in category_path.split("/")]
for level in path[:-1]: for level in path[:-1]:
if level not in node: if level not in node:
node[level] = {"subcategories": defaultdict(dict), node[level] = {"subcategories": defaultdict(dict),
"entries": defaultdict(dict), "entries": defaultdict(dict),
"sort_key": level} "sort_key": level}
node = node[level]["subcategories"] node = node[level]["subcategories"]
level = path[-1] level = path[-1]
if level not in node: if level not in node:
node[level] = {"subcategories": defaultdict(dict), node[level] = {"subcategories": defaultdict(dict),
"entries": defaultdict(dict), "entries": defaultdict(dict),
"sort_key": level} "sort_key": level}
for entry in entries: 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"]} "sort_key": entry["sort_key"]}
for topic, entry in course.metadata.get('discussion_topics', {}).items(): for topic, entry in course.metadata.get('discussion_topics', {}).items():
...@@ -134,7 +134,7 @@ def initialize_discussion_info(course): ...@@ -134,7 +134,7 @@ def initialize_discussion_info(course):
def get_courseware_context(content, course): def get_courseware_context(content, course):
id_map = get_discussion_id_map(course) id_map = get_discussion_id_map(course)
id = content['commentable_id'] id = content['commentable_id']
content_info = None content_info = None
if id in id_map: if id in id_map:
location = id_map[id]["location"].url() location = id_map[id]["location"].url()
...@@ -149,21 +149,21 @@ class JsonResponse(HttpResponse): ...@@ -149,21 +149,21 @@ 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=''):
super(HtmlResponse, self).__init__(html, content_type='text/plain') super(HtmlResponse, self).__init__(html, content_type='text/plain')
class ViewNameMiddleware(object): class ViewNameMiddleware(object):
def process_view(self, request, view_func, view_args, view_kwargs): def process_view(self, request, view_func, view_args, view_kwargs):
request.view_name = view_func.__name__ request.view_name = view_func.__name__
class QueryCountDebugMiddleware(object): class QueryCountDebugMiddleware(object):
......
...@@ -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) ->
...@@ -24,8 +23,8 @@ if Backbone? ...@@ -24,8 +23,8 @@ if Backbone?
sortByDateRecentFirst: (thread) -> sortByDateRecentFirst: (thread) ->
-(new Date(thread.get("created_at")).getTime()) -(new Date(thread.get("created_at")).getTime())
#return String.fromCharCode.apply(String, #return String.fromCharCode.apply(String,
# _.map(thread.get("created_at").split(""), # _.map(thread.get("created_at").split(""),
# ((c) -> return 0xffff - c.charChodeAt())) # ((c) -> return 0xffff - c.charChodeAt()))
#) #)
...@@ -134,7 +133,7 @@ if Backbone? ...@@ -134,7 +133,7 @@ if Backbone?
@$(".discussion-submit-post").click $.proxy(@submitNewPost, @) @$(".discussion-submit-post").click $.proxy(@submitNewPost, @)
@$(".discussion-cancel-post").click $.proxy(@cancelNewPost, @) @$(".discussion-cancel-post").click $.proxy(@cancelNewPost, @)
@$el.children(".blank").hide() @$el.children(".blank").hide()
@$(".new-post-form").show() @$(".new-post-form").show()
...@@ -177,7 +176,7 @@ if Backbone? ...@@ -177,7 +176,7 @@ if Backbone?
threadView = new ThreadView el: $thread[0], model: thread threadView = new ThreadView el: $thread[0], model: thread
thread.updateInfo response.annotated_content_info thread.updateInfo response.annotated_content_info
@cancelNewPost() @cancelNewPost()
cancelNewPost: (event) -> cancelNewPost: (event) ->
if @$el.hasClass("inline-discussion") if @$el.hasClass("inline-discussion")
......
...@@ -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)
window.user = new DiscussionUser(response.user_info) createDiscussion: (event, response, textStatus, discussionId) =>
$(event.target).html("Hide Discussion") window.user = new DiscussionUser(response.user_info)
discussion = new Discussion() Content.loadContentInfos(response.annotated_content_info)
discussion.reset(response.discussion_data, {silent: false}) $(event.target).html("Hide Discussion")
$discussion = $(Mustache.render $("script#_inline_discussion").html(), {'threads':response.discussion_data}) @discussion = new Discussion()
$(".discussion-module").append($discussion) @discussion.reset(response.discussion_data, {silent: false})
discussion.each (thread) -> $discussion = $(Mustache.render $("script#_inline_discussion").html(), {'threads':response.discussion_data, 'discussionId': discussionId})
element = $("article#thread_#{thread.id}") $(".discussion-module").append($discussion)
dtv = new DiscussionThreadInlineView el: element, model: thread @newPostForm = $('.new-post-article')
dtv.render() @threadviews = @discussion.map (thread) ->
DiscussionUtil.bulkUpdateContentInfo(window.$$annotated_content_info) new DiscussionThreadInlineView el: @$("article#thread_#{thread.id}"), model: thread
@retrieved = true _.each @threadviews, (dtv) -> dtv.render()
@showed = true 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 = $("<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
...@@ -168,7 +169,7 @@ class @NewPostView extends Backbone.View ...@@ -168,7 +169,7 @@ class @NewPostView extends Backbone.View
itemTop = $(items[index]).parent().offset().top itemTop = $(items[index]).parent().offset().top
scrollTop = $(".topic_menu").scrollTop() scrollTop = $(".topic_menu").scrollTop()
itemFromTop = $(".topic_menu").offset().top - itemTop itemFromTop = $(".topic_menu").offset().top - itemTop
scrollTarget = Math.min(scrollTop - itemFromTop, scrollTop) scrollTarget = Math.min(scrollTop - itemFromTop, scrollTop)
scrollTarget = Math.max(scrollTop - itemFromTop - $(".topic_menu").height() + $(items[index]).height() + 20, scrollTarget) scrollTarget = Math.max(scrollTop - itemFromTop - $(".topic_menu").height() + $(items[index]).height() + 20, scrollTarget)
$(".topic_menu").scrollTop(scrollTarget) $(".topic_menu").scrollTop(scrollTarget)
...@@ -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) {
......
/* /*
jQuery Tags Input Plugin 1.3.3 jQuery Tags Input Plugin 1.3.3
Copyright (c) 2011 XOXCO, Inc Copyright (c) 2011 XOXCO, Inc
Documentation for this plugin lives here: Documentation for this plugin lives here:
http://xoxco.com/clickable/jquery-tags-input http://xoxco.com/clickable/jquery-tags-input
Licensed under the MIT license: Licensed under the MIT license:
http://www.opensource.org/licenses/mit-license.php http://www.opensource.org/licenses/mit-license.php
...@@ -24,9 +24,9 @@ ...@@ -24,9 +24,9 @@
val = '', val = '',
input = $(this), input = $(this),
testSubject = $('#'+$(this).data('tester_id')); testSubject = $('#'+$(this).data('tester_id'));
if (val === (val = input.val())) {return;} if (val === (val = input.val())) {return;}
// Enter new content into testSubject // Enter new content into testSubject
var escaped = val.replace(/&/g, '&amp;').replace(/\s/g,' ').replace(/</g, '&lt;').replace(/>/g, '&gt;'); var escaped = val.replace(/&/g, '&amp;').replace(/\s/g,' ').replace(/</g, '&lt;').replace(/>/g, '&gt;');
testSubject.html(escaped); testSubject.html(escaped);
...@@ -36,7 +36,7 @@ ...@@ -36,7 +36,7 @@
currentWidth = input.width(), currentWidth = input.width(),
isValidWidthChange = (newWidth < currentWidth && newWidth >= minWidth) isValidWidthChange = (newWidth < currentWidth && newWidth >= minWidth)
|| (newWidth > minWidth && newWidth < maxWidth); || (newWidth > minWidth && newWidth < maxWidth);
// Animate width // Animate width
if (isValidWidthChange) { if (isValidWidthChange) {
input.width(newWidth); input.width(newWidth);
...@@ -72,19 +72,24 @@ ...@@ -72,19 +72,24 @@
input.data('tester_id', testerId); input.data('tester_id', testerId);
input.css('width', minWidth); input.css('width', minWidth);
}; };
$.fn.addTag = function(value,options) { $.fn.addTag = function(value,options) {
options = jQuery.extend({focus:false,callback:true},options); options = jQuery.extend({focus:false,callback:true},options);
this.each(function() { this.each(function() {
var id = $(this).attr('id'); var id = $(this).attr('id');
var tagslist = $(this).val().split(delimiter[id]); var tagslist = $(this).val().split(delimiter[id]);
if (tagslist[0] == '') { if (tagslist[0] == '') {
tagslist = new Array(); tagslist = new Array();
} }
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) {
...@@ -92,10 +97,10 @@ ...@@ -92,10 +97,10 @@
$('#'+id+'_tag').addClass('not_valid'); $('#'+id+'_tag').addClass('not_valid');
} }
} else { } else {
var skipTag = false; var skipTag = false;
} }
if (value !='' && skipTag != true) { if (value !='' && skipTag != true) {
$('<span>').addClass('tag').append( $('<span>').addClass('tag').append(
$('<span>').text(value).append('&nbsp;&nbsp;'), $('<span>').text(value).append('&nbsp;&nbsp;'),
$('<a>', { $('<a>', {
...@@ -108,16 +113,16 @@ ...@@ -108,16 +113,16 @@
).insertBefore('#' + id + '_addTag'); ).insertBefore('#' + id + '_addTag');
tagslist.push(value); tagslist.push(value);
$('#'+id+'_tag').val(''); $('#'+id+'_tag').val('');
if (options.focus) { if (options.focus) {
$('#'+id+'_tag').focus(); $('#'+id+'_tag').focus();
} else { } else {
$('#'+id+'_tag').blur(); $('#'+id+'_tag').blur();
} }
$.fn.tagsInput.updateTagsField(this,tagslist); $.fn.tagsInput.updateTagsField(this,tagslist);
if (options.callback && tags_callbacks[id] && tags_callbacks[id]['onAddTag']) { if (options.callback && tags_callbacks[id] && tags_callbacks[id]['onAddTag']) {
var f = tags_callbacks[id]['onAddTag']; var f = tags_callbacks[id]['onAddTag'];
f.call(this, value); f.call(this, value);
...@@ -127,29 +132,29 @@ ...@@ -127,29 +132,29 @@
var i = tagslist.length; var i = tagslist.length;
var f = tags_callbacks[id]['onChange']; var f = tags_callbacks[id]['onChange'];
f.call(this, $(this), tagslist[i-1]); f.call(this, $(this), tagslist[i-1]);
} }
} }
}); });
return false; return false;
}; };
$.fn.removeTag = function(value) { $.fn.removeTag = function(value) {
value = unescape(value); value = unescape(value);
this.each(function() { this.each(function() {
var id = $(this).attr('id'); var id = $(this).attr('id');
var old = $(this).val().split(delimiter[id]); var old = $(this).val().split(delimiter[id]);
$('#'+id+'_tagsinput .tag').remove(); $('#'+id+'_tagsinput .tag').remove();
str = ''; str = '';
for (i=0; i< old.length; i++) { for (i=0; i< old.length; i++) {
if (old[i]!=value) { if (old[i]!=value) {
str = str + delimiter[id] +old[i]; str = str + delimiter[id] +old[i];
} }
} }
$.fn.tagsInput.importTags(this,str); $.fn.tagsInput.importTags(this,str);
if (tags_callbacks[id] && tags_callbacks[id]['onRemoveTag']) { if (tags_callbacks[id] && tags_callbacks[id]['onRemoveTag']) {
...@@ -157,24 +162,24 @@ ...@@ -157,24 +162,24 @@
f.call(this, value); f.call(this, value);
} }
}); });
return false; return false;
}; };
$.fn.tagExist = function(val) { $.fn.tagExist = function(val) {
var id = $(this).attr('id'); var id = $(this).attr('id');
var tagslist = $(this).val().split(delimiter[id]); var tagslist = $(this).val().split(delimiter[id]);
return (jQuery.inArray(val, tagslist) >= 0); //true when tag exists, false when not return (jQuery.inArray(val, tagslist) >= 0); //true when tag exists, false when not
}; };
// clear all existing tags and import new ones from a string // clear all existing tags and import new ones from a string
$.fn.importTags = function(str) { $.fn.importTags = function(str) {
id = $(this).attr('id'); id = $(this).attr('id');
$('#'+id+'_tagsinput .tag').remove(); $('#'+id+'_tagsinput .tag').remove();
$.fn.tagsInput.importTags(this,str); $.fn.tagsInput.importTags(this,str);
} }
$.fn.tagsInput = function(options) { $.fn.tagsInput = function(options) {
var settings = jQuery.extend({ var settings = jQuery.extend({
interactive:true, interactive:true,
defaultText:'add a tag', defaultText:'add a tag',
...@@ -192,15 +197,15 @@ ...@@ -192,15 +197,15 @@
inputPadding: 6*2 inputPadding: 6*2
},options); },options);
this.each(function() { this.each(function() {
if (settings.hide) { if (settings.hide) {
$(this).hide(); $(this).hide();
} }
var id = $(this).attr('id'); var id = $(this).attr('id');
if (!id || delimiter[$(this).attr('id')]) { if (!id || delimiter[$(this).attr('id')]) {
id = $(this).attr('id', 'tags' + new Date().getTime()).attr('id'); id = $(this).attr('id', 'tags' + new Date().getTime()).attr('id');
} }
var data = jQuery.extend({ var data = jQuery.extend({
pid:id, pid:id,
real_input: '#'+id, real_input: '#'+id,
...@@ -208,57 +213,58 @@ ...@@ -208,57 +213,58 @@
input_wrapper: '#'+id+'_addTag', input_wrapper: '#'+id+'_addTag',
fake_input: '#'+id+'_tag' fake_input: '#'+id+'_tag'
},settings); },settings);
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">';
if (settings.interactive) { if (settings.interactive) {
markup = markup + '<input id="'+id+'_tag" value="" data-default="'+settings.defaultText+'" />'; markup = markup + '<input id="'+id+'_tag" value="" data-default="'+settings.defaultText+'" />';
} }
markup = markup + '</div><div class="tags_clear"></div></div>'; markup = markup + '</div><div class="tags_clear"></div></div>';
$(markup).insertAfter(this); $(markup).insertAfter(this);
$(data.holder).css('width',settings.width); $(data.holder).css('width',settings.width);
$(data.holder).css('min-height',settings.height); $(data.holder).css('min-height',settings.height);
$(data.holder).css('height','100%'); $(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()); $.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).val($(data.fake_input).attr('data-default'));
$(data.fake_input).css('color',settings.placeholderColor); $(data.fake_input).css('color',settings.placeholderColor);
$(data.fake_input).resetAutosize(settings); $(data.fake_input).resetAutosize(settings);
$(data.fake_input).doAutosize(settings); $(data.fake_input).doAutosize(settings);
$(data.holder).bind('click',data,function(event) { $(data.holder).bind('click',data,function(event) {
$(event.data.fake_input).focus(); $(event.data.fake_input).focus();
}); });
$(data.fake_input).bind('focus',data,function(event) { $(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).val('');
} }
$(event.data.fake_input).css('color','#000000'); $(event.data.fake_input).css('color','#000000');
}); });
if (settings.autocomplete_url != undefined) { if (settings.autocomplete_url != undefined) {
autocomplete_options = {source: settings.autocomplete_url}; autocomplete_options = {source: settings.autocomplete_url};
for (attrname in settings.autocomplete) { for (attrname in settings.autocomplete) {
autocomplete_options[attrname] = settings.autocomplete[attrname]; autocomplete_options[attrname] = settings.autocomplete[attrname];
} }
if (jQuery.Autocompleter !== undefined) { if (jQuery.Autocompleter !== undefined) {
onSelectCallback = settings.autocomplete.onItemSelect; onSelectCallback = settings.autocomplete.onItemSelect;
settings.autocomplete.onItemSelect = function() { settings.autocomplete.onItemSelect = function() {
...@@ -278,18 +284,18 @@ ...@@ -278,18 +284,18 @@
$(data.fake_input).autocomplete(autocomplete_options); $(data.fake_input).autocomplete(autocomplete_options);
$(data.fake_input).bind('autocompleteselect',data,function(event,ui) { $(data.fake_input).bind('autocompleteselect',data,function(event,ui) {
$(event.data.real_input).addTag(ui.item.value,{focus:true,unique:(settings.unique)}); $(event.data.real_input).addTag(ui.item.value,{focus:true,unique:(settings.unique)});
return false; return false;
}); });
} }
} else { } else {
// if a user tabs out of the field, create a new tag // if a user tabs out of the field, create a new tag
// this is only available if autocomplete is not used. // 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'); 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)) ) 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)}); $(event.data.real_input).addTag($(event.data.fake_input).val(),{focus:true,unique:(settings.unique)});
} else { } else {
...@@ -298,7 +304,7 @@ ...@@ -298,7 +304,7 @@
} }
return false; return false;
}); });
} }
// if user types a comma, create a new tag // if user types a comma, create a new tag
$(data.fake_input).bind('keypress',data,function(event) { $(data.fake_input).bind('keypress',data,function(event) {
...@@ -326,7 +332,7 @@ ...@@ -326,7 +332,7 @@
} }
}); });
$(data.fake_input).blur(); $(data.fake_input).blur();
//Removes the not_valid class when user changes the value of the fake input //Removes the not_valid class when user changes the value of the fake input
if(data.unique) { if(data.unique) {
$(data.fake_input).keydown(function(event){ $(data.fake_input).keydown(function(event){
...@@ -337,21 +343,21 @@ ...@@ -337,21 +343,21 @@
} }
} // if settings.interactive } // if settings.interactive
}); });
return this; return this;
}; };
$.fn.tagsInput.updateTagsField = function(obj,tagslist) { $.fn.tagsInput.updateTagsField = function(obj,tagslist) {
var id = $(obj).attr('id'); var id = $(obj).attr('id');
$(obj).val(tagslist.join(delimiter[id])); $(obj).val(tagslist.join(delimiter[id]));
}; };
$.fn.tagsInput.importTags = function(obj,val) { $.fn.tagsInput.importTags = function(obj,val) {
$(obj).val(''); $(obj).val('');
var id = $(obj).attr('id'); var id = $(obj).attr('id');
var tags = val.split(delimiter[id]); var tags = val.split(delimiter[id]);
for (i=0; i<tags.length; i++) { for (i=0; i<tags.length; i++) {
$(obj).addTag(tags[i],{focus:false,callback:false}); $(obj).addTag(tags[i],{focus:false,callback:false});
} }
if(tags_callbacks[id] && tags_callbacks[id]['onChange']) if(tags_callbacks[id] && tags_callbacks[id]['onChange'])
......
...@@ -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>
...@@ -22,9 +22,7 @@ ...@@ -22,9 +22,7 @@
</%def> </%def>
<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}}">
{{#threads}} <a href="#" class="new-post-btn"><span class="new-post-icon"></span>New Post</a>
<article class="discussion-thread" id="thread_{{id}}">
<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> </article>
{{/threads}}
<section class="threads">
{{#threads}}
<article class="discussion-thread" id="thread_{{id}}">
</article>
{{/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