Commit faabb01d by Tom Giannattasio

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 1515919f 795c1163
...@@ -174,7 +174,7 @@ def forum_form_discussion(request, course_id): ...@@ -174,7 +174,7 @@ def forum_form_discussion(request, course_id):
return render_to_response('discussion/index.html', context) return render_to_response('discussion/index.html', context)
def render_single_thread(request, discussion_id, course_id, thread_id): def render_single_thread(request, discussion_id, course_id, thread_id):
thread = cc.Thread.find(thread_id).retrieve(recursive=True).to_dict() thread = cc.Thread.find(thread_id).retrieve(recursive=True).to_dict()
threads, query_params = get_threads(request, course_id) threads, query_params = get_threads(request, course_id)
...@@ -212,6 +212,7 @@ def single_thread(request, course_id, discussion_id, thread_id): ...@@ -212,6 +212,7 @@ def single_thread(request, course_id, discussion_id, thread_id):
else: else:
course = get_course_with_access(request.user, course_id, 'load') course = get_course_with_access(request.user, course_id, 'load')
category_map = utils.get_discussion_category_map(course)
threads, query_params = get_threads(request, course_id) threads, query_params = get_threads(request, course_id)
recent_active_threads = cc.search_recent_active_threads( recent_active_threads = cc.search_recent_active_threads(
...@@ -238,6 +239,7 @@ def single_thread(request, course_id, discussion_id, thread_id): ...@@ -238,6 +239,7 @@ def single_thread(request, course_id, discussion_id, thread_id):
'course_id': course.id, 'course_id': course.id,
'thread_id': thread_id, 'thread_id': thread_id,
'threads': json.dumps(threads), 'threads': json.dumps(threads),
'category_map': category_map,
} }
return render_to_response('discussion/single_thread.html', context) return render_to_response('discussion/single_thread.html', context)
......
from collections import defaultdict
from importlib import import_module from importlib import import_module
from courseware.models import StudentModuleCache from courseware.models import StudentModuleCache
from courseware.module_render import get_module from courseware.module_render import get_module
...@@ -21,8 +22,6 @@ import pystache_custom as pystache ...@@ -21,8 +22,6 @@ import pystache_custom as pystache
_FULLMODULES = None _FULLMODULES = None
_DISCUSSIONINFO = None _DISCUSSIONINFO = None
def extract(dic, keys): def extract(dic, keys):
return {k: dic.get(k) for k in keys} return {k: dic.get(k) for k in keys}
...@@ -40,80 +39,101 @@ def merge_dict(dic1, dic2): ...@@ -40,80 +39,101 @@ def merge_dict(dic1, dic2):
def get_full_modules(): def get_full_modules():
global _FULLMODULES global _FULLMODULES
if not _FULLMODULES: if not _FULLMODULES:
class_path = settings.MODULESTORE['default']['ENGINE'] _FULLMODULES = modulestore().modules
module_path, _, class_name = class_path.rpartition('.')
class_ = getattr(import_module(module_path), class_name)
modulestore = class_(**dict(settings.MODULESTORE['default']['OPTIONS'].items() + [('eager', True)]))
_FULLMODULES = modulestore.modules
return _FULLMODULES return _FULLMODULES
def get_categorized_discussion_info(request, course): def get_discussion_id_map(course):
""" """
return a dict of the form {category: modules} return a dict of the form {category: modules}
""" """
global _DISCUSSIONINFO global _DISCUSSIONINFO
if not _DISCUSSIONINFO: if not _DISCUSSIONINFO:
initialize_discussion_info(request, course) initialize_discussion_info(course)
return _DISCUSSIONINFO['categorized'] return _DISCUSSIONINFO['id_map']
def get_discussion_title(request, course, discussion_id): def get_discussion_title(course, discussion_id):
global _DISCUSSIONINFO global _DISCUSSIONINFO
if not _DISCUSSIONINFO: if not _DISCUSSIONINFO:
initialize_discussion_info(request, course) initialize_discussion_info(course)
title = _DISCUSSIONINFO['by_id'].get(discussion_id, {}).get('title', '(no title)') title = _DISCUSSIONINFO['by_id'].get(discussion_id, {}).get('title', '(no title)')
return title return title
def initialize_discussion_info(request, course): def get_discussion_category_map(course):
global _DISCUSSIONINFO
if not _DISCUSSIONINFO:
initialize_discussion_info(course)
return _DISCUSSIONINFO['category_map']
def initialize_discussion_info(course):
global _DISCUSSIONINFO global _DISCUSSIONINFO
if _DISCUSSIONINFO: if _DISCUSSIONINFO:
return return
course_id = course.id course_id = course.id
_, course_name, _ = course_id.split('/')
user = request.user
url_course_id = course_id.replace('/', '_').replace('.', '_') url_course_id = course_id.replace('/', '_').replace('.', '_')
_is_course_discussion = lambda x: x[0].dict()['category'] == 'discussion' \ all_modules = get_full_modules()[course_id]
and x[0].dict()['course'] == course_name
discussion_id_map = {}
_get_module_descriptor = operator.itemgetter(1) unexpanded_category_map = defaultdict(list)
for location, module in all_modules.items():
def _get_module(module_descriptor): if location.category == 'discussion':
print module_descriptor id = module.metadata['id']
module = get_module(user, request, module_descriptor.location, student_module_cache) category = module.metadata['discussion_category']
return module title = module.metadata['for']
sort_key = module.metadata.get('sort_key', title)
def _extract_info(module): discussion_id_map[id] = {"location": location, "title": title}
return { category = " / ".join([x.strip() for x in category.split("/")])
'title': module.title, unexpanded_category_map[category].append({"title": title, "id": id,
'discussion_id': module.discussion_id, "sort_key": sort_key})
'category': module.discussion_category,
} category_map = {"entries": defaultdict(dict), "subcategories": defaultdict(dict)}
for category_path, entries in unexpanded_category_map.items():
def _pack_with_id(info): node = category_map["subcategories"]
return (info['discussion_id'], info) path = [x.strip() for x in category_path.split("/")]
for level in path[:-1]:
discussion_module_descriptors = map(_get_module_descriptor, if level not in node:
filter(_is_course_discussion, node[level] = {"subcategories": defaultdict(dict),
get_full_modules().items())) "entries": defaultdict(dict),
"sort_key": level}
student_module_cache = StudentModuleCache.cache_for_descriptor_descendents(user, course) node = node[level]["subcategories"]
discussion_info = map(_extract_info, map(_get_module, discussion_module_descriptors)) level = path[-1]
if level not in node:
node[level] = {"subcategories": defaultdict(dict),
"entries": defaultdict(dict),
"sort_key": level}
for entry in entries:
node[level]["entries"][entry["title"]] = {"id": entry["id"],
"sort_key": entry["sort_key"]}
def sort_map_entries(map):
things = []
for title, entry in map["entries"].items():
things.append((title, entry))
for title, category in map["subcategories"].items():
things.append((title, category))
sort_map_entries(map["subcategories"][title])
map["children"] = [x[0] for x in sorted(things, key=lambda x: x[1]["sort_key"])]
sort_map_entries(category_map)
#for level in category_map["subcategories"].values():
# sort_map_entries(level)
_DISCUSSIONINFO = {} _DISCUSSIONINFO = {}
_DISCUSSIONINFO['by_id'] = dict(map(_pack_with_id, discussion_info)) _DISCUSSIONINFO['id_map'] = discussion_id_map
_DISCUSSIONINFO['categorized'] = dict((category, list(l)) \ _DISCUSSIONINFO['category_map'] = category_map
for category, l in itertools.groupby(discussion_info, operator.itemgetter('category')))
_DISCUSSIONINFO['categorized']['General'] = [{ # TODO delete me when you've used me
'title': 'General discussion', #_DISCUSSIONINFO['categorized']['General'] = [{
'discussion_id': url_course_id, # 'title': 'General',
'category': 'General', # 'discussion_id': url_course_id,
}] # 'category': 'General',
#}]
class JsonResponse(HttpResponse): class JsonResponse(HttpResponse):
def __init__(self, data=None): def __init__(self, data=None):
......
...@@ -429,13 +429,13 @@ if Backbone? ...@@ -429,13 +429,13 @@ if Backbone?
display_body: -> display_body: ->
if @has("highlighted_body") if @has("highlighted_body")
@get("highlighted_body") String(@get("highlighted_body")).replace(/<highlight>/g, '<mark>').replace(/<\/highlight>/g, '</mark>')
else else
@get("body") @get("body")
display_title: -> display_title: ->
if @has("highlighted_title") if @has("highlighted_title")
@get("highlighted_title") String(@get("highlighted_title")).replace(/<highlight>/g, '<mark>').replace(/<\/highlight>/g, '</mark>')
else else
@get("title") @get("title")
......
...@@ -2,8 +2,10 @@ class @DiscussionThreadListView extends Backbone.View ...@@ -2,8 +2,10 @@ class @DiscussionThreadListView extends Backbone.View
template: _.template($("#thread-list-template").html()) template: _.template($("#thread-list-template").html())
events: events:
"click .search": "showSearch" "click .search": "showSearch"
"click .browse": "toggleTopicDrop"
"keyup .post-search-field": "performSearch" "keyup .post-search-field": "performSearch"
"click .sort-bar a": "sortThreads" "click .sort-bar a": "sortThreads"
"click .board-drop-menu": "setTopic"
render: -> render: ->
@timer = 0; @timer = 0;
...@@ -32,10 +34,38 @@ class @DiscussionThreadListView extends Backbone.View ...@@ -32,10 +34,38 @@ class @DiscussionThreadListView extends Backbone.View
@$("a[data-id='#{thread_id}']").addClass("active") @$("a[data-id='#{thread_id}']").addClass("active")
showSearch: -> showSearch: ->
@$(".search").addClass('is-open'); @$(".search").addClass('is-open')
@$(".browse").removeClass('is-open'); @$(".browse").removeClass('is-open')
setTimeout (-> @$(".post-search-field").focus()), 200 setTimeout (-> @$(".post-search-field").focus()), 200
toggleTopicDrop: =>
@$(".browse").toggleClass('is-dropped')
if @$(".browse").hasClass('is-dropped')
@$(".board-drop-menu").show()
setTimeout((=>
$("body").bind("click", @toggleTopicDrop)
), 0)
else
@$(".board-drop-menu").hide()
$("body").unbind("click", @toggleTopicDrop)
setTopic: (e) ->
item = $(e.target).closest('a')
boardName = item.find(".board-name").html()
_.each item.parents('ul').not('.board-drop-menu'), (parent) ->
console.log(parent)
boardName = $(parent).siblings('a').find('.board-name').html() + ' / ' + boardName
@$(".current-board").html(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');
sortThreads: (event) -> sortThreads: (event) ->
@$(".sort-bar a").removeClass("active") @$(".sort-bar a").removeClass("active")
$(event.target).addClass("active") $(event.target).addClass("active")
...@@ -64,9 +94,7 @@ class @DiscussionThreadListView extends Backbone.View ...@@ -64,9 +94,7 @@ class @DiscussionThreadListView extends Backbone.View
url: url url: url
type: "GET" type: "GET"
success: (response, textStatus) => success: (response, textStatus) =>
console.log textStatus
if textStatus == 'success' if textStatus == 'success'
@collection.reset(response.discussion_data) @collection.reset(response.discussion_data)
console.log(@collection)
@delay(callback, 300) @delay(callback, 300)
...@@ -6,6 +6,7 @@ class @DiscussionThreadView extends Backbone.View ...@@ -6,6 +6,7 @@ class @DiscussionThreadView extends Backbone.View
template: _.template($("#thread-template").html()) template: _.template($("#thread-template").html())
render: -> render: ->
console.log(@model)
@$el.html(@template(@model.toJSON())) @$el.html(@template(@model.toJSON()))
@model.bind "change", @updateModelDetails @model.bind "change", @updateModelDetails
if window.user.following(@model) if window.user.following(@model)
...@@ -24,7 +25,7 @@ class @DiscussionThreadView extends Backbone.View ...@@ -24,7 +25,7 @@ class @DiscussionThreadView extends Backbone.View
convertMath: -> convertMath: ->
element = @$(".post-body") element = @$(".post-body")
element.html DiscussionUtil.postMathJaxProcessor DiscussionUtil.markdownWithHighlight element.html() element.html DiscussionUtil.postMathJaxProcessor(element.html())
MathJax.Hub.Queue ["Typeset", MathJax.Hub, element.attr("id")] MathJax.Hub.Queue ["Typeset", MathJax.Hub, element.attr("id")]
renderResponses: -> renderResponses: ->
......
...@@ -26,8 +26,8 @@ var SIDEBAR_HEADER_HEIGHT = 87; ...@@ -26,8 +26,8 @@ var SIDEBAR_HEADER_HEIGHT = 87;
$(document).ready(function() { $(document).ready(function() {
$body = $('body'); $body = $('body');
$browse = $('.browse-search .browse'); $browse = $('.browse-search .browse');
// $search = $('.browse-search .search'); $search = $('.browse-search .search');
// $searchField = $('.post-search-field'); $searchField = $('.post-search-field');
$topicDrop = $('.board-drop-menu'); $topicDrop = $('.board-drop-menu');
$currentBoard = $('.current-board'); $currentBoard = $('.current-board');
$tooltip = $('<div class="tooltip"></div>'); $tooltip = $('<div class="tooltip"></div>');
...@@ -43,7 +43,7 @@ $(document).ready(function() { ...@@ -43,7 +43,7 @@ $(document).ready(function() {
sidebarXOffset = $sidebar.offset().top; sidebarXOffset = $sidebar.offset().top;
$browse.bind('click', showTopicDrop); $browse.bind('click', showTopicDrop);
// $search.bind('click', showSearch); $search.bind('click', showSearch);
$topicDrop.bind('click', setTopic); $topicDrop.bind('click', setTopic);
$formTopicDropBtn.bind('click', showFormTopicDrop); $formTopicDropBtn.bind('click', showFormTopicDrop);
$formTopicDropMenu.bind('click', setFormTopic); $formTopicDropMenu.bind('click', setFormTopic);
...@@ -111,13 +111,13 @@ function showBrowse(e) { ...@@ -111,13 +111,13 @@ function showBrowse(e) {
$searchField.val(''); $searchField.val('');
} }
// function showSearch(e) { function showSearch(e) {
// $search.addClass('is-open'); $search.addClass('is-open');
// $browse.removeClass('is-open'); $browse.removeClass('is-open');
// setTimeout(function() { setTimeout(function() {
// $searchField.focus(); $searchField.focus();
// }, 200); }, 200);
// } }
function showTopicDrop(e) { function showTopicDrop(e) {
e.preventDefault(); e.preventDefault();
...@@ -229,4 +229,4 @@ function updateSidebarDimensions(e) { ...@@ -229,4 +229,4 @@ function updateSidebarDimensions(e) {
$postListWrapper.css('height', (sidebarHeight - SIDEBAR_HEADER_HEIGHT - 4) + 'px'); $postListWrapper.css('height', (sidebarHeight - SIDEBAR_HEADER_HEIGHT - 4) + 'px');
$sidebarWidthStyles.html('.discussion-body .post-list a .title { width: ' + titleWidth + 'px !important; }'); $sidebarWidthStyles.html('.discussion-body .post-list a .title { width: ' + titleWidth + 'px !important; }');
} }
\ No newline at end of file
<%def name="render_dropdown(map)">
% for child in map["children"]:
% if child in map["entries"]:
${render_entry(map["entries"], child)}
%else:
${render_category(map["subcategories"], child)}
%endif
%endfor
</%def>
<%def name="render_entry(entries, entry)">
<li><a href="#"><span class="board-name" data-discussion_id="${entries[entry]}">${entry}</span> <span class="unread">1,248</span></a></li>
</%def>
<%def name="render_category(categories, category)">
<li>
<a href="#"><span class="board-name">${category}</span></a>
<ul>
${render_dropdown(categories[category])}
</ul>
</li>
</%def>
<ul class="board-drop-menu">
${render_dropdown(category_map)}
</ul>
...@@ -132,24 +132,7 @@ ...@@ -132,24 +132,7 @@
<a href="#" class="board-drop-icon"></a> <a href="#" class="board-drop-icon"></a>
<a href="#" class="board-drop-btn"><span class="current-board">Homework / Week 1</span> <span class="drop-arrow">▾</span></a> <a href="#" class="board-drop-btn"><span class="current-board">Homework / Week 1</span> <span class="drop-arrow">▾</span></a>
</div> </div>
<ul class="board-drop-menu"> <%include file="_filter_dropdown.html" />
<li><a href="#"><span class="board-name">All</span> <span class="unread">1,248</span></a></li>
<li><a href="#"><span class="board-name">Following</span></a></li>
<li><a href="#"><span class="board-name">General</span> <span class="unread">124</span></a></li>
<li>
<a href="#"><span class="board-name">Homework</span> <span class="unread">27</span></a>
<ul>
<li><a href="#"><span class="board-name">Week 1</span> <span class="unread">24</span></a></li>
<li>
<a href="#"><span class="board-name">Week 2</span> <span class="unread">27</span></a>
<ul>
<li><a href="#"><span class="board-name">Problem 1</span> <span class="unread">14</span></a></li>
<li><a href="#"><span class="board-name">Problem 2</span> <span class="unread">13</span></a></li>
</ul>
</li>
</ul>
</li>
</ul>
<div class="search"> <div class="search">
<form class="post-search"> <form class="post-search">
<input type="text" placeholder="Search all discussions" class="post-search-field"> <input type="text" placeholder="Search all discussions" class="post-search-field">
......
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