Merge branch 'feature/tomg/new-discussions' of github.com:MITx/mitx into feature/tomg/new-discussions
This commit is contained in:
@@ -38,11 +38,10 @@ def permitted(fn):
|
||||
else:
|
||||
content = None
|
||||
return content
|
||||
|
||||
if check_permissions_by_view(request.user, kwargs['course_id'], fetch_content(), request.view_name):
|
||||
return fn(request, *args, **kwargs)
|
||||
else:
|
||||
return JsonError("unauthorized")
|
||||
return JsonError("unauthorized", status=401)
|
||||
return wrapper
|
||||
|
||||
def ajax_content_response(request, course_id, content, template_name):
|
||||
@@ -214,7 +213,7 @@ def undo_vote_for_thread(request, course_id, thread_id):
|
||||
thread = cc.Thread.find(thread_id)
|
||||
user.unvote(thread)
|
||||
return JsonResponse(utils.safe_content(thread.to_dict()))
|
||||
|
||||
|
||||
|
||||
@require_POST
|
||||
@login_required
|
||||
@@ -288,7 +287,7 @@ def update_moderator_status(request, course_id, user_id):
|
||||
course = get_course_with_access(request.user, course_id, 'load')
|
||||
discussion_user = cc.User(id=user_id, course_id=course_id)
|
||||
context = {
|
||||
'course': course,
|
||||
'course': course,
|
||||
'course_id': course_id,
|
||||
'user': request.user,
|
||||
'django_user': user,
|
||||
@@ -327,7 +326,7 @@ def tags_autocomplete(request, course_id):
|
||||
@require_POST
|
||||
@login_required
|
||||
@csrf.csrf_exempt
|
||||
def upload(request, course_id):#ajax upload file to a question or answer
|
||||
def upload(request, course_id):#ajax upload file to a question or answer
|
||||
"""view that handles file upload via Ajax
|
||||
"""
|
||||
|
||||
@@ -337,7 +336,7 @@ def upload(request, course_id):#ajax upload file to a question or answer
|
||||
new_file_name = ''
|
||||
try:
|
||||
# TODO authorization
|
||||
#may raise exceptions.PermissionDenied
|
||||
#may raise exceptions.PermissionDenied
|
||||
#if request.user.is_anonymous():
|
||||
# msg = _('Sorry, anonymous users cannot upload files')
|
||||
# raise exceptions.PermissionDenied(msg)
|
||||
@@ -357,7 +356,7 @@ def upload(request, course_id):#ajax upload file to a question or answer
|
||||
new_file_name = str(
|
||||
time.time()
|
||||
).replace(
|
||||
'.',
|
||||
'.',
|
||||
str(random.randint(0,100000))
|
||||
) + file_extension
|
||||
|
||||
@@ -386,7 +385,7 @@ def upload(request, course_id):#ajax upload file to a question or answer
|
||||
parsed_url = urlparse.urlparse(file_url)
|
||||
file_url = urlparse.urlunparse(
|
||||
urlparse.ParseResult(
|
||||
parsed_url.scheme,
|
||||
parsed_url.scheme,
|
||||
parsed_url.netloc,
|
||||
parsed_url.path,
|
||||
'', '', ''
|
||||
|
||||
@@ -136,10 +136,16 @@ def inline_discussion(request, course_id, discussion_id):
|
||||
# html = render_inline_discussion(request, course_id, threads, discussion_id=discussion_id, \
|
||||
# query_params=query_params)
|
||||
user_info = cc.User.from_django_user(request.user).to_dict()
|
||||
|
||||
def infogetter(thread):
|
||||
return utils.get_annotated_content_infos(course_id, thread, request.user, user_info)
|
||||
|
||||
annotated_content_info = reduce(merge_dict, map(infogetter, threads), {})
|
||||
return utils.JsonResponse({
|
||||
# 'html': html,
|
||||
'discussion_data': map(utils.safe_content, threads),
|
||||
'user_info': user_info,
|
||||
'annotated_content_info': annotated_content_info
|
||||
})
|
||||
|
||||
def render_search_bar(request, course_id, discussion_id=None, text=''):
|
||||
|
||||
@@ -100,24 +100,24 @@ def initialize_discussion_info(course):
|
||||
unexpanded_category_map[category].append({"title": title, "id": id,
|
||||
"sort_key": sort_key})
|
||||
|
||||
category_map = {"entries": defaultdict(dict), "subcategories": defaultdict(dict)}
|
||||
category_map = {"entries": defaultdict(dict), "subcategories": defaultdict(dict)}
|
||||
for category_path, entries in unexpanded_category_map.items():
|
||||
node = category_map["subcategories"]
|
||||
path = [x.strip() for x in category_path.split("/")]
|
||||
for level in path[:-1]:
|
||||
if level not in node:
|
||||
node[level] = {"subcategories": defaultdict(dict),
|
||||
node[level] = {"subcategories": defaultdict(dict),
|
||||
"entries": defaultdict(dict),
|
||||
"sort_key": level}
|
||||
"sort_key": level}
|
||||
node = node[level]["subcategories"]
|
||||
|
||||
level = path[-1]
|
||||
if level not in node:
|
||||
node[level] = {"subcategories": defaultdict(dict),
|
||||
"entries": defaultdict(dict),
|
||||
node[level] = {"subcategories": defaultdict(dict),
|
||||
"entries": defaultdict(dict),
|
||||
"sort_key": level}
|
||||
for entry in entries:
|
||||
node[level]["entries"][entry["title"]] = {"id": entry["id"],
|
||||
node[level]["entries"][entry["title"]] = {"id": entry["id"],
|
||||
"sort_key": entry["sort_key"]}
|
||||
|
||||
for topic, entry in course.metadata.get('discussion_topics', {}).items():
|
||||
@@ -134,7 +134,7 @@ def initialize_discussion_info(course):
|
||||
|
||||
def get_courseware_context(content, course):
|
||||
id_map = get_discussion_id_map(course)
|
||||
id = content['commentable_id']
|
||||
id = content['commentable_id']
|
||||
content_info = None
|
||||
if id in id_map:
|
||||
location = id_map[id]["location"].url()
|
||||
@@ -149,21 +149,21 @@ class JsonResponse(HttpResponse):
|
||||
mimetype='application/json; charset=utf8')
|
||||
|
||||
class JsonError(HttpResponse):
|
||||
def __init__(self, error_messages=[]):
|
||||
def __init__(self, error_messages=[], status=400):
|
||||
if isinstance(error_messages, str):
|
||||
error_messages = [error_messages]
|
||||
content = simplejson.dumps({'errors': error_messages},
|
||||
indent=2,
|
||||
ensure_ascii=False)
|
||||
super(JsonError, self).__init__(content,
|
||||
mimetype='application/json; charset=utf8', status=400)
|
||||
mimetype='application/json; charset=utf8', status=status)
|
||||
|
||||
class HtmlResponse(HttpResponse):
|
||||
def __init__(self, html=''):
|
||||
super(HtmlResponse, self).__init__(html, content_type='text/plain')
|
||||
|
||||
class ViewNameMiddleware(object):
|
||||
def process_view(self, request, view_func, view_args, view_kwargs):
|
||||
class ViewNameMiddleware(object):
|
||||
def process_view(self, request, view_func, view_args, view_kwargs):
|
||||
request.view_name = view_func.__name__
|
||||
|
||||
class QueryCountDebugMiddleware(object):
|
||||
|
||||
@@ -12,6 +12,7 @@ class Thread(models.Model):
|
||||
'created_at', 'updated_at', 'comments_count',
|
||||
'at_position_list', 'children', 'type',
|
||||
'highlighted_title', 'highlighted_body',
|
||||
'endorsed'
|
||||
]
|
||||
|
||||
updatable_fields = [
|
||||
|
||||
@@ -7,7 +7,6 @@ if Backbone?
|
||||
item.discussion = @
|
||||
@comparator = @sortByDateRecentFirst
|
||||
@on "thread:remove", (thread) =>
|
||||
console.log "remove triggered"
|
||||
@remove(thread)
|
||||
|
||||
find: (id) ->
|
||||
@@ -24,8 +23,8 @@ if Backbone?
|
||||
|
||||
sortByDateRecentFirst: (thread) ->
|
||||
-(new Date(thread.get("created_at")).getTime())
|
||||
#return String.fromCharCode.apply(String,
|
||||
# _.map(thread.get("created_at").split(""),
|
||||
#return String.fromCharCode.apply(String,
|
||||
# _.map(thread.get("created_at").split(""),
|
||||
# ((c) -> return 0xffff - c.charChodeAt()))
|
||||
#)
|
||||
|
||||
@@ -134,7 +133,7 @@ if Backbone?
|
||||
|
||||
@$(".discussion-submit-post").click $.proxy(@submitNewPost, @)
|
||||
@$(".discussion-cancel-post").click $.proxy(@cancelNewPost, @)
|
||||
|
||||
|
||||
|
||||
@$el.children(".blank").hide()
|
||||
@$(".new-post-form").show()
|
||||
@@ -177,7 +176,7 @@ if Backbone?
|
||||
threadView = new ThreadView el: $thread[0], model: thread
|
||||
thread.updateInfo response.annotated_content_info
|
||||
@cancelNewPost()
|
||||
|
||||
|
||||
|
||||
cancelNewPost: (event) ->
|
||||
if @$el.hasClass("inline-discussion")
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
if Backbone?
|
||||
class @DiscussionModuleView extends Backbone.View
|
||||
events:
|
||||
"click .discussion-show": "toggleDiscussion"
|
||||
toggleDiscussion: (event) ->
|
||||
if @showed
|
||||
@$("section.discussion").hide()
|
||||
$(event.target).html("Show Discussion")
|
||||
@showed = false
|
||||
else
|
||||
if @retrieved
|
||||
@$("section.discussion").show()
|
||||
$(event.target).html("Hide Discussion")
|
||||
@showed = true
|
||||
else
|
||||
$elem = $(event.target)
|
||||
discussion_id = $elem.attr("discussion_id")
|
||||
url = DiscussionUtil.urlFor 'retrieve_discussion', discussion_id
|
||||
DiscussionUtil.safeAjax
|
||||
$elem: $elem
|
||||
$loading: $elem
|
||||
url: url
|
||||
type: "GET"
|
||||
dataType: 'json'
|
||||
success: (response, textStatus) =>
|
||||
#@$el.append(response.html)
|
||||
window.user = new DiscussionUser(response.user_info)
|
||||
$(event.target).html("Hide Discussion")
|
||||
discussion = new Discussion()
|
||||
discussion.reset(response.discussion_data, {silent: false})
|
||||
$discussion = $(Mustache.render $("script#_inline_discussion").html(), {'threads':response.discussion_data})
|
||||
$(".discussion-module").append($discussion)
|
||||
discussion.each (thread) ->
|
||||
element = $("article#thread_#{thread.id}")
|
||||
dtv = new DiscussionThreadInlineView el: element, model: thread
|
||||
dtv.render()
|
||||
DiscussionUtil.bulkUpdateContentInfo(window.$$annotated_content_info)
|
||||
@retrieved = true
|
||||
@showed = true
|
||||
@@ -0,0 +1,64 @@
|
||||
if Backbone?
|
||||
class @DiscussionModuleView extends Backbone.View
|
||||
events:
|
||||
"click .discussion-show": "toggleDiscussion"
|
||||
"click .new-post-btn": "toggleNewPost"
|
||||
"click .new-post-cancel": "hideNewPost"
|
||||
initialize: ->
|
||||
|
||||
toggleNewPost: (event) ->
|
||||
if @newPostForm.is(':hidden')
|
||||
@newPostForm.slideDown(300)
|
||||
else
|
||||
@newPostForm.slideUp(300)
|
||||
hideNewPost: (event) ->
|
||||
@newPostForm.slideUp(300)
|
||||
|
||||
toggleDiscussion: (event) ->
|
||||
if @showed
|
||||
@$("section.discussion").hide()
|
||||
$(event.target).html("Show Discussion")
|
||||
@showed = false
|
||||
else
|
||||
if @retrieved
|
||||
@$("section.discussion").show()
|
||||
$(event.target).html("Hide Discussion")
|
||||
@showed = true
|
||||
else
|
||||
$elem = $(event.target)
|
||||
discussionId = $elem.data("discussion-id")
|
||||
url = DiscussionUtil.urlFor 'retrieve_discussion', discussionId
|
||||
DiscussionUtil.safeAjax
|
||||
$elem: $elem
|
||||
$loading: $elem
|
||||
url: url
|
||||
type: "GET"
|
||||
dataType: 'json'
|
||||
success: (response, textStatus, jqXHR) => @createDiscussion(event, response, textStatus, discussionId)
|
||||
|
||||
createDiscussion: (event, response, textStatus, discussionId) =>
|
||||
window.user = new DiscussionUser(response.user_info)
|
||||
Content.loadContentInfos(response.annotated_content_info)
|
||||
$(event.target).html("Hide Discussion")
|
||||
@discussion = new Discussion()
|
||||
@discussion.reset(response.discussion_data, {silent: false})
|
||||
$discussion = $(Mustache.render $("script#_inline_discussion").html(), {'threads':response.discussion_data, 'discussionId': discussionId})
|
||||
$(".discussion-module").append($discussion)
|
||||
@newPostForm = $('.new-post-article')
|
||||
@threadviews = @discussion.map (thread) ->
|
||||
new DiscussionThreadInlineView el: @$("article#thread_#{thread.id}"), model: thread
|
||||
_.each @threadviews, (dtv) -> dtv.render()
|
||||
DiscussionUtil.bulkUpdateContentInfo(window.$$annotated_content_info)
|
||||
@newPostView = new NewPostInlineView el: @$('.new-post-article'), collection: @discussion
|
||||
@discussion.on "add", @addThread
|
||||
@retrieved = true
|
||||
@showed = true
|
||||
|
||||
addThread: (thread, collection, options) =>
|
||||
# TODO: When doing pagination, this will need to repaginate
|
||||
article = $("<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 @@ $ ->
|
||||
window.$$contents = {}
|
||||
$.fn.extend
|
||||
loading: ->
|
||||
@$_loading = $("<span class='discussion-loading'></span>")
|
||||
@$_loading = $("<div class='loading-animation'></div>")
|
||||
$(this).after(@$_loading)
|
||||
loaded: ->
|
||||
@$_loading.remove()
|
||||
@@ -107,6 +107,9 @@ class @DiscussionUtil
|
||||
[event, selector] = eventSelector.split(' ')
|
||||
$local(selector).unbind(event)[event] handler
|
||||
|
||||
@processTag: (text) ->
|
||||
text.toLowerCase()
|
||||
|
||||
@tagsInputOptions: ->
|
||||
autocomplete_url: @urlFor('tags_autocomplete')
|
||||
autocomplete:
|
||||
@@ -116,6 +119,7 @@ class @DiscussionUtil
|
||||
width: '100%'
|
||||
defaultText: "Tag your post: press enter after each tag"
|
||||
removeWithBackspace: true
|
||||
preprocessTag: @processTag
|
||||
|
||||
@formErrorHandler: (errorsField) ->
|
||||
(xhr, textStatus, error) ->
|
||||
@@ -123,7 +127,7 @@ class @DiscussionUtil
|
||||
if response.errors? and response.errors.length > 0
|
||||
errorsField.empty()
|
||||
for error in response.errors
|
||||
errorsField.append($("<li>").addClass("new-post-form-error").html(error))
|
||||
errorsField.append($("<li>").addClass("new-post-form-error").html(error)).show()
|
||||
|
||||
@clearFormErrors: (errorsField) ->
|
||||
errorsField.empty()
|
||||
|
||||
@@ -21,10 +21,6 @@ class @DiscussionThreadInlineView extends DiscussionContentView
|
||||
@model.on "change", @updateModelDetails
|
||||
|
||||
render: ->
|
||||
#TODO: Debugging, remove when done
|
||||
if not window.$disc
|
||||
window.$disc = []
|
||||
window.$disc.push(@)
|
||||
if not @model.has('abbreviatedBody')
|
||||
@abbreviateBody()
|
||||
@$el.html(Mustache.render(@template(), $.extend(@model.toJSON(),{expanded: @expanded}) ))
|
||||
@@ -38,8 +34,6 @@ class @DiscussionThreadInlineView extends DiscussionContentView
|
||||
if @expanded
|
||||
@makeWmdEditor "reply-body"
|
||||
@renderResponses()
|
||||
# @highlight @$(".post-body")
|
||||
# @highlight @$("h1")
|
||||
@
|
||||
|
||||
renderDogear: ->
|
||||
@@ -58,12 +52,13 @@ class @DiscussionThreadInlineView extends DiscussionContentView
|
||||
|
||||
convertMath: ->
|
||||
element = @$(".post-body")
|
||||
element.html DiscussionUtil.postMathJaxProcessor(element.html())
|
||||
element.html DiscussionUtil.postMathJaxProcessor DiscussionUtil.markdownWithHighlight element.html()
|
||||
MathJax.Hub.Queue ["Typeset", MathJax.Hub, element[0]]
|
||||
|
||||
renderResponses: ->
|
||||
DiscussionUtil.safeAjax
|
||||
url: "/courses/#{$$course_id}/discussion/forum/#{@model.get('commentable_id')}/threads/#{@model.id}"
|
||||
$loading: @$el
|
||||
success: (data, textStatus, xhr) =>
|
||||
@$el.find(".loading").remove()
|
||||
Content.loadContentInfos(data['annotated_content_info'])
|
||||
@@ -192,16 +187,14 @@ class @DiscussionThreadInlineView extends DiscussionContentView
|
||||
success: (response, textStatus) =>
|
||||
@model.set('endorsed', not endorsed)
|
||||
|
||||
highlight: (el) ->
|
||||
el.html(el.html().replace(/<mark>/g, "<mark>").replace(/<\/mark>/g, "</mark>"))
|
||||
|
||||
abbreviateBody: ->
|
||||
abbreviated = DiscussionUtil.abbreviateString @model.get('body'), 140 # Because twitter
|
||||
abbreviated = DiscussionUtil.abbreviateString @model.get('body'), 140
|
||||
@model.set('abbreviatedBody', abbreviated)
|
||||
|
||||
expandPost: (event) ->
|
||||
@expanded = true
|
||||
@$el.find('.post-body').html(@model.get('body'))
|
||||
@convertMath()
|
||||
@$el.find('.expand-post').hide()
|
||||
@$el.find('.collapse-post').show()
|
||||
@$el.find('.post-extended-content').show()
|
||||
@@ -212,6 +205,7 @@ class @DiscussionThreadInlineView extends DiscussionContentView
|
||||
collapsePost: (event) ->
|
||||
@expanded = false
|
||||
@$el.find('.post-body').html(@model.get('abbreviatedBody'))
|
||||
@convertMath()
|
||||
@$el.find('.collapse-post').hide()
|
||||
@$el.find('.post-extended-content').hide()
|
||||
@$el.find('.expand-post').show()
|
||||
|
||||
@@ -15,6 +15,7 @@ class @DiscussionThreadListView extends Backbone.View
|
||||
@collection.on "add", @addAndSelectThread
|
||||
@sidebar_padding = 10
|
||||
@sidebar_header_height = 87
|
||||
@boardName
|
||||
|
||||
reloadDisplayedCollection: (thread) =>
|
||||
thread_id = thread.get('id')
|
||||
@@ -41,8 +42,8 @@ class @DiscussionThreadListView extends Backbone.View
|
||||
windowHeight = $(window).height();
|
||||
|
||||
discussionBody = $(".discussion-article")
|
||||
discussionsBodyTop = if discussionBody[0] then discussionBody.offset().top;
|
||||
discussionsBodyBottom = discussionsBodyTop + discussionBody.outerHeight();
|
||||
discussionsBodyTop = if discussionBody[0] then discussionBody.offset().top
|
||||
discussionsBodyBottom = discussionsBodyTop + discussionBody.outerHeight()
|
||||
|
||||
sidebar = $(".sidebar")
|
||||
if scrollTop > discussionsBodyTop - @sidebar_padding
|
||||
@@ -62,10 +63,11 @@ class @DiscussionThreadListView extends Backbone.View
|
||||
amount = Math.max(topOffset - discussionBottomOffset, 0)
|
||||
|
||||
sidebarHeight = sidebarHeight - @sidebar_padding - amount
|
||||
sidebar.css 'height', Math.min(Math.max(sidebarHeight, 400), discussionBody.outerHeight())
|
||||
sidebarHeight = Math.min(Math.max(sidebarHeight, 400), discussionBody.outerHeight())
|
||||
sidebar.css 'height', sidebarHeight
|
||||
|
||||
postListWrapper = @$('.post-list-wrapper')
|
||||
postListWrapper.css('height', (sidebarHeight - @sidebar_header_height - 4) + 'px');
|
||||
postListWrapper.css('height', (sidebarHeight - @sidebar_header_height - 4) + 'px')
|
||||
|
||||
|
||||
# Because we want the behavior that when the body is clicked the menu is
|
||||
@@ -101,6 +103,8 @@ class @DiscussionThreadListView extends Backbone.View
|
||||
content = $(_.template($("#thread-list-item-template").html())(thread.toJSON()))
|
||||
if thread.get('subscribed')
|
||||
content.addClass("followed")
|
||||
if thread.get('endorsed')
|
||||
content.addClass("resolved")
|
||||
@highlight(content)
|
||||
|
||||
|
||||
@@ -137,28 +141,66 @@ class @DiscussionThreadListView extends Backbone.View
|
||||
@$(".browse").toggleClass('is-dropped')
|
||||
if @$(".browse").hasClass('is-dropped')
|
||||
@$(".browse-topic-drop-menu-wrapper").show()
|
||||
$('body').bind 'click', @toggleTopicDrop
|
||||
$('body').bind 'keydown', @setActiveItem
|
||||
$(".browse-topic-drop-search-input").focus()
|
||||
$("body").bind "click", @toggleTopicDrop
|
||||
$("body").bind "keydown", @setActiveItem
|
||||
else
|
||||
@$(".browse-topic-drop-menu-wrapper").hide()
|
||||
$('body').unbind 'click', @toggleTopicDrop
|
||||
$('body').unbind 'keydown', @setActiveItem
|
||||
$("body").unbind "click", @toggleTopicDrop
|
||||
$("body").unbind "keydown", @setActiveItem
|
||||
|
||||
setTopic: (event) ->
|
||||
item = $(event.target).closest('a')
|
||||
boardName = item.find(".board-name").html()
|
||||
_.each item.parents('ul').not('.browse-topic-drop-menu'), (parent) ->
|
||||
boardName = $(parent).siblings('a').find('.board-name').html() + ' / ' + boardName
|
||||
@$(".current-board").html(boardName)
|
||||
@$(".current-board").html(@fitName(boardName))
|
||||
fontSize = 16
|
||||
@$(".current-board").css('font-size', '16px')
|
||||
|
||||
while @$(".current-board").width() > (@$el.width() * .8) - 40
|
||||
fontSize--
|
||||
if fontSize < 11
|
||||
break
|
||||
@$(".current-board").css('font-size', fontSize + 'px')
|
||||
|
||||
setSelectedTopic: (name) ->
|
||||
@$(".current-board").html(@fitName(name))
|
||||
|
||||
getNameWidth: (name) ->
|
||||
test = $("<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) ->
|
||||
@setTopic(event)
|
||||
item = $(event.target).closest('li')
|
||||
|
||||
@@ -49,7 +49,7 @@ class @DiscussionThreadView extends DiscussionContentView
|
||||
|
||||
convertMath: ->
|
||||
element = @$(".post-body")
|
||||
element.html DiscussionUtil.postMathJaxProcessor(element.html())
|
||||
element.html DiscussionUtil.postMathJaxProcessor DiscussionUtil.markdownWithHighlight element.html()
|
||||
MathJax.Hub.Queue ["Typeset", MathJax.Hub, element[0]]
|
||||
|
||||
renderResponses: ->
|
||||
@@ -66,12 +66,17 @@ class @DiscussionThreadView extends DiscussionContentView
|
||||
response.set('thread', @model)
|
||||
view = new ThreadResponseView(model: response)
|
||||
view.on "comment:add", @addComment
|
||||
view.on "comment:endorse", @endorseThread
|
||||
view.render()
|
||||
@$el.find(".responses").append(view.el)
|
||||
|
||||
addComment: =>
|
||||
@model.comment()
|
||||
|
||||
endorseThread: (endorsed) =>
|
||||
is_endorsed = @$el.find(".is-endorsed").length
|
||||
@model.set 'endorsed', is_endorsed
|
||||
|
||||
toggleVote: (event) ->
|
||||
event.preventDefault()
|
||||
if window.user.voted(@model)
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
class @NewPostInlineView extends Backbone.View
|
||||
|
||||
initialize: () ->
|
||||
|
||||
@topicId = @$(".topic").first().data("discussion-id")
|
||||
|
||||
@maxNameWidth = 100
|
||||
|
||||
DiscussionUtil.makeWmdEditor @$el, $.proxy(@$, @), "new-post-body"
|
||||
@$(".new-post-tags").tagsInput DiscussionUtil.tagsInputOptions()
|
||||
|
||||
events:
|
||||
"submit .new-post-form": "createPost"
|
||||
|
||||
# Because we want the behavior that when the body is clicked the menu is
|
||||
# closed, we need to ignore clicks in the search field and stop propagation.
|
||||
# Without this, clicking the search field would also close the menu.
|
||||
ignoreClick: (event) ->
|
||||
event.stopPropagation()
|
||||
|
||||
createPost: (event) ->
|
||||
event.preventDefault()
|
||||
title = @$(".new-post-title").val()
|
||||
body = @$(".new-post-body").find(".wmd-input").val()
|
||||
tags = @$(".new-post-tags").val()
|
||||
|
||||
anonymous = false || @$("input.discussion-anonymous").is(":checked")
|
||||
follow = false || @$("input.discussion-follow").is(":checked")
|
||||
|
||||
url = DiscussionUtil.urlFor('create_thread', @topicId)
|
||||
|
||||
DiscussionUtil.safeAjax
|
||||
$elem: $(event.target)
|
||||
$loading: $(event.target) if event
|
||||
url: url
|
||||
type: "POST"
|
||||
dataType: 'json'
|
||||
async: false # TODO when the rest of the stuff below is made to work properly..
|
||||
data:
|
||||
title: title
|
||||
body: body
|
||||
tags: tags
|
||||
anonymous: anonymous
|
||||
auto_subscribe: follow
|
||||
error: DiscussionUtil.formErrorHandler(@$(".new-post-form-errors"))
|
||||
success: (response, textStatus) =>
|
||||
# TODO: Move this out of the callback, this makes it feel sluggish
|
||||
thread = new Thread response['content']
|
||||
DiscussionUtil.clearFormErrors(@$(".new-post-form-errors"))
|
||||
@$el.hide()
|
||||
@$(".new-post-title").val("").attr("prev-text", "")
|
||||
@$(".new-post-body textarea").val("").attr("prev-text", "")
|
||||
@$(".new-post-tags").val("")
|
||||
@$(".new-post-tags").importTags("")
|
||||
@collection.add thread
|
||||
@@ -38,9 +38,10 @@ class @NewPostView extends Backbone.View
|
||||
@menuOpen = true
|
||||
@dropdownButton.addClass('dropped')
|
||||
@topicMenu.show()
|
||||
$(".form-topic-drop-search-input").focus()
|
||||
|
||||
$('body').bind 'keydown', @setActiveItem
|
||||
$('body').bind 'click', @hideTopicDropdown
|
||||
$("body").bind "keydown", @setActiveItem
|
||||
$("body").bind "click", @hideTopicDropdown
|
||||
|
||||
# Set here because 1) the window might get resized and things could
|
||||
# change and 2) can't set in initialize because the button is hidden
|
||||
@@ -52,8 +53,8 @@ class @NewPostView extends Backbone.View
|
||||
@dropdownButton.removeClass('dropped')
|
||||
@topicMenu.hide()
|
||||
|
||||
$('body').unbind 'keydown', @setActiveItem
|
||||
$('body').unbind 'click', @hideTopicDropdown
|
||||
$("body").unbind "keydown", @setActiveItem
|
||||
$("body").unbind "click", @hideTopicDropdown
|
||||
|
||||
setTopic: (event) ->
|
||||
$target = $(event.target)
|
||||
@@ -142,7 +143,7 @@ class @NewPostView extends Backbone.View
|
||||
DiscussionUtil.clearFormErrors(@$(".new-post-form-errors"))
|
||||
@$el.hide()
|
||||
@$(".new-post-title").val("").attr("prev-text", "")
|
||||
@$(".new-post-body").val("").attr("prev-text", "")
|
||||
@$(".new-post-body textarea").val("").attr("prev-text", "")
|
||||
@$(".new-post-tags").val("")
|
||||
@$(".new-post-tags").importTags("")
|
||||
@collection.add thread
|
||||
@@ -168,7 +169,7 @@ class @NewPostView extends Backbone.View
|
||||
|
||||
itemTop = $(items[index]).parent().offset().top
|
||||
scrollTop = $(".topic_menu").scrollTop()
|
||||
itemFromTop = $(".topic_menu").offset().top - itemTop
|
||||
itemFromTop = $(".topic_menu").offset().top - itemTop
|
||||
scrollTarget = Math.min(scrollTop - itemFromTop, scrollTop)
|
||||
scrollTarget = Math.max(scrollTop - itemFromTop - $(".topic_menu").height() + $(items[index]).height() + 20, scrollTarget)
|
||||
$(".topic_menu").scrollTop(scrollTarget)
|
||||
|
||||
@@ -2,6 +2,7 @@ class @ResponseCommentView extends DiscussionContentView
|
||||
tagName: "li"
|
||||
template: _.template($("#response-comment-template").html())
|
||||
initLocal: ->
|
||||
# TODO .response-local is the parent of the comments so @$local is null, not sure what was intended here...
|
||||
@$local = @$el.find(".response-local")
|
||||
@$delegateElement = @$local
|
||||
|
||||
@@ -14,8 +15,9 @@ class @ResponseCommentView extends DiscussionContentView
|
||||
@convertMath()
|
||||
@
|
||||
convertMath: ->
|
||||
body = @$(".response-body")
|
||||
body = @$el.find(".response-body")
|
||||
body.html DiscussionUtil.postMathJaxProcessor DiscussionUtil.markdownWithHighlight body.html()
|
||||
# This removes paragraphs so that comments are more compact
|
||||
body.children("p").each (index, elem) ->
|
||||
$(elem).replaceWith($(elem).html())
|
||||
MathJax.Hub.Queue ["Typeset", MathJax.Hub, body[0]]
|
||||
|
||||
@@ -109,6 +109,7 @@ class @ThreadResponseView extends DiscussionContentView
|
||||
endorsed = @model.get('endorsed')
|
||||
data = { endorsed: not endorsed }
|
||||
@model.set('endorsed', not endorsed)
|
||||
@trigger "comment:endorse", not endorsed
|
||||
DiscussionUtil.safeAjax
|
||||
$elem: $elem
|
||||
url: url
|
||||
|
||||
BIN
lms/static/images/staff-icons.png
Normal file
BIN
lms/static/images/staff-icons.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
BIN
lms/static/images/white-error-icon.png
Normal file
BIN
lms/static/images/white-error-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
@@ -58,7 +58,7 @@ $(document).ready(function() {
|
||||
$('.new-post-btn').bind('click', newPost);
|
||||
$('.new-post-cancel').bind('click', closeNewPost);
|
||||
|
||||
$('[data-tooltip]').bind({
|
||||
$body.delegate('[data-tooltip]', {
|
||||
'mouseover': showTooltip,
|
||||
'mousemove': moveTooltip,
|
||||
'mouseout': hideTooltip,
|
||||
@@ -66,14 +66,6 @@ $(document).ready(function() {
|
||||
});
|
||||
|
||||
$body.delegate('.browse-topic-drop-search-input, .form-topic-drop-search-input', 'keyup', filterDrop);
|
||||
|
||||
// $(window).bind('resize', updateSidebar);
|
||||
// $(window).bind('scroll', updateSidebar);
|
||||
// $('.discussion-column').bind("input", function (e) {
|
||||
// console.log("resized");
|
||||
// updateSidebar();
|
||||
// })
|
||||
// updateSidebar();
|
||||
});
|
||||
|
||||
function filterDrop(e) {
|
||||
@@ -276,6 +268,7 @@ function setTopic(e) {
|
||||
|
||||
function newPost(e) {
|
||||
$newPost.slideDown(300);
|
||||
$('.new-post-title').focus();
|
||||
}
|
||||
|
||||
function closeNewPost(e) {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
/*
|
||||
|
||||
jQuery Tags Input Plugin 1.3.3
|
||||
|
||||
|
||||
Copyright (c) 2011 XOXCO, Inc
|
||||
|
||||
|
||||
Documentation for this plugin lives here:
|
||||
http://xoxco.com/clickable/jquery-tags-input
|
||||
|
||||
|
||||
Licensed under the MIT license:
|
||||
http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -24,9 +24,9 @@
|
||||
val = '',
|
||||
input = $(this),
|
||||
testSubject = $('#'+$(this).data('tester_id'));
|
||||
|
||||
|
||||
if (val === (val = input.val())) {return;}
|
||||
|
||||
|
||||
// Enter new content into testSubject
|
||||
var escaped = val.replace(/&/g, '&').replace(/\s/g,' ').replace(/</g, '<').replace(/>/g, '>');
|
||||
testSubject.html(escaped);
|
||||
@@ -36,7 +36,7 @@
|
||||
currentWidth = input.width(),
|
||||
isValidWidthChange = (newWidth < currentWidth && newWidth >= minWidth)
|
||||
|| (newWidth > minWidth && newWidth < maxWidth);
|
||||
|
||||
|
||||
// Animate width
|
||||
if (isValidWidthChange) {
|
||||
input.width(newWidth);
|
||||
@@ -72,19 +72,24 @@
|
||||
input.data('tester_id', testerId);
|
||||
input.css('width', minWidth);
|
||||
};
|
||||
|
||||
|
||||
$.fn.addTag = function(value,options) {
|
||||
options = jQuery.extend({focus:false,callback:true},options);
|
||||
this.each(function() {
|
||||
this.each(function() {
|
||||
var id = $(this).attr('id');
|
||||
|
||||
var tagslist = $(this).val().split(delimiter[id]);
|
||||
if (tagslist[0] == '') {
|
||||
if (tagslist[0] == '') {
|
||||
tagslist = new Array();
|
||||
}
|
||||
|
||||
value = jQuery.trim(value);
|
||||
|
||||
|
||||
if (options.callback && tags_callbacks[id] && tags_callbacks[id]['preprocessTag']) {
|
||||
var f = tags_callbacks[id]['preprocessTag'];
|
||||
value = f.call(this, value);
|
||||
}
|
||||
|
||||
if (options.unique) {
|
||||
var skipTag = $(this).tagExist(value);
|
||||
if(skipTag == true) {
|
||||
@@ -92,10 +97,10 @@
|
||||
$('#'+id+'_tag').addClass('not_valid');
|
||||
}
|
||||
} else {
|
||||
var skipTag = false;
|
||||
var skipTag = false;
|
||||
}
|
||||
|
||||
if (value !='' && skipTag != true) {
|
||||
|
||||
if (value !='' && skipTag != true) {
|
||||
$('<span>').addClass('tag').append(
|
||||
$('<span>').text(value).append(' '),
|
||||
$('<a>', {
|
||||
@@ -108,16 +113,16 @@
|
||||
).insertBefore('#' + id + '_addTag');
|
||||
|
||||
tagslist.push(value);
|
||||
|
||||
|
||||
$('#'+id+'_tag').val('');
|
||||
if (options.focus) {
|
||||
$('#'+id+'_tag').focus();
|
||||
} else {
|
||||
} else {
|
||||
$('#'+id+'_tag').blur();
|
||||
}
|
||||
|
||||
|
||||
$.fn.tagsInput.updateTagsField(this,tagslist);
|
||||
|
||||
|
||||
if (options.callback && tags_callbacks[id] && tags_callbacks[id]['onAddTag']) {
|
||||
var f = tags_callbacks[id]['onAddTag'];
|
||||
f.call(this, value);
|
||||
@@ -127,29 +132,29 @@
|
||||
var i = tagslist.length;
|
||||
var f = tags_callbacks[id]['onChange'];
|
||||
f.call(this, $(this), tagslist[i-1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
$.fn.removeTag = function(value) {
|
||||
|
||||
$.fn.removeTag = function(value) {
|
||||
value = unescape(value);
|
||||
this.each(function() {
|
||||
this.each(function() {
|
||||
var id = $(this).attr('id');
|
||||
|
||||
|
||||
var old = $(this).val().split(delimiter[id]);
|
||||
|
||||
|
||||
$('#'+id+'_tagsinput .tag').remove();
|
||||
str = '';
|
||||
for (i=0; i< old.length; i++) {
|
||||
if (old[i]!=value) {
|
||||
for (i=0; i< old.length; i++) {
|
||||
if (old[i]!=value) {
|
||||
str = str + delimiter[id] +old[i];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$.fn.tagsInput.importTags(this,str);
|
||||
|
||||
if (tags_callbacks[id] && tags_callbacks[id]['onRemoveTag']) {
|
||||
@@ -157,24 +162,24 @@
|
||||
f.call(this, value);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
$.fn.tagExist = function(val) {
|
||||
var id = $(this).attr('id');
|
||||
var tagslist = $(this).val().split(delimiter[id]);
|
||||
return (jQuery.inArray(val, tagslist) >= 0); //true when tag exists, false when not
|
||||
};
|
||||
|
||||
|
||||
// clear all existing tags and import new ones from a string
|
||||
$.fn.importTags = function(str) {
|
||||
id = $(this).attr('id');
|
||||
$('#'+id+'_tagsinput .tag').remove();
|
||||
$.fn.tagsInput.importTags(this,str);
|
||||
}
|
||||
|
||||
$.fn.tagsInput = function(options) {
|
||||
|
||||
$.fn.tagsInput = function(options) {
|
||||
var settings = jQuery.extend({
|
||||
interactive:true,
|
||||
defaultText:'add a tag',
|
||||
@@ -192,15 +197,15 @@
|
||||
inputPadding: 6*2
|
||||
},options);
|
||||
|
||||
this.each(function() {
|
||||
if (settings.hide) {
|
||||
$(this).hide();
|
||||
this.each(function() {
|
||||
if (settings.hide) {
|
||||
$(this).hide();
|
||||
}
|
||||
var id = $(this).attr('id');
|
||||
if (!id || delimiter[$(this).attr('id')]) {
|
||||
id = $(this).attr('id', 'tags' + new Date().getTime()).attr('id');
|
||||
}
|
||||
|
||||
|
||||
var data = jQuery.extend({
|
||||
pid:id,
|
||||
real_input: '#'+id,
|
||||
@@ -208,57 +213,58 @@
|
||||
input_wrapper: '#'+id+'_addTag',
|
||||
fake_input: '#'+id+'_tag'
|
||||
},settings);
|
||||
|
||||
|
||||
delimiter[id] = data.delimiter;
|
||||
|
||||
if (settings.onAddTag || settings.onRemoveTag || settings.onChange) {
|
||||
|
||||
if (settings.onAddTag || settings.onRemoveTag || settings.onChange || settings.preprocessTag) {
|
||||
tags_callbacks[id] = new Array();
|
||||
tags_callbacks[id]['onAddTag'] = settings.onAddTag;
|
||||
tags_callbacks[id]['onRemoveTag'] = settings.onRemoveTag;
|
||||
tags_callbacks[id]['onChange'] = settings.onChange;
|
||||
tags_callbacks[id]['preprocessTag'] = settings.preprocessTag;
|
||||
}
|
||||
|
||||
|
||||
var markup = '<div id="'+id+'_tagsinput" class="tagsinput"><div id="'+id+'_addTag">';
|
||||
|
||||
|
||||
if (settings.interactive) {
|
||||
markup = markup + '<input id="'+id+'_tag" value="" data-default="'+settings.defaultText+'" />';
|
||||
}
|
||||
|
||||
|
||||
markup = markup + '</div><div class="tags_clear"></div></div>';
|
||||
|
||||
|
||||
$(markup).insertAfter(this);
|
||||
|
||||
|
||||
|
||||
$(data.holder).css('width',settings.width);
|
||||
$(data.holder).css('min-height',settings.height);
|
||||
$(data.holder).css('height','100%');
|
||||
|
||||
if ($(data.real_input).val()!='') {
|
||||
|
||||
if ($(data.real_input).val()!='') {
|
||||
$.fn.tagsInput.importTags($(data.real_input),$(data.real_input).val());
|
||||
}
|
||||
if (settings.interactive) {
|
||||
}
|
||||
if (settings.interactive) {
|
||||
$(data.fake_input).val($(data.fake_input).attr('data-default'));
|
||||
$(data.fake_input).css('color',settings.placeholderColor);
|
||||
$(data.fake_input).resetAutosize(settings);
|
||||
|
||||
|
||||
$(data.fake_input).doAutosize(settings);
|
||||
$(data.holder).bind('click',data,function(event) {
|
||||
$(event.data.fake_input).focus();
|
||||
});
|
||||
|
||||
|
||||
$(data.fake_input).bind('focus',data,function(event) {
|
||||
if ($(event.data.fake_input).val()==$(event.data.fake_input).attr('data-default')) {
|
||||
if ($(event.data.fake_input).val()==$(event.data.fake_input).attr('data-default')) {
|
||||
$(event.data.fake_input).val('');
|
||||
}
|
||||
$(event.data.fake_input).css('color','#000000');
|
||||
$(event.data.fake_input).css('color','#000000');
|
||||
});
|
||||
|
||||
|
||||
if (settings.autocomplete_url != undefined) {
|
||||
autocomplete_options = {source: settings.autocomplete_url};
|
||||
for (attrname in settings.autocomplete) {
|
||||
autocomplete_options[attrname] = settings.autocomplete[attrname];
|
||||
for (attrname in settings.autocomplete) {
|
||||
autocomplete_options[attrname] = settings.autocomplete[attrname];
|
||||
}
|
||||
|
||||
|
||||
if (jQuery.Autocompleter !== undefined) {
|
||||
onSelectCallback = settings.autocomplete.onItemSelect;
|
||||
settings.autocomplete.onItemSelect = function() {
|
||||
@@ -278,18 +284,18 @@
|
||||
$(data.fake_input).autocomplete(autocomplete_options);
|
||||
$(data.fake_input).bind('autocompleteselect',data,function(event,ui) {
|
||||
$(event.data.real_input).addTag(ui.item.value,{focus:true,unique:(settings.unique)});
|
||||
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
} else {
|
||||
// if a user tabs out of the field, create a new tag
|
||||
// this is only available if autocomplete is not used.
|
||||
$(data.fake_input).bind('blur',data,function(event) {
|
||||
$(data.fake_input).bind('blur',data,function(event) {
|
||||
var d = $(this).attr('data-default');
|
||||
if ($(event.data.fake_input).val()!='' && $(event.data.fake_input).val()!=d) {
|
||||
if ($(event.data.fake_input).val()!='' && $(event.data.fake_input).val()!=d) {
|
||||
if( (event.data.minChars <= $(event.data.fake_input).val().length) && (!event.data.maxChars || (event.data.maxChars >= $(event.data.fake_input).val().length)) )
|
||||
$(event.data.real_input).addTag($(event.data.fake_input).val(),{focus:true,unique:(settings.unique)});
|
||||
} else {
|
||||
@@ -298,7 +304,7 @@
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
// if user types a comma, create a new tag
|
||||
$(data.fake_input).bind('keypress',data,function(event) {
|
||||
@@ -326,7 +332,7 @@
|
||||
}
|
||||
});
|
||||
$(data.fake_input).blur();
|
||||
|
||||
|
||||
//Removes the not_valid class when user changes the value of the fake input
|
||||
if(data.unique) {
|
||||
$(data.fake_input).keydown(function(event){
|
||||
@@ -337,21 +343,21 @@
|
||||
}
|
||||
} // if settings.interactive
|
||||
});
|
||||
|
||||
|
||||
return this;
|
||||
|
||||
|
||||
};
|
||||
|
||||
$.fn.tagsInput.updateTagsField = function(obj,tagslist) {
|
||||
|
||||
$.fn.tagsInput.updateTagsField = function(obj,tagslist) {
|
||||
var id = $(obj).attr('id');
|
||||
$(obj).val(tagslist.join(delimiter[id]));
|
||||
};
|
||||
|
||||
$.fn.tagsInput.importTags = function(obj,val) {
|
||||
|
||||
$.fn.tagsInput.importTags = function(obj,val) {
|
||||
$(obj).val('');
|
||||
var id = $(obj).attr('id');
|
||||
var tags = val.split(delimiter[id]);
|
||||
for (i=0; i<tags.length; i++) {
|
||||
for (i=0; i<tags.length; i++) {
|
||||
$(obj).addTag(tags[i],{focus:false,callback:false});
|
||||
}
|
||||
if(tags_callbacks[id] && tags_callbacks[id]['onChange'])
|
||||
|
||||
@@ -211,6 +211,28 @@
|
||||
|
||||
|
||||
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 {
|
||||
float: right;
|
||||
|
||||
@@ -393,6 +415,22 @@ body.discussion {
|
||||
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 {
|
||||
@include discussion-wmd-preview;
|
||||
position: relative;
|
||||
@@ -713,7 +751,9 @@ body.discussion {
|
||||
width: 100%;
|
||||
background: #737373;
|
||||
border: 1px solid #4b4b4b;
|
||||
border-left: none;
|
||||
border-radius: 0 0 3px 3px;
|
||||
@include box-shadow(1px 0 0 #4b4b4b inset);
|
||||
|
||||
.browse-topic-drop-menu {
|
||||
max-height: 400px;
|
||||
@@ -906,7 +946,7 @@ body.discussion {
|
||||
position: relative;
|
||||
display: block;
|
||||
height: 36px;
|
||||
padding: 0 10px;
|
||||
padding: 0 10px 0 18px;
|
||||
margin-bottom: 1px;
|
||||
margin-right: -1px;
|
||||
@include linear-gradient(top, rgba(255, 255, 255, .7), rgba(255, 255, 255, 0));
|
||||
@@ -917,6 +957,35 @@ body.discussion {
|
||||
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 {
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
@@ -958,6 +1027,14 @@ body.discussion {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.staff-post-icon {
|
||||
background-position: 0 -13px;
|
||||
}
|
||||
|
||||
.staff-response-icon {
|
||||
background-position: -13px -13px;
|
||||
}
|
||||
|
||||
.votes-count,
|
||||
.comments-count {
|
||||
@include linear-gradient(top, #3994c7, #4da7d3);
|
||||
@@ -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 {
|
||||
display: none;
|
||||
padding: 50px;
|
||||
@@ -1472,14 +1517,10 @@ body.discussion {
|
||||
}
|
||||
|
||||
.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 {
|
||||
/* 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 {
|
||||
padding: 0.5em;
|
||||
|
||||
@@ -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 {
|
||||
background: #444;
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<%include file="_underscore_templates.html" />
|
||||
|
||||
<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>
|
||||
|
||||
29
lms/templates/discussion/_inline_new_post.html
Normal file
29
lms/templates/discussion/_inline_new_post.html
Normal file
@@ -0,0 +1,29 @@
|
||||
<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…"></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 @@
|
||||
</%def>
|
||||
|
||||
<article class="new-post-article">
|
||||
<div class="inner-wrapper">
|
||||
<div class="new-post-form-errors">
|
||||
</div>
|
||||
<div class="inner-wrapper">
|
||||
<form class="new-post-form">
|
||||
<div class="left-column">
|
||||
<label>Create new post about:</label>
|
||||
@@ -46,6 +44,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="right-column">
|
||||
<ul class="new-post-form-errors"></ul>
|
||||
<div class="form-row">
|
||||
<input type="text" class="new-post-title" name="title" placeholder="Title">
|
||||
</div>
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<div class="discussion-body">
|
||||
<div class="sidebar"></div>
|
||||
<div class="discussion-column">
|
||||
<div class="blank-slate">
|
||||
<div class="discussion-article blank-slate">
|
||||
<h1>${course.title} discussions</h1>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,39 @@
|
||||
<section class="discussion">
|
||||
{{#threads}}
|
||||
<article class="discussion-thread" id="thread_{{id}}">
|
||||
<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…"></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>
|
||||
{{/threads}}
|
||||
|
||||
<section class="threads">
|
||||
{{#threads}}
|
||||
<article class="discussion-thread" id="thread_{{id}}">
|
||||
</article>
|
||||
{{/threads}}
|
||||
</section>
|
||||
</section>
|
||||
|
||||
@@ -1,21 +1,27 @@
|
||||
<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">
|
||||
<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>
|
||||
<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>
|
||||
<span class="post-status-closed top-post-status" style="display: none">
|
||||
• This thread is closed.
|
||||
</span>
|
||||
</p>
|
||||
<div class="local post-tools">
|
||||
<a href="javascript:void(0)" class="expand-post">Expand...</a>
|
||||
<a href="javascript:void(0)" class="collapse-post">Collapse...</a>
|
||||
</div>
|
||||
</header>
|
||||
<div class="post-body">
|
||||
{{abbreviatedBody}}
|
||||
</div>
|
||||
<div class="post-body">{{abbreviatedBody}}</div>
|
||||
<ul class="moderator-actions post-extended-content">
|
||||
<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>
|
||||
<ol class="responses post-extended-content">
|
||||
<li class="loading"><div class="loading-animation"></div></li>
|
||||
|
||||
Reference in New Issue
Block a user