diff --git a/common/static/common/js/discussion/views/discussion_thread_list_view.js b/common/static/common/js/discussion/views/discussion_thread_list_view.js index 7ab85715d6..5ac5c91295 100644 --- a/common/static/common/js/discussion/views/discussion_thread_list_view.js +++ b/common/static/common/js/discussion/views/discussion_thread_list_view.js @@ -1,7 +1,6 @@ -/* globals _, Backbone, Content, Discussion, DiscussionUtil, DiscussionThreadView, DiscussionThreadListView */ +/* globals _, Backbone, Content, Discussion, DiscussionUtil */ (function() { 'use strict'; - /* eslint-disable */ var __hasProp = {}.hasOwnProperty, __extends = function(child, parent) { for (var key in parent) { @@ -18,7 +17,6 @@ child.__super__ = parent.prototype; return child; }; - /* eslint-enable */ if (typeof Backbone !== 'undefined' && Backbone !== null) { this.DiscussionThreadListView = (function(_super) { @@ -38,6 +36,24 @@ this.chooseFilter = function() { return DiscussionThreadListView.prototype.chooseFilter.apply(self, arguments); }; + this.keyboardBinding = function() { + return DiscussionThreadListView.prototype.keyboardBinding.apply(self, arguments); + }; + this.filterTopics = function() { + return DiscussionThreadListView.prototype.filterTopics.apply(self, arguments); + }; + this.toggleBrowseMenu = function() { + return DiscussionThreadListView.prototype.toggleBrowseMenu.apply(self, arguments); + }; + this.hideBrowseMenu = function() { + return DiscussionThreadListView.prototype.hideBrowseMenu.apply(self, arguments); + }; + this.showBrowseMenu = function() { + return DiscussionThreadListView.prototype.showBrowseMenu.apply(self, arguments); + }; + this.isBrowseMenuVisible = function() { + return DiscussionThreadListView.prototype.isBrowseMenuVisible.apply(self, arguments); + }; this.threadRemoved = function() { return DiscussionThreadListView.prototype.threadRemoved.apply(self, arguments); }; @@ -56,6 +72,9 @@ this.renderThreads = function() { return DiscussionThreadListView.prototype.renderThreads.apply(self, arguments); }; + this.updateSidebar = function() { + return DiscussionThreadListView.prototype.updateSidebar.apply(self, arguments); + }; this.addAndSelectThread = function() { return DiscussionThreadListView.prototype.addAndSelectThread.apply(self, arguments); }; @@ -71,16 +90,17 @@ this.addSearchAlert = function() { return DiscussionThreadListView.prototype.addSearchAlert.apply(self, arguments); }; - this.performSearch = function() { - return DiscussionThreadListView.prototype.performSearch.apply(self, arguments); - }; - return DiscussionThreadListView.__super__.constructor.apply(this, arguments); // eslint-disable-line no-underscore-dangle, max-len + return DiscussionThreadListView.__super__.constructor.apply(this, arguments); } DiscussionThreadListView.prototype.events = { 'keypress .forum-nav-browse-filter-input': function(event) { return DiscussionUtil.ignoreEnterKey(event); }, + 'keyup .forum-nav-browse-filter-input': 'filterTopics', + 'keydown .forum-nav-browse-filter-input': 'keyboardBinding', + 'click .forum-nav-browse-menu-wrapper': 'ignoreClick', + 'click .forum-nav-browse-title': 'selectTopicHandler', 'change .forum-nav-sort-control': 'sortThreads', 'click .forum-nav-thread-link': 'threadSelected', 'click .forum-nav-load-more-link': 'loadMorePages', @@ -97,6 +117,8 @@ this.collection.on('change', this.reloadDisplayedCollection); this.discussionIds = ''; this.collection.on('reset', function(discussion) { + var board; + board = $('.current-board').html(); self.displayedCollection.current_page = discussion.current_page; self.displayedCollection.pages = discussion.pages; return self.displayedCollection.reset(discussion.models); @@ -107,15 +129,23 @@ this.boardName = null; this.current_search = ''; this.mode = 'all'; + this.keyCodes = { + enter: 13, + escape: 27, + up: 38, + down: 40 + }; + this.filterInputReset(); + this.selectedTopic = $('.forum-nav-browse-menu-item:visible .forum-nav-browse-title.is-focused'); this.searchAlertCollection = new Backbone.Collection([], { model: Backbone.Model }); this.searchAlertCollection.on('add', function(searchAlert) { var content; content = edx.HtmlUtils.template($('#search-alert-template').html())({ - messageHtml: searchAlert.attributes.message, - cid: searchAlert.cid, - css_class: searchAlert.attributes.css_class + 'messageHtml': searchAlert.attributes.message, + 'cid': searchAlert.cid, + 'css_class': searchAlert.attributes.css_class }); edx.HtmlUtils.append(self.$('.search-alerts'), content); return self.$('#search-alert-' + searchAlert.cid + ' .dismiss') @@ -130,6 +160,7 @@ return self.$('.search-alerts').empty(); }); this.template = edx.HtmlUtils.template($('#thread-list-template').html()); + this.homeTemplate = edx.HtmlUtils.template($('#discussion-home-template').html()); this.threadListItemTemplate = edx.HtmlUtils.template($('#thread-list-item-template').html()); }; @@ -141,9 +172,10 @@ * @returns {Backbone.Model} */ DiscussionThreadListView.prototype.addSearchAlert = function(message, cssClass) { - var searchAlertModel = new Backbone.Model({message: message, css_class: cssClass || ''}); - this.searchAlertCollection.add(searchAlertModel); - return searchAlertModel; + var m; + m = new Backbone.Model({message: message, css_class: cssClass || ''}); + this.searchAlertCollection.add(m); + return m; }; DiscussionThreadListView.prototype.removeSearchAlert = function(searchAlert) { @@ -164,7 +196,7 @@ $currentElement.replaceWith($content); this.showMetadataAccordingToSort(); if (active) { - this.setActiveThread(threadId); + return this.setActiveThread(threadId); } }; @@ -180,6 +212,37 @@ }); }; + DiscussionThreadListView.prototype.updateSidebar = function() { + var amount, browseFilterHeight, discussionBody, discussionBottomOffset, discussionsBodyBottom, + discussionsBodyTop, headerHeight, refineBarHeight, scrollTop, sidebar, sidebarHeight, topOffset, + windowHeight; + scrollTop = $(window).scrollTop(); + windowHeight = $(window).height(); + discussionBody = $('.discussion-column'); + discussionsBodyTop = discussionBody[0] ? discussionBody.offset().top : void 0; + discussionsBodyBottom = discussionsBodyTop + discussionBody.outerHeight(); + sidebar = $('.forum-nav'); + if (scrollTop > discussionsBodyTop - this.sidebar_padding) { + sidebar.css('top', scrollTop - discussionsBodyTop + this.sidebar_padding); + } else { + sidebar.css('top', '0'); + } + sidebarHeight = windowHeight - Math.max(discussionsBodyTop - scrollTop, this.sidebar_padding); + topOffset = scrollTop + windowHeight; + discussionBottomOffset = discussionsBodyBottom + this.sidebar_padding; + amount = Math.max(topOffset - discussionBottomOffset, 0); + sidebarHeight = sidebarHeight - this.sidebar_padding - amount; + sidebarHeight = Math.min(sidebarHeight + 1, discussionBody.outerHeight()); + sidebar.css('height', sidebarHeight); + headerHeight = this.$('.forum-nav-header').outerHeight(); + refineBarHeight = this.$('.forum-nav-refine-bar').outerHeight(); + browseFilterHeight = this.$('.forum-nav-browse-filter').outerHeight(); + this.$('.forum-nav-thread-list') + .css('height', (sidebarHeight - headerHeight - refineBarHeight - 2) + 'px'); + this.$('.forum-nav-browse-menu') + .css('height', (sidebarHeight - headerHeight - browseFilterHeight - 2) + 'px'); + }; + DiscussionThreadListView.prototype.ignoreClick = function(event) { return event.stopPropagation(); }; @@ -198,14 +261,16 @@ this.$('.forum-nav-sort-control option').removeProp('selected'); this.$('.forum-nav-sort-control option[value=' + this.collection.sort_preference + ']') .prop('selected', true); + $(window).bind('load scroll resize', this.updateSidebar); this.displayedCollection.on('reset', this.renderThreads); this.displayedCollection.on('thread:remove', this.renderThreads); this.displayedCollection.on('change:commentable_id', function() { if (self.mode === 'commentables') { - self.retrieveDiscussions(self.discussionIds.split(',')); + return self.retrieveDiscussions(self.discussionIds.split(',')); } }); this.renderThreads(); + this.showBrowseMenu(true); return this; }; @@ -219,6 +284,7 @@ } this.showMetadataAccordingToSort(); this.renderMorePages(); + this.updateSidebar(); this.trigger('threads:rendered'); }; @@ -253,7 +319,7 @@ }; DiscussionThreadListView.prototype.loadMorePages = function(event) { - var error, lastThread, loadMoreElem, loadingElem, options, ref, + var error, lastThread, loadMoreElem, loadingElem, options, _ref, self = this; if (event) { event.preventDefault(); @@ -287,11 +353,9 @@ if (this.group_id) { options.group_id = this.group_id; } - break; - default: } - ref = this.collection.last(); - lastThread = ref ? ref.get('id') : void 0; + _ref = this.collection.last(); + lastThread = _ref ? _ref.get('id') : void 0; if (lastThread) { this.once('threads:rendered', function() { var classSelector = @@ -301,8 +365,8 @@ }); } else { this.once('threads:rendered', function() { - var ref1 = $('.forum-nav-thread-link').first(); - return ref1 ? ref1.focus() : void 0; + var _ref1 = $('.forum-nav-thread-link').first(); + return _ref1 ? _ref1.focus() : void 0; }); } error = function() { @@ -357,10 +421,224 @@ .prepend($srElem); }; + DiscussionThreadListView.prototype.goHome = function() { + var url, $templateContent; + $templateContent = $(this.homeTemplate().toString()); + $('.forum-content').empty().append($templateContent); + $('.forum-nav-thread-list a').removeClass('is-active').find('.sr') + .remove(); + $('input.email-setting').bind('click', this.updateEmailNotifications); + url = DiscussionUtil.urlFor('notifications_status', window.user.get('id')); + DiscussionUtil.safeAjax({ + url: url, + type: 'GET', + success: function(response) { + $('input.email-setting').prop('checked', response.status); + } + }); + }; + + DiscussionThreadListView.prototype.isBrowseMenuVisible = function() { + return this.$('.forum-nav-browse-menu-wrapper').is(':visible'); + }; + + DiscussionThreadListView.prototype.showBrowseMenu = function(initialLoad) { + if (!this.isBrowseMenuVisible()) { + this.$('.forum-nav-browse-menu-wrapper').show(); + this.$('.forum-nav-thread-list-wrapper').hide(); + if (!initialLoad) { + $('.forum-nav-browse-filter-input').focus(); + this.filterInputReset(); + } + $('body').bind('click', this.hideBrowseMenu); + return this.updateSidebar(); + } + }; + + DiscussionThreadListView.prototype.hideBrowseMenu = function() { + var selectedTopicList = this.$('.forum-nav-browse-title.is-focused'); + if (this.isBrowseMenuVisible()) { + selectedTopicList.removeClass('is-focused'); + this.$('.forum-nav-browse-menu-wrapper').hide(); + this.$('.forum-nav-thread-list-wrapper').show(); + if (this.selectedTopicId !== 'undefined') { + this.$('.forum-nav-browse-filter-input').attr('aria-activedescendant', this.selectedTopicId); + } + $('body').unbind('click', this.hideBrowseMenu); + return this.updateSidebar(); + } + }; + + DiscussionThreadListView.prototype.toggleBrowseMenu = function(event) { + var inputText = $('.forum-nav-browse-filter-input').val(); + event.preventDefault(); + event.stopPropagation(); + if (this.isBrowseMenuVisible()) { + return this.hideBrowseMenu(); + } else { + if (inputText !== '') { + this.filterTopics(inputText); + } + return this.showBrowseMenu(); + } + }; + + DiscussionThreadListView.prototype.getPathText = function(item) { + var path, pathTitles; + path = item.parents('.forum-nav-browse-menu-item').andSelf(); + pathTitles = path.children('.forum-nav-browse-title').map(function(i, elem) { + return $(elem).text(); + }).get(); + return pathTitles.join(' / '); + }; + + DiscussionThreadListView.prototype.getBreadcrumbText = function($item) { + var $parentSubMenus = $item.parents('.forum-nav-browse-submenu'), + crumbs = [], + subTopic = $('.forum-nav-browse-title', $item) + .first() + .text() + .trim(); + + $parentSubMenus.each(function(i, el) { + crumbs.push($(el).siblings('.forum-nav-browse-title') + .first() + .text() + .trim() + ); + }); + + if (subTopic !== 'All Discussions') { + crumbs.push(subTopic); + } + + return crumbs; + }; + + DiscussionThreadListView.prototype.selectOption = function(element) { + var activeDescendantId, activeDescendantText; + if (this.selectedTopic.length > 0) { + this.selectedTopic.removeClass('is-focused'); + } + if (element) { + element.addClass('is-focused'); + activeDescendantId = element.parent().attr('id'); + activeDescendantText = element.text(); + this.selectedTopic = element; + this.selectedTopicId = activeDescendantId; + $('.forum-nav-browse-filter-input') + .attr('aria-activedescendant', activeDescendantId) + .val(activeDescendantText); + } + }; + + DiscussionThreadListView.prototype.filterInputReset = function() { + this.filterEnabled = true; + this.selectedTopicIndex = -1; + this.selectedTopicId = null; + }; + + DiscussionThreadListView.prototype.keyboardBinding = function(event) { + var $inputText = $('.forum-nav-browse-filter-input'), + $filteredMenuItems = $('.forum-nav-browse-menu-item:visible'), + filteredMenuItemsLen = $filteredMenuItems.length, + $curOption = $filteredMenuItems.eq(0).find('.forum-nav-browse-title').eq(0), + $activeOption, $prev, $next; + + switch (event.keyCode) { + case this.keyCodes.enter: + $activeOption = $filteredMenuItems.find('.forum-nav-browse-title.is-focused'); + if ($inputText.val() !== '') { + $activeOption.trigger('click'); + this.filterInputReset(); + } + break; + + case this.keyCodes.escape: + this.toggleBrowseMenu(event); + $('.forum-nav-browse-filter-input').val(''); + this.filterInputReset(); + $('.all-topics').trigger('click'); + break; + + case this.keyCodes.up: + if (this.selectedTopicIndex > 0) { + this.selectedTopicIndex -= 1; + if (this.isBrowseMenuVisible()) { + $prev = $('.forum-nav-browse-menu-item:visible') + .eq(this.selectedTopicIndex).find('.forum-nav-browse-title') + .eq(0); + this.filterEnabled = false; + $curOption.removeClass('is-focused'); + $prev.addClass('is-focused'); + } + this.selectOption($prev); + } + break; + + case this.keyCodes.down: + if (this.selectedTopicIndex < filteredMenuItemsLen - 1) { + this.selectedTopicIndex += 1; + if (this.isBrowseMenuVisible()) { + $next = $('.forum-nav-browse-menu-item:visible') + .eq(this.selectedTopicIndex).find('.forum-nav-browse-title') + .eq(0); + this.filterEnabled = false; + $curOption.removeClass('is-focused'); + $next.addClass('is-focused'); + } + this.selectOption($next); + } + break; + + default: + break; + } + return true; + }; + + DiscussionThreadListView.prototype.filterTopics = function() { + var items, query, filteredItems, + self = this; + query = this.$('.forum-nav-browse-filter-input').val(); + items = this.$('.forum-nav-browse-menu-item'); + if (query.length === 0) { + items.find('.forum-nav-browse-title.is-focused').removeClass('is-focused'); + return items.show(); + } else { + if (this.filterEnabled) { + items.hide(); + filteredItems = items.each(function(i, item) { + var path, pathText, + $item = $(item); + if (!$item.is(':visible')) { + pathText = self.getPathText($item).toLowerCase(); + if (query.split(' ').every(function(term) { + return pathText.search(term.toLowerCase()) !== -1; + })) { + path = $item.parents('.forum-nav-browse-menu-item').andSelf(); + return path.add($item.find('.forum-nav-browse-menu-item')).show(); + } + } + return filteredItems; + }); + } + return filteredItems; + } + }; + + DiscussionThreadListView.prototype.selectTopicHandler = function(event) { + event.preventDefault(); + return this.selectTopic($(event.target)); + }; + DiscussionThreadListView.prototype.selectTopic = function($target) { var allItems, discussionIds, $item; + this.hideBrowseMenu(); $item = $target.closest('.forum-nav-browse-menu-item'); + this.trigger('topic:selected', this.getBreadcrumbText($item)); + if ($item.hasClass('forum-nav-browse-menu-all')) { this.discussionIds = ''; this.$('.forum-nav-filter-cohort').show(); @@ -389,8 +667,9 @@ }; DiscussionThreadListView.prototype.retrieveDiscussion = function(discussionId, callback) { - var url = DiscussionUtil.urlFor('retrieve_discussion', discussionId), + var url, self = this; + url = DiscussionUtil.urlFor('retrieve_discussion', discussionId); return DiscussionUtil.safeAjax({ url: url, type: 'GET', @@ -407,8 +686,8 @@ }); }; - DiscussionThreadListView.prototype.retrieveDiscussions = function(discussionIds) { - this.discussionIds = discussionIds.join(','); + DiscussionThreadListView.prototype.retrieveDiscussions = function(discussion_ids) { + this.discussionIds = discussion_ids.join(','); this.mode = 'commentables'; return this.retrieveFirstPage(); }; @@ -430,18 +709,19 @@ }; DiscussionThreadListView.prototype.performSearch = function($searchInput) { + this.hideBrowseMenu(); // trigger this event so the breadcrumbs can update as well this.trigger('search:initiated'); this.searchFor($searchInput.val(), $searchInput); }; DiscussionThreadListView.prototype.searchFor = function(text, $searchInput) { - var url = DiscussionUtil.urlFor('search'), - self = this; + var url, self = this; this.clearSearchAlerts(); this.clearFilters(); this.mode = 'search'; this.current_search = text; + url = DiscussionUtil.urlFor('search'); /* TODO: This might be better done by setting discussion.current_page=0 and calling discussion.loadMorePages @@ -509,7 +789,6 @@ return self.searchForUser(text); } } - return response; } }); }; @@ -537,9 +816,9 @@ ); message = edx.HtmlUtils.interpolateHtml( - gettext('Show posts by {username}.'), {username: username} + gettext('Show posts by {username}.'), {'username': username} ); - self.addSearchAlert(message, 'search-by-user'); + return self.addSearchAlert(message, 'search-by-user'); } } }); diff --git a/common/static/common/js/spec/discussion/view/discussion_thread_edit_view_spec.js b/common/static/common/js/spec/discussion/view/discussion_thread_edit_view_spec.js index 6b3c3d2921..c2d876ae73 100644 --- a/common/static/common/js/spec/discussion/view/discussion_thread_edit_view_spec.js +++ b/common/static/common/js/spec/discussion/view/discussion_thread_edit_view_spec.js @@ -16,7 +16,7 @@ 'title': 'test thread title' }); this.thread = new Thread(this.threadData); - this.course_settings = DiscussionSpecHelper.createTestCourseSettings(); + this.course_settings = DiscussionSpecHelper.makeCourseSettings(); this.createEditView = function(options) { options = _.extend({ diff --git a/common/static/common/js/spec/discussion/view/discussion_thread_list_view_spec.js b/common/static/common/js/spec/discussion/view/discussion_thread_list_view_spec.js index b142c6b784..7ccf8018fa 100644 --- a/common/static/common/js/spec/discussion/view/discussion_thread_list_view_spec.js +++ b/common/static/common/js/spec/discussion/view/discussion_thread_list_view_spec.js @@ -266,88 +266,6 @@ changeSorting(this.threads, 'votes', 'comments', ['Thread1', 'Thread4', 'Thread3', 'Thread2']); }); }); - - describe('post type renders correctly', function() { - it('for discussion', function() { - renderSingleThreadWithProps({ - thread_type: 'discussion' - }); - expect($('.forum-nav-thread-wrapper-0 .icon')).toHaveClass('fa-comments'); - return expect($('.forum-nav-thread-wrapper-0 .sr')).toHaveText('discussion'); - }); - - it('for answered question', function() { - renderSingleThreadWithProps({ - thread_type: 'question', - endorsed: true - }); - expect($('.forum-nav-thread-wrapper-0 .icon')).toHaveClass('fa-check-square-o'); - return expect($('.forum-nav-thread-wrapper-0 .sr')).toHaveText('answered question'); - }); - - it('for unanswered question', function() { - renderSingleThreadWithProps({ - thread_type: 'question', - endorsed: false - }); - expect($('.forum-nav-thread-wrapper-0 .icon')).toHaveClass('fa-question'); - return expect($('.forum-nav-thread-wrapper-0 .sr')).toHaveText('unanswered question'); - }); - }); - - describe('post labels render correctly', function() { - beforeEach(function() { - this.moderatorId = '42'; - this.administratorId = '43'; - this.communityTaId = '44'; - return DiscussionUtil.loadRoles({ - Moderator: [parseInt(this.moderatorId, 10)], - Administrator: [parseInt(this.administratorId, 10)], - 'Community TA': [parseInt(this.communityTaId, 10)] - }); - }); - - it('for pinned', function() { - renderSingleThreadWithProps({ - pinned: true - }); - return expect($('.post-label-pinned').length).toEqual(1); - }); - - it('for following', function() { - renderSingleThreadWithProps({ - subscribed: true - }); - return expect($('.post-label-following').length).toEqual(1); - }); - - it('for moderator', function() { - renderSingleThreadWithProps({ - user_id: this.moderatorId - }); - return expect($('.post-label-by-staff').length).toEqual(1); - }); - - it('for administrator', function() { - renderSingleThreadWithProps({ - user_id: this.administratorId - }); - return expect($('.post-label-by-staff').length).toEqual(1); - }); - - it('for community TA', function() { - renderSingleThreadWithProps({ - user_id: this.communityTaId - }); - return expect($('.post-label-by-community-ta').length).toEqual(1); - }); - - it('when none should be present', function() { - renderSingleThreadWithProps({}); - return expect($('.forum-nav-thread-labels').length).toEqual(0); - }); - }); - describe('search alerts', function() { var testAlertMessages, getAlertMessagesAndClasses; @@ -542,5 +460,228 @@ }); }); + describe('post type renders correctly', function() { + it('for discussion', function() { + renderSingleThreadWithProps({ + thread_type: 'discussion' + }); + expect($('.forum-nav-thread-wrapper-0 .icon')).toHaveClass('fa-comments'); + return expect($('.forum-nav-thread-wrapper-0 .sr')).toHaveText('discussion'); + }); + + it('for answered question', function() { + renderSingleThreadWithProps({ + thread_type: 'question', + endorsed: true + }); + expect($('.forum-nav-thread-wrapper-0 .icon')).toHaveClass('fa-check-square-o'); + return expect($('.forum-nav-thread-wrapper-0 .sr')).toHaveText('answered question'); + }); + + it('for unanswered question', function() { + renderSingleThreadWithProps({ + thread_type: 'question', + endorsed: false + }); + expect($('.forum-nav-thread-wrapper-0 .icon')).toHaveClass('fa-question'); + return expect($('.forum-nav-thread-wrapper-0 .sr')).toHaveText('unanswered question'); + }); + }); + + describe('post labels render correctly', function() { + beforeEach(function() { + this.moderatorId = '42'; + this.administratorId = '43'; + this.communityTaId = '44'; + return DiscussionUtil.loadRoles({ + 'Moderator': [parseInt(this.moderatorId)], + 'Administrator': [parseInt(this.administratorId)], + 'Community TA': [parseInt(this.communityTaId)] + }); + }); + + it('for pinned', function() { + renderSingleThreadWithProps({ + pinned: true + }); + return expect($('.post-label-pinned').length).toEqual(1); + }); + + it('for following', function() { + renderSingleThreadWithProps({ + subscribed: true + }); + return expect($('.post-label-following').length).toEqual(1); + }); + + it('for moderator', function() { + renderSingleThreadWithProps({ + user_id: this.moderatorId + }); + return expect($('.post-label-by-staff').length).toEqual(1); + }); + + it('for administrator', function() { + renderSingleThreadWithProps({ + user_id: this.administratorId + }); + return expect($('.post-label-by-staff').length).toEqual(1); + }); + + it('for community TA', function() { + renderSingleThreadWithProps({ + user_id: this.communityTaId + }); + return expect($('.post-label-by-community-ta').length).toEqual(1); + }); + + it('when none should be present', function() { + renderSingleThreadWithProps({}); + return expect($('.forum-nav-thread-labels').length).toEqual(0); + }); + }); + + describe('browse menu', function() { + var expectBrowseMenuVisible; + afterEach(function() { + return $('body').unbind('click'); + }); + + expectBrowseMenuVisible = function(isVisible) { + expect($('.forum-nav-browse-menu:visible').length).toEqual(isVisible ? 1 : 0); + return expect($('.forum-nav-thread-list-wrapper:visible').length).toEqual(isVisible ? 0 : 1); + }; + + it('should be visible by default', function() { + expectBrowseMenuVisible(true); + }); + + it('should disappear when header button is clicked', function() { + $('.forum-nav-browse').click(); + return expectBrowseMenuVisible(false); + }); + + describe('when shown', function() { + it('should show again when header button is clicked', function() { + $('.forum-nav-browse').click(); + return expectBrowseMenuVisible(false); + }); + + it('should hide when a click outside the menu occurs', function() { + $('.forum-nav-search-input').click(); + return expectBrowseMenuVisible(false); + }); + + it('should hide when a category is clicked', function() { + $('.forum-nav-browse-title')[0].click(); + return expectBrowseMenuVisible(false); + }); + + it('should still be shown when filter input is clicked', function() { + $('.forum-nav-browse-filter-input').click(); + return expectBrowseMenuVisible(true); + }); + + describe('filtering', function() { + var checkFilter; + checkFilter = function(filterText, expectedItems) { + var visibleItems; + $('.forum-nav-browse-filter-input').val(filterText).keyup(); + visibleItems = $('.forum-nav-browse-title:visible').map(function(i, elem) { + return $(elem).text(); + }).get(); + return expect(visibleItems).toEqual(expectedItems); + }; + + it('should be case-insensitive', function() { + return checkFilter('other', ['Other Category']); + }); + + it('should match partial words', function() { + return checkFilter('ateg', ['Other Category']); + }); + + it('should show ancestors and descendants of matches', function() { + return checkFilter('Target', ['Parent', 'Target', 'Child']); + }); + + it('should handle multiple words regardless of order', function() { + return checkFilter('Following Posts', ["Posts I'm Following"]); + }); + + it('should handle multiple words in different depths', function() { + return checkFilter('Parent Child', ['Parent', 'Target', 'Child']); + }); + }); + }); + + describe('selecting an item', function() { + var testSelectionRequest; + + it('should show/hide the cohort selector', function() { + var self = this; + DiscussionSpecHelper.makeModerator(); + this.view.render(); + setupAjax(); + return _.each([ + { + selector: '.forum-nav-browse-menu-all', + cohortVisibility: true + }, { + selector: '.forum-nav-browse-menu-following', + cohortVisibility: false + }, { + selector: '.forum-nav-browse-menu-item:' + + 'has(.forum-nav-browse-menu-item .forum-nav-browse-menu-item)', + cohortVisibility: false + }, { + selector: '[data-discussion-id=child]', + cohortVisibility: false + }, { + selector: '[data-discussion-id=other]', + cohortVisibility: true + } + ], function(itemInfo) { + self.view.$('' + itemInfo.selector + ' > .forum-nav-browse-title').click(); + return expect(self.view.$('.forum-nav-filter-cohort').is(':visible')) + .toEqual(itemInfo.cohortVisibility); + }); + }); + + testSelectionRequest = function(callback, itemText) { + setupAjax(callback); + $('.forum-nav-browse-title:contains(' + itemText + ')').click(); + return expect($.ajax).toHaveBeenCalled(); + }; + + it('should get all discussions', function() { + return testSelectionRequest(function(params) { + return expect(params.url.path()).toEqual(DiscussionUtil.urlFor('threads')); + }, 'All'); + }); + + it('should get followed threads', function() { + testSelectionRequest(function(params) { + return expect(params.url.path()) + .toEqual(DiscussionUtil.urlFor('followed_threads', window.user.id)); + }, 'Following'); + return expect($.ajax.calls.mostRecent().args[0].data.group_id).toBeUndefined(); + }); + + it('should get threads for the selected leaf', function() { + return testSelectionRequest(function(params) { + expect(params.url.path()).toEqual(DiscussionUtil.urlFor('search')); + return expect(params.data.commentable_ids).toEqual('child'); + }, 'Child'); + }); + + it('should get threads for children of the selected intermediate node', function() { + return testSelectionRequest(function(params) { + expect(params.url.path()).toEqual(DiscussionUtil.urlFor('search')); + return expect(params.data.commentable_ids).toEqual('child,sibling'); + }, 'Parent'); + }); + }); + }); }); }).call(this); diff --git a/common/static/common/js/spec/discussion/view/discussion_thread_show_view_spec.js b/common/static/common/js/spec/discussion/view/discussion_thread_show_view_spec.js index fe9ac3332e..b13c9d6c23 100644 --- a/common/static/common/js/spec/discussion/view/discussion_thread_show_view_spec.js +++ b/common/static/common/js/spec/discussion/view/discussion_thread_show_view_spec.js @@ -5,7 +5,7 @@ var $$course_id = '$$course_id'; describe('DiscussionThreadShowView', function() { beforeEach(function() { - DiscussionSpecHelper.setUpGlobals({}); + DiscussionSpecHelper.setUpGlobals(); DiscussionSpecHelper.setUnderscoreFixtures(); this.user = DiscussionUtil.getUser(); this.threadData = { diff --git a/common/static/common/js/spec/discussion/view/discussion_thread_view_spec.js b/common/static/common/js/spec/discussion/view/discussion_thread_view_spec.js index 3a35cfdbc5..9b39017d54 100644 --- a/common/static/common/js/spec/discussion/view/discussion_thread_view_spec.js +++ b/common/static/common/js/spec/discussion/view/discussion_thread_view_spec.js @@ -129,7 +129,7 @@ model: thread, el: $('#fixture-element'), mode: mode, - course_settings: DiscussionSpecHelper.createTestCourseSettings() + course_settings: DiscussionSpecHelper.makeCourseSettings() }); renderWithTestResponses(view, 1); if (mode === 'inline') { @@ -185,7 +185,7 @@ model: this.thread, el: $('#fixture-element'), mode: 'tab', - course_settings: DiscussionSpecHelper.createTestCourseSettings() + course_settings: DiscussionSpecHelper.makeCourseSettings() }); }); describe('responses', function() { @@ -282,7 +282,7 @@ model: this.thread, el: $('#fixture-element'), mode: 'inline', - course_settings: DiscussionSpecHelper.createTestCourseSettings() + course_settings: DiscussionSpecHelper.makeCourseSettings() }); }); describe('render', function() { @@ -397,7 +397,7 @@ model: this.thread, el: $('#fixture-element'), mode: 'tab', - course_settings: DiscussionSpecHelper.createTestCourseSettings() + course_settings: DiscussionSpecHelper.makeCourseSettings() }); }); generateContent = function(idStart, idEnd) { @@ -465,7 +465,7 @@ model: this.thread, el: $('#fixture-element'), mode: 'tab', - course_settings: DiscussionSpecHelper.createTestCourseSettings() + course_settings: DiscussionSpecHelper.makeCourseSettings() }); }); it("doesn't show report option if can_report ability is disabled", function() { diff --git a/common/static/common/js/spec/discussion/view/response_comment_view_spec.js b/common/static/common/js/spec/discussion/view/response_comment_view_spec.js index 35ba510892..57d5fbfeea 100644 --- a/common/static/common/js/spec/discussion/view/response_comment_view_spec.js +++ b/common/static/common/js/spec/discussion/view/response_comment_view_spec.js @@ -5,7 +5,7 @@ describe('ResponseCommentView', function() { beforeEach(function() { - DiscussionSpecHelper.setUpGlobals({}); + DiscussionSpecHelper.setUpGlobals(); this.comment = new Comment({ id: '01234567', user_id: user.id, diff --git a/common/static/common/js/spec_helpers/discussion_spec_helper.js b/common/static/common/js/spec_helpers/discussion_spec_helper.js index e1838b05ad..679795f6ee 100644 --- a/common/static/common/js/spec_helpers/discussion_spec_helper.js +++ b/common/static/common/js/spec_helpers/discussion_spec_helper.js @@ -1,16 +1,15 @@ -/* global Content, Discussion, DiscussionCourseSettings, DiscussionUtil, DiscussionUser */ +/* global DiscussionCourseSettings, DiscussionUtil, DiscussionUser */ (function() { 'use strict'; this.DiscussionSpecHelper = (function() { function DiscussionSpecHelper() { } - DiscussionSpecHelper.setUpGlobals = function(opts) { - var options = opts || {}; - DiscussionUtil.loadRoles(options.roles || DiscussionSpecHelper.getTestRoleInfo()); - window.$$course_id = options.courseName || 'edX/999/test'; - window.user = new DiscussionUser(options.userInfo || DiscussionSpecHelper.getTestUserInfo()); - DiscussionUtil.setUser(window.user); + DiscussionSpecHelper.setUpGlobals = function() { + DiscussionUtil.loadRoles(DiscussionSpecHelper.getTestRoleInfo()); + window.$$course_id = 'edX/999/test'; + window.user = new DiscussionUser(DiscussionSpecHelper.getTestUserInfo()); + return DiscussionUtil.setUser(window.user); }; DiscussionSpecHelper.getTestUserInfo = function() { @@ -51,7 +50,7 @@ return jasmine.createSpyObj('event', ['preventDefault', 'target']); }; - DiscussionSpecHelper.createTestCourseSettings = function() { + DiscussionSpecHelper.makeCourseSettings = function() { return new DiscussionCourseSettings({ category_map: { children: [['Test Topic', 'entry'], ['Other Topic', 'entry']], @@ -70,24 +69,12 @@ }); }; - DiscussionSpecHelper.createTestDiscussion = function(options) { - var sortPreference = options.sort_preference, - threads = options.threads || [], - threadPages = options.thread_pages || 1, - contentInfo = options.content_info; - DiscussionSpecHelper.setUpGlobals(options); - if (contentInfo) { - Content.loadContentInfos(contentInfo); - } - return new Discussion(threads, {pages: threadPages, sort: sortPreference}); - }; - DiscussionSpecHelper.setUnderscoreFixtures = function() { var templateFixture, templateName, templateNames, templateNamesNoTrailingTemplate, i, j, len; templateNames = [ 'thread', 'thread-show', 'thread-edit', 'thread-response', 'thread-response-show', 'thread-response-edit', 'response-comment-show', 'response-comment-edit', 'thread-list-item', - 'search-alert', 'new-post', 'thread-type', 'new-post-menu-entry', 'new-post-alert', + 'discussion-home', 'search-alert', 'new-post', 'thread-type', 'new-post-menu-entry', 'new-post-alert', 'new-post-menu-category', 'topic', 'post-user-display', 'inline-discussion', 'pagination', 'profile-thread', 'customwmd-prompt', 'nav-loading' ]; diff --git a/lms/djangoapps/discussion/static/discussion/templates/discussion-home.underscore b/common/static/common/templates/discussion/discussion-home.underscore similarity index 100% rename from lms/djangoapps/discussion/static/discussion/templates/discussion-home.underscore rename to common/static/common/templates/discussion/discussion-home.underscore diff --git a/common/test/acceptance/tests/discussion/test_discussion.py b/common/test/acceptance/tests/discussion/test_discussion.py index 226fa15b08..77d823d950 100644 --- a/common/test/acceptance/tests/discussion/test_discussion.py +++ b/common/test/acceptance/tests/discussion/test_discussion.py @@ -511,7 +511,6 @@ class DiscussionOpenClosedThreadTest(BaseDiscussionTestCase): page.a11y_audit.config.set_rules({ 'ignore': [ 'section', # TODO: AC-491 - 'aria-required-children', # TODO: AC-534 'color-contrast', # Commented out for now because they reproducibly fail on Jenkins but not locally ] }) @@ -521,7 +520,6 @@ class DiscussionOpenClosedThreadTest(BaseDiscussionTestCase): page.a11y_audit.config.set_rules({ 'ignore': [ 'section', # TODO: AC-491 - 'aria-required-children', # TODO: AC-534 'color-contrast', # Commented out for now because they reproducibly fail on Jenkins but not locally ] }) diff --git a/lms/djangoapps/discussion/static/discussion/js/discussion_board_factory.js b/lms/djangoapps/discussion/static/discussion/js/discussion_board_factory.js index 6c2c310343..bea9bd56dc 100644 --- a/lms/djangoapps/discussion/static/discussion/js/discussion_board_factory.js +++ b/lms/djangoapps/discussion/static/discussion/js/discussion_board_factory.js @@ -5,50 +5,38 @@ [ 'jquery', 'backbone', - 'common/js/discussion/content', - 'common/js/discussion/discussion', - 'common/js/discussion/utils', - 'common/js/discussion/models/discussion_course_settings', - 'common/js/discussion/models/discussion_user', - 'common/js/discussion/views/new_post_view', 'discussion/js/discussion_router', - 'discussion/js/views/discussion_board_view' + 'discussion/js/views/discussion_fake_breadcrumbs', + 'discussion/js/views/discussion_search_view', + 'common/js/discussion/views/new_post_view' ], - function($, Backbone, Content, Discussion, DiscussionUtil, DiscussionCourseSettings, DiscussionUser, - NewPostView, DiscussionRouter, DiscussionBoardView) { + function($, Backbone, DiscussionRouter, DiscussionFakeBreadcrumbs, DiscussionSearchView, NewPostView) { return function(options) { var userInfo = options.user_info, sortPreference = options.sort_preference, threads = options.threads, threadPages = options.thread_pages, contentInfo = options.content_info, - user = new DiscussionUser(userInfo), + user = new window.DiscussionUser(userInfo), discussion, courseSettings, newPostView, - discussionBoardView, router, + breadcrumbs, + BreadcrumbsModel, + searchBox, routerEvents; - // TODO: eliminate usage of global variables when possible - DiscussionUtil.loadRoles(options.roles); + // TODO: Perhaps eliminate usage of global variables when possible + window.DiscussionUtil.loadRoles(options.roles); window.$$course_id = options.courseId; window.courseName = options.course_name; - DiscussionUtil.setUser(user); + window.DiscussionUtil.setUser(user); window.user = user; - Content.loadContentInfos(contentInfo); + window.Content.loadContentInfos(contentInfo); - // Create a discussion model - discussion = new Discussion(threads, {pages: threadPages, sort: sortPreference}); - courseSettings = new DiscussionCourseSettings(options.course_settings); - - // Create the discussion board view - discussionBoardView = new DiscussionBoardView({ - el: $('.discussion-board'), - discussion: discussion, - courseSettings: courseSettings - }); - discussionBoardView.render(); + discussion = new window.Discussion(threads, {pages: threadPages, sort: sortPreference}); + courseSettings = new window.DiscussionCourseSettings(options.course_settings); // Create the new post view newPostView = new NewPostView({ @@ -59,27 +47,59 @@ }); newPostView.render(); - // Set up a router to manage the page's history + // Set up the router to manage the page's history router = new DiscussionRouter({ courseId: options.courseId, discussion: discussion, courseSettings: courseSettings, - discussionBoardView: discussionBoardView, newPostView: newPostView }); router.start(); + + // Initialize and render search box + searchBox = new DiscussionSearchView({ + el: $('.forum-search'), + threadListView: router.nav + }).render(); + + // Initialize and render breadcrumbs + BreadcrumbsModel = Backbone.Model.extend({ + defaults: { + contents: [] + } + }); + + breadcrumbs = new DiscussionFakeBreadcrumbs({ + el: $('.has-breadcrumbs'), + model: new BreadcrumbsModel(), + events: { + 'click .all-topics': function(event) { + event.preventDefault(); + searchBox.clearSearch(); + this.model.set('contents', []); + router.navigate('', {trigger: true}); + router.nav.toggleBrowseMenu(event); + } + } + }).render(); + routerEvents = { // Add new breadcrumbs and clear search box when the user selects topics 'topic:selected': function(topic) { - router.discussionBoardView.breadcrumbs.model.set('contents', topic); + breadcrumbs.model.set('contents', topic); }, // Clear search box when a thread is selected 'thread:selected': function() { - router.discussionBoardView.searchView.clearSearch(); + searchBox.clearSearch(); + }, + // Add 'Search Results' to breadcrumbs when user searches + 'search:initiated': function() { + breadcrumbs.model.set('contents', ['Search Results']); } }; + Object.keys(routerEvents).forEach(function(key) { - router.discussionBoardView.on(key, routerEvents[key]); + router.nav.on(key, routerEvents[key]); }); }; }); diff --git a/lms/djangoapps/discussion/static/discussion/js/discussion_profile_page_factory.js b/lms/djangoapps/discussion/static/discussion/js/discussion_profile_page_factory.js index 537418a102..0b59b15a91 100644 --- a/lms/djangoapps/discussion/static/discussion/js/discussion_profile_page_factory.js +++ b/lms/djangoapps/discussion/static/discussion/js/discussion_profile_page_factory.js @@ -1,14 +1,8 @@ (function(define) { 'use strict'; - define( - [ - 'jquery', - 'common/js/discussion/utils', - 'common/js/discussion/models/discussion_user', - 'discussion/js/views/discussion_user_profile_view' - ], - function($, DiscussionUtil, DiscussionUser, DiscussionUserProfileView) { + define(['jquery', 'discussion/js/views/discussion_user_profile_view'], + function($, DiscussionUserProfileView) { return function(options) { var $element = options.$el, threads = options.threads, @@ -16,16 +10,13 @@ page = options.page, numPages = options.numPages; // Roles are not included in user profile page, but they are not used for anything - DiscussionUtil.loadRoles({ + window.DiscussionUtil.loadRoles({ Moderator: [], Administrator: [], 'Community TA': [] }); - - // TODO: remove global variable usage window.$$course_id = options.courseId; - window.user = new DiscussionUser(userInfo); - + window.user = new window.DiscussionUser(userInfo); new DiscussionUserProfileView({ // eslint-disable-line no-new el: $element, collection: threads, diff --git a/lms/djangoapps/discussion/static/discussion/js/discussion_router.js b/lms/djangoapps/discussion/static/discussion/js/discussion_router.js index 83a4d19cd8..1280940c2a 100644 --- a/lms/djangoapps/discussion/static/discussion/js/discussion_router.js +++ b/lms/djangoapps/discussion/static/discussion/js/discussion_router.js @@ -6,10 +6,10 @@ 'underscore', 'backbone', 'common/js/discussion/utils', - 'common/js/discussion/models/discussion_course_settings', + 'common/js/discussion/views/discussion_thread_list_view', 'common/js/discussion/views/discussion_thread_view' ], - function(_, Backbone, DiscussionUtil, DiscussionCourseSettings, DiscussionThreadView) { + function(_, Backbone, DiscussionUtil, DiscussionThreadListView, DiscussionThreadView) { var DiscussionRouter = Backbone.Router.extend({ routes: { '': 'allThreads', @@ -21,9 +21,14 @@ _.bindAll(this, 'allThreads', 'showThread'); this.courseId = options.courseId; this.discussion = options.discussion; - this.course_settings = new DiscussionCourseSettings(options.course_settings); - this.discussionBoardView = options.discussionBoardView; + this.course_settings = options.courseSettings; this.newPostView = options.newPostView; + this.nav = new DiscussionThreadListView({ + collection: this.discussion, + el: $('.forum-nav'), + courseSettings: this.course_settings + }); + this.nav.render(); }, start: function() { @@ -36,18 +41,10 @@ }); // Automatically navigate when the user selects threads - this.discussionBoardView.discussionThreadListView.on( - 'thread:selected', _.bind(this.navigateToThread, this) - ); - this.discussionBoardView.discussionThreadListView.on( - 'thread:removed', _.bind(this.navigateToAllThreads, this) - ); - this.discussionBoardView.discussionThreadListView.on( - 'threads:rendered', _.bind(this.setActiveThread, this) - ); - this.discussionBoardView.discussionThreadListView.on( - 'thread:created', _.bind(this.navigateToThread, this) - ); + this.nav.on('thread:selected', _.bind(this.navigateToThread, this)); + this.nav.on('thread:removed', _.bind(this.navigateToAllThreads, this)); + this.nav.on('threads:rendered', _.bind(this.setActiveThread, this)); + this.nav.on('thread:created', _.bind(this.navigateToThread, this)); Backbone.history.start({ pushState: true, @@ -60,15 +57,15 @@ }, allThreads: function() { - this.discussionBoardView.updateSidebar(); - return this.discussionBoardView.goHome(); + this.nav.updateSidebar(); + return this.nav.goHome(); }, setActiveThread: function() { if (this.thread) { - return this.discussionBoardView.discussionThreadListView.setActiveThread(this.thread.get('id')); + return this.nav.setActiveThread(this.thread.get('id')); } else { - return this.discussionBoardView.goHome; + return this.nav.goHome; } }, @@ -89,8 +86,8 @@ if (!($('.forum-content').is(':visible'))) { $('.forum-content').fadeIn(); } - if ($('.new-post-article').is(':visible')) { - $('.new-post-article').fadeOut(); + if (this.newPostView.$el.is(':visible')) { + this.newPostView.$el.fadeOut(); } this.main = new DiscussionThreadView({ el: $('.forum-content'), @@ -100,13 +97,14 @@ }); this.main.render(); this.main.on('thread:responses:rendered', function() { - return self.discussionBoardView.updateSidebar(); + return self.nav.updateSidebar(); }); return this.thread.on('thread:thread_type_updated', this.showMain); }, navigateToThread: function(threadId) { - var thread = this.discussion.get(threadId); + var thread; + thread = this.discussion.get(threadId); return this.navigate('' + (thread.get('commentable_id')) + '/threads/' + threadId, { trigger: true }); @@ -137,7 +135,6 @@ } }); } - }); return DiscussionRouter; diff --git a/lms/djangoapps/discussion/static/discussion/js/spec/discussion_board_factory_spec.js b/lms/djangoapps/discussion/static/discussion/js/spec/discussion_board_factory_spec.js index cf571920a1..53e49dbd70 100644 --- a/lms/djangoapps/discussion/static/discussion/js/spec/discussion_board_factory_spec.js +++ b/lms/djangoapps/discussion/static/discussion/js/spec/discussion_board_factory_spec.js @@ -4,34 +4,16 @@ define( 'backbone', 'common/js/spec_helpers/page_helpers', 'common/js/spec_helpers/discussion_spec_helper', - 'discussion/js/discussion_board_factory', - 'discussion/js/views/discussion_board_view' + 'discussion/js/discussion_board_factory' ], - function($, Backbone, PageHelpers, DiscussionSpecHelper, DiscussionBoardFactory, DiscussionBoardView) { + function($, Backbone, PageHelpers, DiscussionSpecHelper, DiscussionBoardFactory) { 'use strict'; // TODO: re-enable when this doesn't interact badly with other history tests - describe('DiscussionBoardFactory', function() { - var createDiscussionBoardView = function() { - var discussionBoardView, - discussion = DiscussionSpecHelper.createTestDiscussion({}), - courseSettings = DiscussionSpecHelper.createTestCourseSettings(); - - setFixtures('