Commit be04740e by Greg Price

Merge pull request #4291 from edx/forum-sidebar-improvements

Forum sidebar improvements
parents 220a028b 6d87443a
describe 'All Content', -> describe 'All Content', ->
beforeEach -> beforeEach ->
# TODO: figure out a better way of handling this DiscussionSpecHelper.setUpGlobals()
# It is set up in main.coffee DiscussionApp.start
window.$$course_id = 'edX/999/test'
window.user = new DiscussionUser {id: '567'}
describe 'Content', -> describe 'Content', ->
beforeEach -> beforeEach ->
......
class @DiscussionSpecHelper
# This is sad. We should avoid dependence on global vars.
@setUpGlobals = ->
DiscussionUtil.loadRoles({"Moderator": [], "Administrator": [], "Community TA": []})
window.$$course_id = "edX/999/test"
window.user = new DiscussionUser({id: "567", upvoted_ids: []})
describe "DiscussionContentView", -> describe "DiscussionContentView", ->
beforeEach -> beforeEach ->
DiscussionSpecHelper.setUpGlobals()
setFixtures( setFixtures(
""" """
<div class="discussion-post"> <div class="discussion-post">
...@@ -36,7 +36,6 @@ describe "DiscussionContentView", -> ...@@ -36,7 +36,6 @@ describe "DiscussionContentView", ->
@thread = new Thread(@threadData) @thread = new Thread(@threadData)
@view = new DiscussionContentView({ model: @thread }) @view = new DiscussionContentView({ model: @thread })
@view.setElement($('.discussion-post')) @view.setElement($('.discussion-post'))
window.user = new DiscussionUser({id: '567', upvoted_ids: []})
it 'defines the tag', -> it 'defines the tag', ->
expect($('#jasmine-fixtures')).toExist expect($('#jasmine-fixtures')).toExist
......
describe "DiscussionThreadShowView", -> describe "DiscussionThreadShowView", ->
beforeEach -> beforeEach ->
DiscussionSpecHelper.setUpGlobals()
setFixtures( setFixtures(
""" """
<div class="discussion-post"> <div class="discussion-post">
...@@ -14,8 +15,6 @@ describe "DiscussionThreadShowView", -> ...@@ -14,8 +15,6 @@ describe "DiscussionThreadShowView", ->
""" """
) )
window.$$course_id = "TestOrg/TestCourse/TestRun"
window.user = new DiscussionUser({id: "567", upvoted_ids: []})
@threadData = { @threadData = {
id: "dummy", id: "dummy",
user_id: user.id, user_id: user.id,
......
describe "DiscussionUserProfileView", -> describe "DiscussionUserProfileView", ->
beforeEach -> beforeEach ->
DiscussionSpecHelper.setUpGlobals()
setFixtures( setFixtures(
""" """
<script type="text/template" id="_user_profile"> <script type="text/template" id="_user_profile">
...@@ -46,7 +47,6 @@ describe "DiscussionUserProfileView", -> ...@@ -46,7 +47,6 @@ describe "DiscussionUserProfileView", ->
<div class="user-profile-fixture"/> <div class="user-profile-fixture"/>
""" """
) )
window.$$course_id = "dummy_course_id"
spyOn(DiscussionThreadProfileView.prototype, "render") spyOn(DiscussionThreadProfileView.prototype, "render")
makeView = (threads, page, numPages) -> makeView = (threads, page, numPages) ->
......
describe 'ResponseCommentShowView', -> describe 'ResponseCommentShowView', ->
beforeEach -> beforeEach ->
DiscussionSpecHelper.setUpGlobals()
# set up the container for the response to go in # set up the container for the response to go in
setFixtures """ setFixtures """
<ol class="responses"></ol> <ol class="responses"></ol>
......
describe 'ResponseCommentView', -> describe 'ResponseCommentView', ->
beforeEach -> beforeEach ->
window.$$course_id = 'edX/999/test' DiscussionSpecHelper.setUpGlobals()
window.user = new DiscussionUser {id: '567'}
DiscussionUtil.loadRoles []
@comment = new Comment { @comment = new Comment {
id: '01234567', id: '01234567',
user_id: user.id, user_id: user.id,
......
describe "ThreadResponseShowView", -> describe "ThreadResponseShowView", ->
beforeEach -> beforeEach ->
DiscussionSpecHelper.setUpGlobals()
setFixtures( setFixtures(
""" """
<div class="discussion-post"> <div class="discussion-post">
...@@ -22,7 +23,6 @@ describe "ThreadResponseShowView", -> ...@@ -22,7 +23,6 @@ describe "ThreadResponseShowView", ->
@comment = new Comment(@commentData) @comment = new Comment(@commentData)
@view = new ThreadResponseShowView({ model: @comment }) @view = new ThreadResponseShowView({ model: @comment })
@view.setElement($(".discussion-post")) @view.setElement($(".discussion-post"))
window.user = new DiscussionUser({id: "567", upvoted_ids: []})
it "renders the vote correctly", -> it "renders the vote correctly", ->
DiscussionViewSpecHelper.checkRenderVote(@view, @comment) DiscussionViewSpecHelper.checkRenderVote(@view, @comment)
......
...@@ -53,9 +53,12 @@ if Backbone? ...@@ -53,9 +53,12 @@ if Backbone?
initialize: -> initialize: ->
Content.addContent @id, @ Content.addContent @id, @
userId = @get('user_id')
@set('staff_authored', DiscussionUtil.isStaff(userId))
@set('community_ta_authored', DiscussionUtil.isTA(userId))
if Content.getInfo(@id) if Content.getInfo(@id)
@updateInfo(Content.getInfo(@id)) @updateInfo(Content.getInfo(@id))
@set 'user_url', DiscussionUtil.urlFor('user_profile', @get('user_id')) @set 'user_url', DiscussionUtil.urlFor('user_profile', userId)
@resetComments(@get('children')) @resetComments(@get('children'))
remove: -> remove: ->
......
...@@ -62,9 +62,9 @@ if Backbone? ...@@ -62,9 +62,9 @@ if Backbone?
new_threads = [new Thread(data) for data in response.discussion_data][0] new_threads = [new Thread(data) for data in response.discussion_data][0]
new_collection = _.union(models, new_threads) new_collection = _.union(models, new_threads)
Content.loadContentInfos(response.annotated_content_info) Content.loadContentInfos(response.annotated_content_info)
@reset new_collection
@pages = response.num_pages @pages = response.num_pages
@current_page = response.page @current_page = response.page
@reset new_collection
error: error error: error
sortByDate: (thread) -> sortByDate: (thread) ->
......
...@@ -20,6 +20,8 @@ if Backbone? ...@@ -20,6 +20,8 @@ if Backbone?
Backbone.history.start({pushState: true, root: "/courses/#{$$course_id}/discussion/forum/"}) Backbone.history.start({pushState: true, root: "/courses/#{$$course_id}/discussion/forum/"})
DiscussionProfileApp = DiscussionProfileApp =
start: (elem) -> start: (elem) ->
# Roles are not included in user profile page, but they are not used for anything
DiscussionUtil.loadRoles({"Moderator": [], "Administrator": [], "Community TA": []})
element = $(elem) element = $(elem)
window.$$course_id = element.data("course-id") window.$$course_id = element.data("course-id")
threads = element.data("threads") threads = element.data("threads")
......
...@@ -32,12 +32,12 @@ class @DiscussionUtil ...@@ -32,12 +32,12 @@ class @DiscussionUtil
@loadFlagModerator($("#discussion-container").data("flag-moderator")) @loadFlagModerator($("#discussion-container").data("flag-moderator"))
@isStaff: (user_id) -> @isStaff: (user_id) ->
user_id ?= @user.id user_id ?= @user?.id
staff = _.union(@roleIds['Moderator'], @roleIds['Administrator']) staff = _.union(@roleIds['Moderator'], @roleIds['Administrator'])
_.include(staff, parseInt(user_id)) _.include(staff, parseInt(user_id))
@isTA: (user_id) -> @isTA: (user_id) ->
user_id ?= @user.id user_id ?= @user?.id
ta = _.union(@roleIds['Community TA']) ta = _.union(@roleIds['Community TA'])
_.include(ta, parseInt(user_id)) _.include(ta, parseInt(user_id))
...@@ -93,6 +93,10 @@ class @DiscussionUtil ...@@ -93,6 +93,10 @@ class @DiscussionUtil
"notifications_status" : "/notification_prefs/status/" "notifications_status" : "/notification_prefs/status/"
}[name] }[name]
@ignoreEnterKey: (event) =>
if event.which == 13
event.preventDefault()
@activateOnSpace: (event, func) -> @activateOnSpace: (event, func) ->
if event.which == 32 if event.which == 32
event.preventDefault() event.preventDefault()
......
...@@ -179,19 +179,20 @@ class DiscussionSortPreferencePage(CoursePage): ...@@ -179,19 +179,20 @@ class DiscussionSortPreferencePage(CoursePage):
""" """
Return true if the browser is on the right page else false. Return true if the browser is on the right page else false.
""" """
return self.q(css="body.discussion .sort-bar").present return self.q(css="body.discussion .forum-nav-sort-control").present
def get_selected_sort_preference_text(self): def get_selected_sort_preference(self):
""" """
Return the text of option that is selected for sorting. Return the text of option that is selected for sorting.
""" """
return self.q(css="body.discussion .sort-bar a.active").text[0].lower() options = self.q(css="body.discussion .forum-nav-sort-control option")
return options.filter(lambda el: el.is_selected())[0].get_attribute("value")
def change_sort_preference(self, sort_by): def change_sort_preference(self, sort_by):
""" """
Change the option of sorting by clicking on new option. Change the option of sorting by clicking on new option.
""" """
self.q(css="body.discussion .sort-bar a[data-sort='{0}']".format(sort_by)).click() self.q(css="body.discussion .forum-nav-sort-control option[value='{0}']".format(sort_by)).click()
def refresh_page(self): def refresh_page(self):
""" """
...@@ -352,12 +353,7 @@ class DiscussionTabHomePage(CoursePage, DiscussionPageMixin): ...@@ -352,12 +353,7 @@ class DiscussionTabHomePage(CoursePage, DiscussionPageMixin):
return self.q(css=".discussion-body section.home-header").present return self.q(css=".discussion-body section.home-header").present
def perform_search(self, text="dummy"): def perform_search(self, text="dummy"):
self.q(css=".discussion-body .sidebar .search").first.click() self.q(css=".forum-nav-search-input").fill(text + chr(10))
EmptyPromise(
lambda: self.q(css=".discussion-body .sidebar .search.is-open").present,
"waiting for search input to be available"
).fulfill()
self.q(css="#search-discussions").fill(text + chr(10))
EmptyPromise( EmptyPromise(
self.is_ajax_finished, self.is_ajax_finished,
"waiting for server to return result" "waiting for server to return result"
......
...@@ -509,7 +509,7 @@ class DiscussionSortPreferenceTest(UniqueCourseTest): ...@@ -509,7 +509,7 @@ class DiscussionSortPreferenceTest(UniqueCourseTest):
""" """
Test to check the default sorting preference of user. (Default = date ) Test to check the default sorting preference of user. (Default = date )
""" """
selected_sort = self.sort_page.get_selected_sort_preference_text() selected_sort = self.sort_page.get_selected_sort_preference()
self.assertEqual(selected_sort, "date") self.assertEqual(selected_sort, "date")
def test_change_sort_preference(self): def test_change_sort_preference(self):
...@@ -520,7 +520,7 @@ class DiscussionSortPreferenceTest(UniqueCourseTest): ...@@ -520,7 +520,7 @@ class DiscussionSortPreferenceTest(UniqueCourseTest):
for sort_type in ["votes", "comments", "date"]: for sort_type in ["votes", "comments", "date"]:
self.assertNotEqual(selected_sort, sort_type) self.assertNotEqual(selected_sort, sort_type)
self.sort_page.change_sort_preference(sort_type) self.sort_page.change_sort_preference(sort_type)
selected_sort = self.sort_page.get_selected_sort_preference_text() selected_sort = self.sort_page.get_selected_sort_preference()
self.assertEqual(selected_sort, sort_type) self.assertEqual(selected_sort, sort_type)
def test_last_preference_saved(self): def test_last_preference_saved(self):
...@@ -531,8 +531,8 @@ class DiscussionSortPreferenceTest(UniqueCourseTest): ...@@ -531,8 +531,8 @@ class DiscussionSortPreferenceTest(UniqueCourseTest):
for sort_type in ["votes", "comments", "date"]: for sort_type in ["votes", "comments", "date"]:
self.assertNotEqual(selected_sort, sort_type) self.assertNotEqual(selected_sort, sort_type)
self.sort_page.change_sort_preference(sort_type) self.sort_page.change_sort_preference(sort_type)
selected_sort = self.sort_page.get_selected_sort_preference_text() selected_sort = self.sort_page.get_selected_sort_preference()
self.assertEqual(selected_sort, sort_type) self.assertEqual(selected_sort, sort_type)
self.sort_page.refresh_page() self.sort_page.refresh_page()
selected_sort = self.sort_page.get_selected_sort_preference_text() selected_sort = self.sort_page.get_selected_sort_preference()
self.assertEqual(selected_sort, sort_type) self.assertEqual(selected_sort, sort_type)
...@@ -48,8 +48,12 @@ ...@@ -48,8 +48,12 @@
@import 'views/shoppingcart'; @import 'views/shoppingcart';
// applications // applications
@import 'discussion/discussion'; @import "discussion/utilities/variables";
@import 'discussion/discussion-developer'; @import 'discussion/discussion'; // Process old file after definitions but before everything else
@import "discussion/elements/navigation";
@import 'discussion/utilities/developer';
@import 'discussion/utilities/shame';
@import 'news'; @import 'news';
// temp - shame and developer // temp - shame and developer
......
.forum-nav {
@include box-sizing(border-box);
float: left;
border: 1px solid #aaa;
border-radius: 3px;
}
// ------
// Header
// ------
.forum-nav-header {
@include box-sizing(border-box);
display: table;
border-bottom: 1px solid $gray-l2;
background-color: $gray-l3;
}
.forum-nav-browse {
@include box-sizing(border-box);
display: table-cell;
vertical-align: middle;
width: 50%;
padding: ($baseline/4);
&:hover, &:focus, &.is-active {
background-color: $gray-l5;
}
.icon {
margin-right: ($baseline/4);
}
}
.forum-nav-browse-current {
@include font-size(12);
}
.forum-nav-browse-drop-arrow {
margin-left: ($baseline/4);
}
.forum-nav-search {
@include box-sizing(border-box);
display: table-cell;
position: relative;
vertical-align: middle;
width: 50%;
padding: ($baseline/4);
}
.forum-nav-search .icon {
@include font-size(12);
position: absolute;
margin-top: -6px;
top: 50%;
right: ($baseline/4 + 1px + $baseline / 4); // Wrapper padding + border + input padding
}
.forum-nav-search-input {
width: 100%;
}
// -----------
// Browse menu
// -----------
.forum-nav-browse-menu-wrapper {
overflow-y: scroll;
border-bottom: 1px solid $gray-l3;
background: $gray-l5;
}
.forum-nav-browse-filter {
position: relative;
border-bottom: 1px solid $gray-l2;
padding: ($baseline/4);
}
.forum-nav-browse-filter .icon {
@include font-size(12);
position: absolute;
margin-top: -6px;
top: 50%;
right: ($baseline/4 + 1px + $baseline / 4); // Wrapper padding + border + input padding
}
.forum-nav-browse-filter-input {
width: 100%;
}
.forum-nav-browse-title .icon {
margin-right: ($baseline/2);
}
// -------------------
// Sort and filter bar
// -------------------
.forum-nav-refine-bar {
@include clearfix();
@include font-size(11);
border-bottom: 1px solid $gray-l3;
background-color: $gray-l5;
padding: ($baseline/4) ($baseline/2);
color: $black;
}
%forum-nav-select {
border: none;
max-width: 100%;
background-color: transparent;
font: inherit;
}
.forum-nav-filter-cohort-control {
@extend %forum-nav-select;
}
.forum-nav-sort {
float: right;
}
.forum-nav-sort-control {
@extend %forum-nav-select;
}
// -----------
// Thread list
// -----------
.forum-nav-thread-list {
overflow-y: scroll;
}
.forum-nav-thread {
border-bottom: 1px solid $gray-l3;
}
.forum-nav-thread-link {
@include clearfix();
}
%forum-nav-thread-wrapper {
display: inline-block;
vertical-align: middle;
}
.forum-nav-thread-wrapper-1 {
@extend %forum-nav-thread-wrapper;
width: 70%;
}
.forum-nav-thread-wrapper-2 {
@extend %forum-nav-thread-wrapper;
width: 30%;
text-align: right;
}
.forum-nav-thread-title {
@extend %t-title7;
display: block;
}
%forum-nav-thread-label {
@extend %t-weight4;
@include font-size(9);
display: inline;
border: 1px solid;
border-radius: 3px;
text-transform: uppercase;
white-space: nowrap;
&:last-child {
margin-right: 0;
}
.icon {
margin-right: ($baseline/5);
}
}
.forum-nav-thread-label-pinned {
@extend %forum-nav-thread-label;
border-color: $forum-color-pinned;
color: $forum-color-pinned;
}
.forum-nav-thread-label-following {
@extend %forum-nav-thread-label;
border-color: $forum-color-following;
color: $forum-color-following;
}
.forum-nav-thread-label-staff {
@extend %forum-nav-thread-label;
border-color: $forum-color-staff;
color: $forum-color-staff;
}
.forum-nav-thread-label-community-ta {
@extend %forum-nav-thread-label;
border-color: $forum-color-community-ta;
color: $forum-color-community-ta;
}
%forum-nav-thread-wrapper-2-content {
@include font-size(11);
display: inline-block;
margin-right: ($baseline/4);
text-align: center;
color: $black;
&:last-child {
margin-right: 0;
}
}
.forum-nav-thread-endorsed {
@extend %forum-nav-thread-wrapper-2-content;
color: $green-d1;
}
.forum-nav-thread-votes-count {
@extend %forum-nav-thread-wrapper-2-content;
}
.forum-nav-thread-comments-count {
@extend %forum-nav-thread-wrapper-2-content;
@extend %t-weight4;
position: relative;
margin-left: ($baseline/4);
margin-bottom: ($baseline/4); // Because tail is position: absolute
border-radius: 2px;
padding: ($baseline/10) ($baseline/5);
min-width: 2em; // Fit most comment counts but allow expansion if necessary
background-color: $gray-l3;
// Speech bubble tail
&:after {
content: '';
display: block;
position: absolute;
bottom: (-$baseline/4);
right: ($baseline/4);
width: 0;
height: 0;
border-style: solid;
border-width: 0 ($baseline/4) ($baseline/4) 0;
border-color: transparent $gray-l3 transparent transparent;
}
&.is-unread {
background-color: $white;
&:after {
border-right-color: $white
}
}
}
.forum-nav-thread.is-unread .forum-nav-thread-comments-count {
background-color: $blue;
color: $white;
&:after {
border-right-color: $blue;
}
}
%forum-nav-load-more-content {
text-align: center;
}
.forum-nav-load-more-link {
@extend %forum-nav-load-more-content;
color: $link-color;
}
.forum-nav-loading {
@extend %forum-nav-load-more-content;
}
// -------------------
// navigation - header
// -------------------
// Override global a rules
.forum-nav-browse {
color: $black !important;
}
// Override global label rules
.forum-nav-search label {
margin-bottom: 0;
}
// Override global input rules
.forum-nav-search-input {
box-shadow: none !important;
border: 1px solid $gray-l2 !important;
border-radius: 3px !important;
height: auto !important;
padding-left: ($baseline/4) !important;
padding-right: ($baseline/2 + 12px) !important; // Leave room for icon
font-size: 12px !important;
}
// Firefox does not compute the correct containing box for absolute positioning
// of .forum-nav-search .icon, so there's an extra div to make it happy
.forum-nav-search-ff-position-fix {
position: relative;
}
// The sidebar class does a lot of things that we don't want in the thread list;
// the following rules contain styling that is necessary and would otherwise
// reside in elements/_navigation.scss if the sidebar styling did not make the
// !important directive necessary.
.forum-nav {
width: 31% !important;
}
// ------------------------
// navigation - browse menu
// ------------------------
// Override global a rules
.forum-nav-browse-title {
color: inherit !important;
}
// Override global label rules
.forum-nav-browse-filter label {
margin-bottom: 0;
}
// Override global input rules
.forum-nav-browse-filter-input {
box-shadow: none !important;
border-radius: 3px !important;
height: auto !important;
padding-left: ($baseline/4) !important;
padding-right: ($baseline/2 + 12px) !important; // Leave room for icon
font-size: 12px !important;
}
// The sidebar class does a lot of things that we don't want in the thread list;
// the following rules contain styling that is necessary and would otherwise
// reside in elements/_navigation.scss if the sidebar styling did not make the
// !important directive necessary.
.forum-nav-browse-title {
border-bottom: 1px solid $gray-l3 !important;
padding: ($baseline/2) ($baseline/2) !important;
&:hover, &:focus {
background: $forum-color-active-thread !important;
}
}
.forum-nav-browse-submenu {
padding-left: $baseline !important;
}
// --------------------------------
// navigation - sort and filter bar
// --------------------------------
// Override global span rules
.forum-nav-sort-label {
color: inherit;
}
// --------------------------------
// navigation - thread list
// --------------------------------
// The sidebar class does a lot of things that we don't want in the thread list;
// the following rules contain styling that is necessary and would otherwise
// reside in elements/_navigation.scss if the sidebar styling did not make the
// !important directive necessary.
.forum-nav-thread {
background-color: $gray-l5 !important;
&.is-unread {
background-color: $white !important;
}
}
.forum-nav-thread-link {
padding: ($baseline/4) ($baseline/2) !important;
&.is-active, &:hover, &:focus {
background-color: $forum-color-active-thread !important;
}
}
li[class*=forum-nav-thread-label-] {
margin-top: ($baseline/4) !important;
padding: 1px 6px !important;
}
.forum-nav-load-more {
border-bottom: 1px solid $gray-l3 !important;
background-color: $gray-l5 !important;
}
.forum-nav-load-more-link {
&:hover, &:focus {
color: $link-color !important;
background-color: $forum-color-active-thread !important;
}
}
.forum-nav-load-more-link, .forum-nav-loading {
padding: $baseline 0 !important;
}
// The following rules would be unnecessary but for broadly scoped rules defined
// elsewhere in our CSS.
li[class*=forum-nav-thread-label-] {
// Override global span rules
span {
color: inherit;
}
// Override clearfix stuff in .sidebar ul li rules
&:before, &:after {
display: none !important;
}
}
$forum-color-active-thread: tint($blue, 85%);
$forum-color-pinned: $pink;
$forum-color-following: $blue;
$forum-color-staff: $blue;
$forum-color-community-ta: $green-d1;
...@@ -12,41 +12,43 @@ ...@@ -12,41 +12,43 @@
</%def> </%def>
<%def name="render_entry(entries, entry)"> <%def name="render_entry(entries, entry)">
<li><a href="#" class="drop-menu-entry"><span class="board-name" data-discussion_id='${json.dumps(entries[entry])}' cohorted = "${str(entries[entry]['is_cohorted']).lower()}">${entry}</span></a></li> <li
class="forum-nav-browse-menu-item"
data-discussion-id='${json.dumps(entries[entry])}'
data-cohorted="${str(entries[entry]['is_cohorted']).lower()}"
>
<a href="#" class="forum-nav-browse-title">${entry}</a>
</li>
</%def> </%def>
<%def name="render_category(categories, category)"> <%def name="render_category(categories, category)">
<li> <li class="forum-nav-browse-menu-item">
<a href="#" class="drop-menu-parent-category"><span class="board-name">${category}</span></a> <a href="#" class="forum-nav-browse-title">${category}</a>
<ul> <ul class="forum-nav-browse-submenu">
${render_dropdown(categories[category])} ${render_dropdown(categories[category])}
</ul> </ul>
</li> </li>
</%def> </%def>
<div class="browse-topic-drop-menu-wrapper"> <div class="forum-nav-browse-menu-wrapper" style="display: none">
<div class="browse-topic-drop-search"> <form class="forum-nav-browse-filter">
<label class="sr" for="browse-topic">${_("Filter Topics")}</label> <label>
<input type="text" id="browse-topic" class="browse-topic-drop-search-input" placeholder="${_('filter topics')}"> <span class="sr">${_("Filter Topics")}</span>
</div> <input type="text" class="forum-nav-browse-filter-input" placeholder="${_("filter topics")}">
<ul class="browse-topic-drop-menu"> <i class="icon icon-filter"></i>
<li> </label>
<a href="#" class="drop-menu-meta-category"> </form>
<span class="board-name" data-discussion_id='#all'>${_("Show All Discussions")}</span> <ul class="forum-nav-browse-menu">
</a> <li class="forum-nav-browse-menu-item forum-nav-browse-menu-all">
<a href="#" class="forum-nav-browse-title">${_("All Discussions")}</a>
</li> </li>
%if flag_moderator: %if flag_moderator:
<li> <li class="forum-nav-browse-menu-item forum-nav-browse-menu-flagged">
<a href="#"> <a href="#" class="forum-nav-browse-title"><i class="icon icon-flag"></i>${_("Flagged Discussions")}</a>
<span class="board-name" data-discussion_id='#flagged'><i class="icon-flag" style="padding-right:5px;"></i>${_("Show Flagged Discussions")}</span>
</a>
</li> </li>
%endif %endif
<li> <li class="forum-nav-browse-menu-item forum-nav-browse-menu-following">
<a href="#" class="drop-menu-meta-category"> <a href="#" class="forum-nav-browse-title"><i class="icon icon-star"></i>${_("Posts I'm Following")}</a>
<span class="board-name" data-discussion_id='#following'><i class="icon-star" style="padding-right:5px;"></i>${_("Posts I'm Following")}</span>
</a>
</li> </li>
${render_dropdown(category_map)} ${render_dropdown(category_map)}
</ul> </ul>
......
<%! from django.utils.translation import ugettext as _ %> <%! from django.utils.translation import ugettext as _ %>
<script type="text/template" id="thread-list-template"> <script type="text/template" id="thread-list-template">
<div class="browse-search"> <div class="forum-nav-header">
<div class="home"> <a href="#" class="forum-nav-browse" aria-haspopup="true">
<a href="#" class="home-icon"> ## There is no whitespace between these because the front-end JS code
<i class="icon icon-home"></i> ## needs to precisely compute the available width for forum-nav-
<span class="sr">${_("Discussion Home")}</span> ## browse-current in order to do truncation of topic names.
</a> <i class="icon icon-reorder"></i><span class="sr">${_("Discussion topics; current selection is: ")}</span><span class="forum-nav-browse-current">${_("All Discussions")}</span><span class="forum-nav-browse-drop-arrow">▾</span>
</div> </a>
<div class="browse is-open"> <form class="forum-nav-search">
<a href="#" class="browse-topic-drop-icon"> <div class="forum-nav-search-ff-position-fix">
<i class="icon icon-reorder"></i> <label>
<span class="sr">${_("Discussion Topics")}</span> <span class="sr">${_("Search")}</span>
</a> <input class="forum-nav-search-input" type="text" placeholder="${_("Search all posts")}">
<a href="#" class="browse-topic-drop-btn" aria-haspopup="true" aria-owns="browse-topic-drop-menu"> <i class="icon icon-search"></i>
<span class="sr">${_("Discussion topics; current selection is: ")}</span> </label>
<span class="current-board">${_("Show All Discussions")}</span> </div>
<span class="drop-arrow" aria-hidden="true"></span> </form>
</a>
</div>
<%include file="_filter_dropdown.html" />
<div class="search">
<form class="post-search">
<label class="sr" for="search-discussions">${_("Search")}</label>
<input type="text" id="search-discussions" placeholder="${_("Search all discussions")}" class="post-search-field">
</form>
</div>
</div> </div>
<div class="sort-bar"> <%include file="_filter_dropdown.html" />
<span class="sort-label" id="sort-label">${_("Sort by:")}</span> <div class="forum-nav-thread-list-wrapper">
<ul role="radiogroup" aria-labelledby="sort-label"> <div class="forum-nav-refine-bar">
<li><a href="#" role="radio" aria-checked="false" data-sort="date">${_("date")}</a></li> %if is_course_cohorted and is_moderator:
<li><a href="#" role="radio" aria-checked="false" data-sort="votes">${_("votes")}</a></li> <span class="forum-nav-filter-cohort">
<li><a href="#" role="radio" aria-checked="false" data-sort="comments">${_("comments")}</a></li> <select class="forum-nav-filter-cohort-control">
</ul> <option value="all">${_("View all cohorts")}</option>
%for c in cohorts:
<option value="${c['id']}">${_("View as {cohort_name}").format(cohort_name=c['name'])}</option>
%endfor
</select>
</span>
%endif
%if is_course_cohorted and is_moderator: <span class="forum-nav-sort">
<span class="group-filter-label cohort">${_("Show:")}</span> <select class="forum-nav-sort-control">
<select class="group-filter-select cohort-options cohort"> ## Translators: This is a menu option for sorting forum threads
<option value="all">${_("View All")}</option> <option value="date">${_("by recent activity")}</option>
%for c in cohorts: ## Translators: This is a menu option for sorting forum threads
<option value="${c['id']}">${_("View as {name}").format(name=c['name'])}</option> <option value="comments">${_("by most activity")}</option>
%endfor ## Translators: This is a menu option for sorting forum threads
</select> <option value="votes">${_("by most votes")}</option>
%endif </select>
</div> </span>
<div class="search-alerts"></div> </div>
<div class="post-list-wrapper"> <div class="search-alerts"></div>
<ul class="post-list"> <ul class="forum-nav-thread-list"></ul>
</ul>
</div> </div>
</script> </script>
...@@ -205,16 +205,67 @@ ...@@ -205,16 +205,67 @@
</script> </script>
<script aria-hidden="true" type="text/template" id="thread-list-item-template"> <script aria-hidden="true" type="text/template" id="thread-list-item-template">
<a href="${'<%- id %>'}" data-id="${'<%- id %>'}"> <li data-id="${'<%- id %>'}" class="forum-nav-thread${'<% if (typeof(read) != "undefined" && !read) { %> is-unread<% } %>'}">
<span class="title">${"<%- title %>"}</span> <a href="#" class="forum-nav-thread-link">
<div class="forum-nav-thread-wrapper-1">
<span class="forum-nav-thread-title">${"<%- title %>"}</span>
<% <%
js_block = u"""
var labels = "";
if (pinned) {{
labels += '<li class="forum-nav-thread-label-pinned"><i class="icon icon-pushpin"></i>{pinned_text}</li> ';
}}
if (typeof(subscribed) != "undefined" && subscribed) {{
labels += '<li class="forum-nav-thread-label-following"><i class="icon icon-star"></i>{following_text}</li> ';
}}
if (staff_authored) {{
labels += '<li class="forum-nav-thread-label-staff"><i class="icon icon-user"></i>{staff_text}</li> ';
}}
if (community_ta_authored) {{
labels += '<li class="forum-nav-thread-label-community-ta"><i class="icon icon-user"></i>{community_ta_text}</li> ';
}}
if (labels != "") {{
print('<ul class="forum-nav-thread-labels">' + labels + '</ul>');
}}
""".format(
## Translators: This is a label for a forum thread that has been pinned
pinned_text=escapejs(_("Pinned")),
## Translators: This is a label for a forum thread that the user is subscribed to
following_text=escapejs(_("Following")),
## Translators: This is a label for a forum thread that was authored by a member of the course staff
staff_text=escapejs(_("By: Staff")),
## Translators: This is a label for a forum thread that was authored by a community TA
community_ta_text=escapejs(_("By: Community TA"))
)
%>
${"<%"}${js_block}${"%>"}
</div><div class="forum-nav-thread-wrapper-2">
${"<% if (endorsed) { %>"}
## Translators: This is a label for a forum thread with a response that was endorsed by the course staff
<span class="forum-nav-thread-endorsed"><i class="icon icon-ok"></i><span class="sr">${_("Endorsed response")}</span></span>
${"<% } %>"}
<%
js_block = u"""
interpolate(
'{}',
{{'span_sr_open': '<span class=\"sr\">', 'span_close': '</span>', 'votes_up_count': votes['up_count']}},
true
)
""".format(
## Translators: 'votes_up_count' is a numerical placeholder for a specific discussion thread; 'span_*' placeholders refer to HTML markup. Please translate the word 'votes'.
escapejs( _('%(votes_up_count)s%(span_sr_open)s votes %(span_close)s'))
)
%>
<span class="forum-nav-thread-votes-count">+${'<%='}${js_block}${'%>'}</span>
<%
js_block = u""" js_block = u"""
var fmt; var fmt;
// Counts in data do not include the post itself, but the UI should
var data = {{ var data = {{
'span_sr_open': '<span class=\"sr\">', 'span_sr_open': '<span class=\"sr\">',
'span_close': '</span>', 'span_close': '</span>',
'unread_comments_count': unread_comments_count, 'unread_comments_count': unread_comments_count + (read ? 0 : 1),
'comments_count': comments_count 'comments_count': comments_count + 1
}}; }};
if (unread_comments_count > 0) {{ if (unread_comments_count > 0) {{
fmt = '{markup_with_unread}'; fmt = '{markup_with_unread}';
...@@ -229,24 +280,14 @@ ...@@ -229,24 +280,14 @@
markup_none_unread=escapejs(_('%(comments_count)s %(span_sr_open)scomments %(span_close)s')) markup_none_unread=escapejs(_('%(comments_count)s %(span_sr_open)scomments %(span_close)s'))
) )
%> %>
<span class="comments-count"> <span class="forum-nav-thread-comments-count ${'<% if (unread_comments_count > 0) { %>is-unread<% } %>'}">
${'<%'}${js_block}${'%>'} ${'<%'}${js_block}${'%>'}
</span> </span>
<% </div>
js_block = u"""
interpolate(
'{}',
{{'span_sr_open': '<span class=\"sr\">', 'span_close': '</span>', 'votes_up_count': votes['up_count']}},
true
)
""".format(
## Translators: 'votes_up_count' is a numerical placeholder for a specific discussion thread; 'span_*' placeholders refer to HTML markup. Please translate the word 'votes'.
escapejs( _('%(votes_up_count)s%(span_sr_open)s votes %(span_close)s'))
)
%>
<span class="votes-count">+${'<%='}${js_block}${'%>'}</span>
</a> </a>
</li>
</script> </script>
<script aria-hidden="true" type="text/template" id="discussion-home"> <script aria-hidden="true" type="text/template" id="discussion-home">
<div class="discussion-article blank-slate"> <div class="discussion-article blank-slate">
<section class="home-header"> <section class="home-header">
......
...@@ -37,7 +37,7 @@ ...@@ -37,7 +37,7 @@
data-user-cohort-id="${user_cohort}" data-user-cohort-id="${user_cohort}"
data-course-settings="${course_settings}"> data-course-settings="${course_settings}">
<div class="discussion-body"> <div class="discussion-body">
<div class="sidebar"></div> <div class="sidebar forum-nav"></div>
<div class="discussion-column"> <div class="discussion-column">
</div> </div>
</div> </div>
......
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