Merge branch 'feature/tomg/new-discussions' of github.com:MITx/mitx into feature/tomg/new-discussions
Conflicts: lms/static/coffee/src/discussion/views/discussion_thread_view.coffee lms/static/coffee/src/discussion/views/thread_list_item_view.coffee
This commit is contained in:
@@ -13,7 +13,7 @@ from courseware.access import has_access
|
||||
from urllib import urlencode
|
||||
from operator import methodcaller
|
||||
from django_comment_client.permissions import check_permissions_by_view
|
||||
from django_comment_client.utils import merge_dict, extract, strip_none, strip_blank
|
||||
from django_comment_client.utils import merge_dict, extract, strip_none, strip_blank, get_courseware_context
|
||||
|
||||
import json
|
||||
import django_comment_client.utils as utils
|
||||
@@ -71,6 +71,15 @@ def render_discussion(request, course_id, threads, *args, **kwargs):
|
||||
|
||||
annotated_content_info = reduce(merge_dict, map(infogetter, threads), {})
|
||||
|
||||
if discussion_type != 'inline':
|
||||
course = get_course_with_access(request.user, course_id, 'load')
|
||||
|
||||
for thread in threads:
|
||||
courseware_context = get_courseware_context(thread, course)
|
||||
if courseware_context:
|
||||
thread['courseware_location'] = courseware_context['courseware_location']
|
||||
thread['courseware_title'] = courseware_context['courseware_title']
|
||||
|
||||
context = {
|
||||
'threads': threads,
|
||||
'discussion_id': discussion_id,
|
||||
@@ -231,15 +240,23 @@ def single_thread(request, course_id, discussion_id, thread_id):
|
||||
category_map = utils.get_discussion_category_map(course)
|
||||
threads, query_params = get_threads(request, course_id)
|
||||
|
||||
recent_active_threads = cc.search_recent_active_threads(
|
||||
course_id,
|
||||
recursive=False,
|
||||
query_params={'follower_id': request.user.id},
|
||||
)
|
||||
course = get_course_with_access(request.user, course_id, 'load')
|
||||
|
||||
trending_tags = cc.search_trending_tags(
|
||||
course_id,
|
||||
)
|
||||
for thread in threads:
|
||||
courseware_context = get_courseware_context(thread, course)
|
||||
if courseware_context:
|
||||
thread['courseware_location'] = courseware_context['courseware_location']
|
||||
thread['courseware_title'] = courseware_context['courseware_title']
|
||||
|
||||
#recent_active_threads = cc.search_recent_active_threads(
|
||||
# course_id,
|
||||
# recursive=False,
|
||||
# query_params={'follower_id': request.user.id},
|
||||
#)
|
||||
|
||||
#trending_tags = cc.search_trending_tags(
|
||||
# course_id,
|
||||
#)
|
||||
|
||||
user_info = cc.User.from_django_user(request.user).to_dict()
|
||||
escapedict = {'"': '"'}
|
||||
@@ -256,8 +273,8 @@ def single_thread(request, course_id, discussion_id, thread_id):
|
||||
'user_info': saxutils.escape(json.dumps(user_info),escapedict),
|
||||
'annotated_content_info': saxutils.escape(json.dumps(annotated_content_info), escapedict),
|
||||
'course': course,
|
||||
'recent_active_threads': recent_active_threads,
|
||||
'trending_tags': trending_tags,
|
||||
#'recent_active_threads': recent_active_threads,
|
||||
#'trending_tags': trending_tags,
|
||||
'course_id': course.id,
|
||||
'thread_id': thread_id,
|
||||
'threads': saxutils.escape(json.dumps(threads), escapedict),
|
||||
|
||||
@@ -124,10 +124,7 @@ def initialize_discussion_info(course):
|
||||
category_map['entries'][topic] = {"id": entry["id"],
|
||||
"sort_key": entry.get("sort_key", topic)}
|
||||
|
||||
|
||||
sort_map_entries(category_map)
|
||||
#for level in category_map["subcategories"].values():
|
||||
# sort_map_entries(level)
|
||||
|
||||
_DISCUSSIONINFO = {}
|
||||
|
||||
@@ -135,12 +132,15 @@ def initialize_discussion_info(course):
|
||||
|
||||
_DISCUSSIONINFO['category_map'] = category_map
|
||||
|
||||
# TODO delete me when you've used me
|
||||
#_DISCUSSIONINFO['categorized']['General'] = [{
|
||||
# 'title': 'General',
|
||||
# 'discussion_id': url_course_id,
|
||||
# 'category': 'General',
|
||||
#}]
|
||||
def get_courseware_context(content, course):
|
||||
id_map = get_discussion_id_map(course)
|
||||
id = content['commentable_id']
|
||||
content_info = None
|
||||
if id in id_map:
|
||||
location = id_map[id]["location"].url()
|
||||
title = id_map[id]["title"]
|
||||
content_info = { "courseware_location": location, "courseware_title": title}
|
||||
return content_info
|
||||
|
||||
class JsonResponse(HttpResponse):
|
||||
def __init__(self, data=None):
|
||||
|
||||
@@ -449,6 +449,9 @@ if Backbone?
|
||||
@set('thread', @)
|
||||
super()
|
||||
|
||||
comment: ->
|
||||
@set("comments_count", parseInt(@get("comments_count")) + 1)
|
||||
|
||||
follow: ->
|
||||
@set('subscribed', true)
|
||||
@trigger "thread:follow"
|
||||
|
||||
@@ -15,8 +15,9 @@ class @DiscussionRouter extends Backbone.Router
|
||||
@newPostView.on "thread:created", @navigateToThread
|
||||
|
||||
allThreads: ->
|
||||
@nav.updateSidebar()
|
||||
# TODO: Do something reasonable here
|
||||
$(".discussion-column").html("No thread selected.")
|
||||
# $(".discussion-column").html($('#blank-slate-template').html())
|
||||
|
||||
setActiveThread: =>
|
||||
if @thread
|
||||
@@ -30,6 +31,8 @@ class @DiscussionRouter extends Backbone.Router
|
||||
|
||||
@main = new DiscussionThreadView(el: $(".discussion-column"), model: @thread)
|
||||
@main.render()
|
||||
@main.on "thread:responses:rendered", =>
|
||||
@nav.updateSidebar()
|
||||
|
||||
navigateToThread: (thread_id) =>
|
||||
thread = @discussion.get(thread_id)
|
||||
|
||||
@@ -10,6 +10,46 @@ class @DiscussionThreadListView extends Backbone.View
|
||||
|
||||
initialize: ->
|
||||
@displayedCollection = new Discussion(@collection.models)
|
||||
@collection.on "change", @reloadDisplayedCollection
|
||||
@sidebar_padding = 10
|
||||
@sidebar_header_height = 87
|
||||
|
||||
reloadDisplayedCollection: =>
|
||||
@displayedCollection.reset(@collection.models)
|
||||
@updateSidebar()
|
||||
|
||||
updateSidebar: =>
|
||||
|
||||
scrollTop = $(window).scrollTop();
|
||||
windowHeight = $(window).height();
|
||||
|
||||
discussionBody = $(".discussion-article")
|
||||
discussionsBodyTop = discussionBody.offset().top;
|
||||
discussionsBodyBottom = discussionsBodyTop + discussionBody.outerHeight();
|
||||
|
||||
sidebar = $(".sidebar")
|
||||
if scrollTop > discussionsBodyTop - @sidebar_padding
|
||||
sidebar.addClass('fixed');
|
||||
sidebar.css('top', @sidebar_padding);
|
||||
else
|
||||
sidebar.removeClass('fixed');
|
||||
sidebar.css('top', '0');
|
||||
|
||||
sidebarWidth = .32 * $(".discussion-body").width() - 10;
|
||||
sidebar.css('width', sidebarWidth + 'px');
|
||||
|
||||
sidebarHeight = windowHeight - Math.max(discussionsBodyTop - scrollTop, @sidebar_padding)
|
||||
|
||||
topOffset = scrollTop + windowHeight
|
||||
discussionBottomOffset = discussionsBodyBottom + @sidebar_padding
|
||||
amount = Math.max(topOffset - discussionBottomOffset, 0)
|
||||
|
||||
sidebarHeight = sidebarHeight - @sidebar_padding - amount
|
||||
sidebar.css 'height', Math.min(Math.max(sidebarHeight, 400), discussionBody.outerHeight())
|
||||
|
||||
postListWrapper = @$('.post-list-wrapper')
|
||||
postListWrapper.css('height', (sidebarHeight - @sidebar_header_height - 4) + 'px');
|
||||
|
||||
|
||||
# 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.
|
||||
@@ -20,6 +60,10 @@ class @DiscussionThreadListView extends Backbone.View
|
||||
render: ->
|
||||
@timer = 0
|
||||
@$el.html(@template())
|
||||
|
||||
$(window).bind "scroll", @updateSidebar
|
||||
$(window).bind "resize", @updateSidebar
|
||||
|
||||
@displayedCollection.on "reset", @renderThreads
|
||||
@displayedCollection.on "thread:remove", @renderThreads
|
||||
@renderThreads()
|
||||
@@ -60,9 +104,11 @@ class @DiscussionThreadListView extends Backbone.View
|
||||
if @$(".browse").hasClass('is-dropped')
|
||||
@$(".browse-topic-drop-menu-wrapper").show()
|
||||
$('body').bind 'click', @toggleTopicDrop
|
||||
$('body').bind 'keyup', @setActiveItem
|
||||
else
|
||||
@$(".browse-topic-drop-menu-wrapper").hide()
|
||||
$('body').unbind 'click', @toggleTopicDrop
|
||||
$('body').unbind 'keyup', @setActiveItem
|
||||
|
||||
setTopic: (event) ->
|
||||
item = $(event.target).closest('a')
|
||||
@@ -102,10 +148,6 @@ class @DiscussionThreadListView extends Backbone.View
|
||||
@displayedCollection.comparator = @displayedCollection.sortByComments
|
||||
@displayedCollection.sort()
|
||||
|
||||
delay: (callback, ms) =>
|
||||
clearTimeout(@timer)
|
||||
@timer = setTimeout(callback, ms)
|
||||
|
||||
performSearch: (event) ->
|
||||
if event.which == 13
|
||||
event.preventDefault()
|
||||
@@ -120,3 +162,35 @@ class @DiscussionThreadListView extends Backbone.View
|
||||
if textStatus == 'success'
|
||||
@collection.reset(response.discussion_data)
|
||||
@displayedCollection.reset(@collection.models)
|
||||
|
||||
|
||||
setActiveItem: (event) ->
|
||||
if event.which == 13
|
||||
console.log($(".browse-topic-drop-menu-wrapper .focused"))
|
||||
$(".browse-topic-drop-menu-wrapper .focused").click()
|
||||
return
|
||||
if event.which != 40 && event.which != 38
|
||||
return
|
||||
event.preventDefault()
|
||||
|
||||
items = $(".browse-topic-drop-menu-wrapper a").not(".hidden")
|
||||
totalItems = items.length
|
||||
index = $(".browse-topic-drop-menu-wrapper .focused").parent().index()
|
||||
# index = parseInt($(".browse-topic-drop-menu-wrapper").attr("data-focused")) || 0
|
||||
|
||||
|
||||
if event.which == 40
|
||||
index = index + 1
|
||||
else if event.which == 38
|
||||
index = index - 1
|
||||
if index == totalItems
|
||||
index = 0
|
||||
|
||||
console.log(index)
|
||||
|
||||
$(".browse-topic-drop-menu-wrapper .focused").removeClass("focused")
|
||||
$(".browse-topic-drop-menu-wrapper li").eq(index).find('a').addClass("focused")
|
||||
# $(items[index]).addClass("focused")
|
||||
$(".browse-topic-drop-menu-wrapper").attr("data-focused", index)
|
||||
|
||||
|
||||
|
||||
@@ -10,12 +10,18 @@ class @DiscussionThreadView extends DiscussionContentView
|
||||
|
||||
template: _.template($("#thread-template").html())
|
||||
|
||||
initLocal: ->
|
||||
@$local = @$el.children(".discussion-article").children(".local")
|
||||
@$delegateElement = @$local
|
||||
|
||||
initialize: ->
|
||||
super()
|
||||
@model.on "change", @updateModelDetails
|
||||
|
||||
render: ->
|
||||
@$el.html(@template(@model.toJSON()))
|
||||
@initLocal()
|
||||
@delegateEvents()
|
||||
@renderDogear()
|
||||
@renderVoted()
|
||||
@renderAttrs()
|
||||
@@ -23,6 +29,8 @@ class @DiscussionThreadView extends DiscussionContentView
|
||||
Markdown.makeWmdEditor @$(".reply-body"), "", DiscussionUtil.urlFor("upload"), (text) -> DiscussionUtil.postMathJaxProcessor(text)
|
||||
@convertMath()
|
||||
@renderResponses()
|
||||
@highlight @$(".post-body")
|
||||
@highlight @$("h1")
|
||||
@
|
||||
|
||||
renderDogear: ->
|
||||
@@ -48,19 +56,20 @@ class @DiscussionThreadView extends DiscussionContentView
|
||||
DiscussionUtil.safeAjax
|
||||
url: "/courses/#{$$course_id}/discussion/forum/#{@model.get('commentable_id')}/threads/#{@model.id}"
|
||||
success: (data, textStatus, xhr) =>
|
||||
@$(".loading").remove()
|
||||
@$el.find(".loading").remove()
|
||||
Content.loadContentInfos(data['annotated_content_info'])
|
||||
comments = new Comments(data['content']['children'])
|
||||
comments.each @renderResponse
|
||||
@trigger "thread:responses:rendered"
|
||||
|
||||
renderResponse: (response) =>
|
||||
view = new ThreadResponseView(model: response)
|
||||
view.on "comment:add", @addComment
|
||||
view.render()
|
||||
@$(".responses").append(view.el)
|
||||
@$el.find(".responses").append(view.el)
|
||||
|
||||
addComment: =>
|
||||
|
||||
@model.comment()
|
||||
|
||||
toggleVote: (event) ->
|
||||
event.preventDefault()
|
||||
@@ -72,6 +81,7 @@ class @DiscussionThreadView extends DiscussionContentView
|
||||
toggleFollowing: (event) ->
|
||||
$elem = $(event.target)
|
||||
url = null
|
||||
console.log "follow"
|
||||
if not @model.get('subscribed')
|
||||
@model.follow()
|
||||
url = @model.urlFor("follow")
|
||||
@@ -129,6 +139,7 @@ class @DiscussionThreadView extends DiscussionContentView
|
||||
if not confirm "Are you sure to delete thread \"#{@model.get('title')}\"?"
|
||||
return
|
||||
@model.remove()
|
||||
@$el.empty()
|
||||
$elem = $(event.target)
|
||||
DiscussionUtil.safeAjax
|
||||
$elem: $elem
|
||||
@@ -162,3 +173,6 @@ class @DiscussionThreadView extends DiscussionContentView
|
||||
type: "POST"
|
||||
success: (response, textStatus) =>
|
||||
@model.set('endorsed', not endorsed)
|
||||
|
||||
highlight: (el) ->
|
||||
el.html(el.html().replace(/<mark>/g, "<mark>").replace(/<\/mark>/g, "</mark>"))
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
class @ThreadListItemView extends Backbone.View
|
||||
tagName: "li"
|
||||
template: _.template($("#thread-list-item-template").html())
|
||||
|
||||
events:
|
||||
"click a": "threadSelected"
|
||||
|
||||
initialize: ->
|
||||
@model.on "change", @render
|
||||
@model.on "thread:remove", @threadRemoved
|
||||
@@ -10,10 +12,12 @@ class @ThreadListItemView extends Backbone.View
|
||||
@model.on "thread:unfollow", @unfollow
|
||||
@model.on "comment:add", @addComment
|
||||
@model.on "comment:remove", @removeComment
|
||||
|
||||
render: =>
|
||||
@$el.html(@template(@model.toJSON()))
|
||||
if window.user.following(@model)
|
||||
@follow()
|
||||
@highlight @$(".title")
|
||||
@
|
||||
|
||||
threadSelected: (event) ->
|
||||
@@ -34,3 +38,9 @@ class @ThreadListItemView extends Backbone.View
|
||||
|
||||
removeComment: (comment) =>
|
||||
@$(".comments-count").html(@model.get('comments_count'))
|
||||
|
||||
#addComment: =>
|
||||
# @$(".comments-count").html(parseInt(@$(".comments-count").html()) + 1)
|
||||
|
||||
highlight: (el) ->
|
||||
el.html(el.html().replace(/<mark>/g, "<mark>").replace(/<\/mark>/g, "</mark>"))
|
||||
|
||||
@@ -67,13 +67,13 @@ $(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();
|
||||
// $(window).bind('resize', updateSidebar);
|
||||
// $(window).bind('scroll', updateSidebar);
|
||||
// $('.discussion-column').bind("input", function (e) {
|
||||
// console.log("resized");
|
||||
// updateSidebar();
|
||||
// })
|
||||
// updateSidebar();
|
||||
});
|
||||
|
||||
function filterDrop(e) {
|
||||
@@ -132,7 +132,7 @@ function filterDrop(e) {
|
||||
return;
|
||||
}
|
||||
|
||||
$items.hide();
|
||||
$items.addClass('hidden');
|
||||
$items.each(function(i) {
|
||||
var thisText = $(this).children().not('.unread').text();
|
||||
$(this).parents('ul').siblings('a').not('.unread').each(function(i) {
|
||||
@@ -147,7 +147,7 @@ function filterDrop(e) {
|
||||
}
|
||||
|
||||
if(test) {
|
||||
$(this).show();
|
||||
$(this).removeClass('hidden');
|
||||
|
||||
// show children
|
||||
$(this).parent().find('a').show();
|
||||
@@ -225,6 +225,7 @@ function showTopicDrop(e) {
|
||||
|
||||
$topicDrop.show();
|
||||
$browse.unbind('click', showTopicDrop);
|
||||
$body.bind('keyup', setActiveDropItem);
|
||||
$browse.bind('click', hideTopicDrop);
|
||||
setTimeout(function() {
|
||||
$body.bind('click', hideTopicDrop);
|
||||
|
||||
@@ -732,8 +732,14 @@ body.discussion {
|
||||
line-height: 22px;
|
||||
color: #fff;
|
||||
@include clearfix;
|
||||
@include transition(none);
|
||||
|
||||
&:hover {
|
||||
&.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&.focused {
|
||||
background-color: #636363;
|
||||
}
|
||||
|
||||
@@ -1022,6 +1028,16 @@ body.discussion {
|
||||
|
||||
|
||||
|
||||
.bottom-post-status {
|
||||
padding: 30px;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: #ccc;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.discussion-column {
|
||||
float: right;
|
||||
@@ -1038,9 +1054,17 @@ body.discussion {
|
||||
}
|
||||
}
|
||||
|
||||
.blank-slate h1 {
|
||||
margin-top: 195px;
|
||||
text-align: center;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.blank-slate,
|
||||
.discussion-article {
|
||||
position: relative;
|
||||
padding: 40px;
|
||||
min-height: 468px;
|
||||
|
||||
h1 {
|
||||
margin-bottom: 10px;
|
||||
@@ -1056,6 +1080,12 @@ body.discussion {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.post-context{
|
||||
margin-top: 20px;
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
p + p {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<div class="left-column">
|
||||
<label>Create new post in:</label>
|
||||
<div class="form-topic-drop">
|
||||
<a href="#" class="topic_dropdown_button">Homework / Week 1 <span class="drop-arrow">▾</span></a>
|
||||
<a href="#" class="topic_dropdown_button">All<span class="drop-arrow">▾</span></a>
|
||||
<div class="topic_menu_wrapper">
|
||||
<div class="topic_menu_search">
|
||||
<input type="text" class="form-topic-drop-search-input" placeholder="filter topics">
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="browse-search">
|
||||
<div class="browse is-open">
|
||||
<a href="#" class="browse-topic-drop-icon"></a>
|
||||
<a href="#" class="browse-topic-drop-btn"><span class="current-board">Homework / Week 1</span> <span class="drop-arrow">▾</span></a>
|
||||
<a href="#" class="browse-topic-drop-btn"><span class="current-board">All</span> <span class="drop-arrow">▾</span></a>
|
||||
</div>
|
||||
<%include file="_filter_dropdown.html" />
|
||||
<div class="search">
|
||||
@@ -23,4 +23,4 @@
|
||||
<ul class="post-list">
|
||||
</ul>
|
||||
</div>
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -13,6 +13,11 @@
|
||||
<div class="post-body">
|
||||
${'<%- body %>'}
|
||||
</div>
|
||||
${'<% if (obj.courseware_location) { %>'}
|
||||
<div class="post-context">
|
||||
(this post is about <a href="../../jump_to/${'<%- courseware_location %>'}">${'<%- courseware_title %>'}</a>)
|
||||
</div>
|
||||
${'<% } %>'}
|
||||
<div class="post-status-closed" style="display: none">
|
||||
This thread is closed.
|
||||
</div>
|
||||
@@ -25,6 +30,9 @@
|
||||
<ol class="responses">
|
||||
<li class="loading"><div class="loading-animation"></div></li>
|
||||
</ol>
|
||||
<div class="post-status-closed bottom-post-status" style="display: none">
|
||||
This thread is closed.
|
||||
</div>
|
||||
<form class="discussion-reply-new" data-id="${'<%- id %>'}">
|
||||
<h4>Post a response:</h4>
|
||||
<ul class="discussion-errors"></ul>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<div class="sidebar-comments-count"><span>${profiled_user['comments_count'] | h}</span> ${pluralize('comment', profiled_user['comments_count']) | h}</div>
|
||||
% if check_permissions_by_view(user, course.id, content=None, name='update_moderator_status'):
|
||||
% if "Moderator" in role_names:
|
||||
<a href="javascript:void(0)" class="sidebar-toggle-moderator-button sidebar-revoke-moderator-button">Revoke Moderator provileges</a>
|
||||
<a href="javascript:void(0)" class="sidebar-toggle-moderator-button sidebar-revoke-moderator-button">Revoke Moderator rights</a>
|
||||
% else:
|
||||
<a href="javascript:void(0)" class="sidebar-toggle-moderator-button sidebar-promote-moderator-button">Promote to Moderator</a>
|
||||
% endif
|
||||
|
||||
@@ -26,7 +26,12 @@
|
||||
<section class="discussion container" id="discussion-container" data-course-id="${course_id}" data-user-info="${user_info}" data-threads="${threads}">
|
||||
<div class="discussion-body">
|
||||
<div class="sidebar"></div>
|
||||
<div class="discussion-column"></div>
|
||||
<div class="discussion-column">
|
||||
<div class="blank-slate">
|
||||
<h1>${course.title} discussion forum</h1>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user