Commit 71693c3a by Brian Jacobel Committed by GitHub

Merge pull request #14026 from edx/bjacobel/inline-discussion-redesign

Inline Discussion "two-level" redesign
parents 26413a57 fd0cf404
class @InlineDiscussion extends XModule.Descriptor
constructor: (element) ->
@el = $(element).find('.discussion-module')
@view = new DiscussionModuleView(el: @el)
@view = new DiscussionInlineView(el: @el)
......@@ -184,10 +184,8 @@
if (!params.error) {
params.error = function() {
self.discussionAlert(
gettext('Sorry'),
gettext(
'We had some trouble processing your request. Please ensure you have copied any ' +
'unsaved work and then reload the page.')
gettext('Error'),
gettext('Your request could not be processed. Refresh the page and try again.')
);
};
}
......@@ -223,7 +221,7 @@
self = this;
if (errorMsg) {
safeAjaxParams.error = function() {
return self.discussionAlert(gettext('Sorry'), errorMsg);
return self.discussionAlert(gettext('Error'), errorMsg);
};
}
undo = _.pick(model.attributes, _.keys(updates));
......@@ -276,7 +274,7 @@
}
}
} else {
$errorItem = makeErrorElem('We had some trouble processing your request. Please try again.', 0);
$errorItem = makeErrorElem('Your request could not be processed. Refresh the page and try again.', 0); // eslint-disable-line max-len
edx.HtmlUtils.append(errorsField, $errorItem);
}
......
/* globals
_, Backbone, Content, Discussion, DiscussionUtil, DiscussionUser, DiscussionCourseSettings,
DiscussionThreadListView, DiscussionThreadView, NewPostView
*/
(function() {
'use strict';
this.DiscussionInlineView = Backbone.View.extend({
events: {
'click .discussion-show': 'toggleDiscussion',
'keydown .discussion-show': function(event) {
return DiscussionUtil.activateOnSpace(event, this.toggleDiscussion);
},
'click .new-post-btn': 'toggleNewPost',
'click .all-posts-btn': 'navigateToAllPosts',
keydown: 'handleKeydown',
'keydown .new-post-btn': function(event) {
return DiscussionUtil.activateOnSpace(event, this.toggleNewPost);
}
},
page_re: /\?discussion_page=(\d+)/,
initialize: function(options) {
var match;
this.$el = options.el;
this.readOnly = options.readOnly;
this.showByDefault = options.showByDefault || false;
this.toggleDiscussionBtn = this.$('.discussion-show');
this.listenTo(this.model, 'change', this.render);
this.escKey = 27;
match = this.page_re.exec(window.location.href);
if (match) {
this.page = parseInt(match[1], 10);
} else {
this.page = 1;
}
// By default the view is displayed in a hidden state. If you want it to be shown by default (e.g. in Teams)
// pass showByDefault as an option. This code will open it on initialization.
if (this.showByDefault) {
this.toggleDiscussion();
}
},
loadDiscussions: function($elem, error) {
var discussionId = this.$el.data('discussion-id'),
url = DiscussionUtil.urlFor('retrieve_discussion', discussionId) + ('?page=' + this.page),
self = this;
DiscussionUtil.safeAjax({
$elem: this.$el,
$loading: this.$el,
takeFocus: true,
url: url,
type: 'GET',
dataType: 'json',
success: function(response, textStatus) {
self.renderDiscussion(self.$el, response, textStatus, discussionId);
},
error: error
});
},
renderDiscussion: function($elem, response, textStatus, discussionId) {
var discussionHtml,
user = new DiscussionUser(response.user_info),
self = this;
$elem.focus();
window.user = user;
DiscussionUtil.setUser(user);
Content.loadContentInfos(response.annotated_content_info);
DiscussionUtil.loadRoles(response.roles);
this.course_settings = new DiscussionCourseSettings(response.course_settings);
this.discussion = new Discussion(undefined, {pages: response.num_pages});
this.discussion.reset(response.discussion_data, {
silent: false
});
discussionHtml = edx.HtmlUtils.template($('#inline-discussion-template').html())({
threads: response.discussion_data,
read_only: this.readOnly,
discussionId: discussionId
});
if (this.$('section.discussion').length) {
edx.HtmlUtils.setHtml(this.$el, discussionHtml);
this.$('section.discussion').replaceWith(edx.HtmlUtils.ensureHtml(discussionHtml).toString());
} else {
edx.HtmlUtils.append(this.$el, discussionHtml);
}
this.threadListView = new DiscussionThreadListView({
el: this.$('.inline-threads'),
collection: self.discussion,
courseSettings: self.course_settings,
hideRefineBar: true // TODO: re-enable the search/filter bar when it works correctly
});
this.threadListView.render();
this.threadListView.on('thread:selected', _.bind(this.navigateToThread, this));
DiscussionUtil.bulkUpdateContentInfo(window.$$annotated_content_info);
this.newPostForm = this.$el.find('.new-post-article');
this.newPostView = new NewPostView({
el: this.newPostForm,
collection: this.discussion,
course_settings: this.course_settings,
topicId: discussionId,
is_commentable_cohorted: response.is_commentable_cohorted
});
this.newPostView.render();
this.listenTo(this.newPostView, 'newPost:createPost', this.onNewPostCreated);
this.listenTo(this.newPostView, 'newPost:cancel', this.hideNewPost);
this.discussion.on('add', this.addThread);
this.retrieved = true;
this.showed = true;
if (this.isWaitingOnNewPost) {
this.newPostForm.removeClass('is-hidden').focus();
}
// Hide the thread view initially
this.$('.inline-thread').addClass('is-hidden');
},
navigateToThread: function(threadId) {
var thread = this.discussion.get(threadId);
this.threadView = new DiscussionThreadView({
el: this.$('.forum-content'),
model: thread,
mode: 'inline',
course_settings: this.course_settings
});
this.threadView.render();
this.listenTo(this.threadView.showView, 'thread:_delete', this.navigateToAllPosts);
this.threadListView.$el.addClass('is-hidden');
this.$('.inline-thread').removeClass('is-hidden');
},
navigateToAllPosts: function() {
// Hide the inline thread section
this.$('.inline-thread').addClass('is-hidden');
// Delete the thread view
this.threadView.$el.empty().off();
this.threadView.stopListening();
this.threadView = null;
// Show the thread list view
this.threadListView.$el.removeClass('is-hidden');
// Set focus to thread list item that was saved as active
this.threadListView.$('.is-active').focus();
},
toggleDiscussion: function() {
var self = this;
if (this.showed) {
this.hideDiscussion();
} else {
this.toggleDiscussionBtn.addClass('shown');
this.toggleDiscussionBtn.find('.button-text').text(gettext('Hide Discussion'));
if (this.retrieved) {
this.$('section.discussion').removeClass('is-hidden');
this.showed = true;
} else {
this.loadDiscussions(this.$el, function() {
self.hideDiscussion();
DiscussionUtil.discussionAlert(
gettext('Error'),
gettext('This discussion could not be loaded. Refresh the page and try again.')
);
});
}
}
},
hideDiscussion: function() {
this.$('section.discussion').addClass('is-hidden');
this.toggleDiscussionBtn.removeClass('shown');
this.toggleDiscussionBtn.find('.button-text').text(gettext('Show Discussion'));
this.showed = false;
},
toggleNewPost: function(event) {
event.preventDefault();
if (!this.newPostForm) {
this.toggleDiscussion();
this.isWaitingOnNewPost = true;
return;
}
if (this.showed) {
this.$('section.discussion').find('.inline-discussion-thread-container').addClass('is-hidden');
this.$('section.discussion').find('.add_post_btn_container').addClass('is-hidden');
this.newPostForm.removeClass('is-hidden');
}
this.newPostView.$el.removeClass('is-hidden');
this.toggleDiscussionBtn.addClass('shown');
this.toggleDiscussionBtn.find('.button-text').text(gettext('Hide Discussion'));
this.showed = true;
},
onNewPostCreated: function() {
this.navigateToAllPosts();
this.hideNewPost();
},
hideNewPost: function() {
this.$('section.discussion').find('.inline-discussion-thread-container').removeClass('is-hidden');
this.$('section.discussion').find('.add_post_btn_container')
.removeClass('is-hidden')
.focus();
this.newPostForm.addClass('is-hidden');
},
handleKeydown: function(event) {
var keyCode = event.keyCode;
if (keyCode === this.escKey) {
this.$('section.discussion').find('.cancel').trigger('click');
}
}
});
}).call(window);
......@@ -91,6 +91,8 @@
DiscussionThreadListView.prototype.initialize = function(options) {
var self = this;
this.courseSettings = options.courseSettings;
this.hideRefineBar = options.hideRefineBar;
this.supportsActiveThread = options.supportsActiveThread;
this.displayedCollection = new Discussion(this.collection.models, {
pages: this.collection.pages
});
......@@ -107,7 +109,7 @@
this.boardName = null;
this.current_search = '';
this.mode = 'all';
this.showThreadPreview = options.showThreadPreview;
this.showThreadPreview = true;
this.searchAlertCollection = new Backbone.Collection([], {
model: Backbone.Model
});
......@@ -164,7 +166,7 @@
active = $currentElement.has('.forum-nav-thread-link.is-active').length !== 0;
$currentElement.replaceWith($content);
this.showMetadataAccordingToSort();
if (active) {
if (this.supportsActiveThread && active) {
this.setActiveThread(threadId);
}
};
......@@ -220,6 +222,9 @@
}
this.showMetadataAccordingToSort();
this.renderMorePages();
if (this.hideRefineBar) {
this.$('.forum-nav-refine-bar').addClass('is-hidden');
}
this.trigger('threads:rendered');
};
......@@ -309,7 +314,8 @@
error = function() {
self.renderThreads();
DiscussionUtil.discussionAlert(
gettext('Sorry'), gettext('We had some trouble loading more threads. Please try again.')
gettext('Error'),
gettext('Additional posts could not be loaded. Refresh the page and try again.')
);
};
return this.collection.retrieveAnotherPage(this.mode, options, {
......@@ -346,7 +352,9 @@
DiscussionThreadListView.prototype.threadSelected = function(e) {
var threadId;
threadId = $(e.target).closest('.forum-nav-thread').attr('data-id');
this.setActiveThread(threadId);
if (this.supportsActiveThread) {
this.setActiveThread(threadId);
}
this.trigger('thread:selected', threadId);
return false;
};
......@@ -478,7 +486,7 @@
element,
edx.HtmlUtils.joinHtml(
edx.HtmlUtils.HTML("<li class='forum-nav-load-more'>"),
self.getLoadingContent(gettext('Loading thread list')),
self.getLoadingContent(gettext('Loading posts list')),
edx.HtmlUtils.HTML('</li>')
)
);
......@@ -515,7 +523,7 @@
);
self.addSearchAlert(message);
} else if (response.discussion_data.length === 0) {
self.addSearchAlert(gettext('No threads matched your query.'));
self.addSearchAlert(gettext('No posts matched your query.'));
}
self.displayedCollection.reset(self.collection.models);
if (text) {
......
......@@ -152,14 +152,7 @@
});
});
}
if (this.mode === 'tab') {
setTimeout(function() {
return self.loadInitialResponses();
}, 100);
return this.$('.post-tools').hide();
} else {
return this.collapse();
}
this.loadInitialResponses();
};
DiscussionThreadView.prototype.attrRenderer = $.extend({}, DiscussionContentView.prototype.attrRenderer, {
......@@ -221,9 +214,7 @@
};
DiscussionThreadView.prototype.loadResponses = function(responseLimit, $elem, firstLoad) {
var takeFocus,
self = this;
takeFocus = this.mode === 'tab' ? false : true;
var self = this;
this.responsesRequest = DiscussionUtil.safeAjax({
url: DiscussionUtil.urlFor(
'retrieve_single_thread', this.model.get('commentable_id'), this.model.id
......@@ -234,7 +225,7 @@
},
$elem: $elem,
$loading: $elem,
takeFocus: takeFocus,
takeFocus: false,
complete: function() {
self.responsesRequest = null;
},
......@@ -253,7 +244,6 @@
);
self.trigger('thread:responses:rendered');
self.loadedResponses = true;
return self.$el.find('.discussion-article[data-id="' + self.model.id + '"]').focus();
},
error: function(xhr, textStatus) {
if (textStatus === 'abort') {
......@@ -261,18 +251,18 @@
}
if (xhr.status === 404) {
DiscussionUtil.discussionAlert(
gettext('Sorry'),
gettext('The thread you selected has been deleted. Please select another thread.')
gettext('Error'),
gettext('The post you selected has been deleted.')
);
} else if (firstLoad) {
DiscussionUtil.discussionAlert(
gettext('Sorry'),
gettext('We had some trouble loading responses. Please reload the page.')
gettext('Error'),
gettext('Responses could not be loaded. Refresh the page and try again.')
);
} else {
DiscussionUtil.discussionAlert(
gettext('Sorry'),
gettext('We had some trouble loading more responses. Please try again.')
gettext('Error'),
gettext('Additional responses could not be loaded. Refresh the page and try again.')
);
}
}
......
/* globals DiscussionTopicMenuView, DiscussionUtil, Thread */
/* globals _, Backbone, DiscussionTopicMenuView, DiscussionUtil, Thread */
(function() {
'use strict';
var __hasProp = {}.hasOwnProperty,
......@@ -81,14 +81,14 @@
};
NewPostView.prototype.getCohortOptions = function() {
var user_cohort_id;
var userCohortId;
if (this.course_settings.get('is_cohorted') && DiscussionUtil.isPrivilegedUser()) {
user_cohort_id = $('#discussion-container').data('user-cohort-id');
userCohortId = $('#discussion-container').data('user-cohort-id');
return _.map(this.course_settings.get('cohorts'), function(cohort) {
return {
value: cohort.id,
text: cohort.name,
selected: cohort.id === user_cohort_id
selected: cohort.id === userCohortId
};
});
} else {
......@@ -100,6 +100,7 @@
'submit .forum-new-post-form': 'createPost',
'change .post-option-input': 'postOptionChange',
'click .cancel': 'cancel',
'click .add-post-cancel': 'cancel',
'reset .forum-new-post-form': 'updateStyles'
};
......@@ -125,15 +126,15 @@
};
NewPostView.prototype.createPost = function(event) {
var anonymous, anonymous_to_peers, body, follow, group, thread_type, title, topicId, url,
var anonymous, anonymousToPeers, body, follow, group, threadType, title, topicId, url,
self = this;
event.preventDefault();
thread_type = this.$('.post-type-input:checked').val();
threadType = this.$('.post-type-input:checked').val();
title = this.$('.js-post-title').val();
body = this.$('.js-post-body').find('.wmd-input').val();
group = this.$('.js-group-select option:selected').attr('value');
anonymous = false || this.$('.js-anon').is(':checked');
anonymous_to_peers = false || this.$('.js-anon-peers').is(':checked');
anonymousToPeers = false || this.$('.js-anon-peers').is(':checked');
follow = false || this.$('.js-follow').is(':checked');
topicId = this.isTabMode() ? this.topicView.getCurrentTopicId() : this.topicId;
url = DiscussionUtil.urlFor('create_thread', topicId);
......@@ -144,11 +145,11 @@
type: 'POST',
dataType: 'json',
data: {
thread_type: thread_type,
thread_type: threadType,
title: title,
body: body,
anonymous: anonymous,
anonymous_to_peers: anonymous_to_peers,
anonymous_to_peers: anonymousToPeers,
auto_subscribe: follow,
group_id: group
},
......@@ -156,20 +157,29 @@
success: function(response) {
var thread;
thread = new Thread(response.content);
self.$el.hide();
self.$el.addClass('is-hidden');
self.resetForm();
self.trigger('newPost:createPost');
return self.collection.add(thread);
}
});
};
NewPostView.prototype.formModified = function() {
var postBodyHasContent = this.$('.js-post-body').find('.wmd-input').val() !== '',
titleHasContent = this.$('.js-post-title').val() !== '';
return postBodyHasContent || titleHasContent;
};
NewPostView.prototype.cancel = function(event) {
event.preventDefault();
if (!confirm(gettext('Your post will be discarded.'))) {
return;
if (this.formModified()) {
if (!confirm(gettext('Your post will be discarded.'))) { // eslint-disable-line no-alert
return;
}
}
this.trigger('newPost:cancel');
return this.resetForm();
this.resetForm();
};
NewPostView.prototype.resetForm = function() {
......@@ -177,7 +187,7 @@
DiscussionUtil.clearFormErrors(this.$('.post-errors'));
this.$('.wmd-preview p').html('');
if (this.isTabMode()) {
return this.topicView.setTopic(this.$('button.topic-title').first());
this.topicView.setTopic(this.$('button.topic-title').first());
}
};
......
......@@ -114,8 +114,8 @@
},
error: function() {
return DiscussionUtil.discussionAlert(
gettext('Sorry'),
gettext('We had some trouble deleting this comment. Please try again.')
gettext('Error'),
gettext('This comment could not be deleted. Refresh the page and try again.')
);
}
});
......
......@@ -29,7 +29,7 @@
});
spyOn(DiscussionUtil, 'discussionAlert');
DiscussionUtil.safeAjax.calls.mostRecent().args[0].error();
expect(DiscussionUtil.discussionAlert).toHaveBeenCalledWith('Sorry', 'error message');
expect(DiscussionUtil.discussionAlert).toHaveBeenCalledWith('Error', 'error message');
deferred.reject();
return expect(model.attributes).toEqual({
hello: false,
......
/* globals
_, Discussion, DiscussionCourseSettings, DiscussionViewSpecHelper, DiscussionSpecHelper,
DiscussionInlineView, DiscussionUtil, DiscussionThreadShowView, Thread
*/
(function() {
'use strict';
describe('DiscussionInlineView', function() {
var createTestView, showDiscussion, setNextAjaxResult,
TEST_THREAD_TITLE = 'Test thread title';
beforeEach(function() {
DiscussionSpecHelper.setUpGlobals();
setFixtures(
'<div class="discussion-module" data-discussion-id="test-discussion-id"' +
' data-user-create-comment="true"' +
' data-user-create-subcomment="true"' +
' data-read-only="false">' +
' <div class="discussion-module-header">' +
' <h3 class="discussion-module-title">Test Discussion</h3>' +
' <div class="inline-discussion-topic">' +
' <span class="inline-discussion-topic-title">Topic:</span> Category / Target ' +
' </div>' +
' </div>' +
' <button class="discussion-show btn btn-brand" data-discussion-id="test-discussion-id">' +
' <span class="button-text">Show Discussion</span>' +
' </button>' +
'</div>'
);
DiscussionSpecHelper.setUnderscoreFixtures();
this.ajaxSpy = spyOn($, 'ajax');
// Don't attempt to render markdown
spyOn(DiscussionUtil, 'makeWmdEditor');
spyOn(DiscussionThreadShowView.prototype, 'convertMath');
});
createTestView = function() {
var testView = new DiscussionInlineView({
el: $('.discussion-module')
});
testView.render();
return testView;
};
showDiscussion = function(test, testView) {
setNextAjaxResult(test, {
user_info: DiscussionSpecHelper.getTestUserInfo(),
roles: DiscussionSpecHelper.getTestRoleInfo(),
course_settings: DiscussionSpecHelper.createTestCourseSettings().attributes,
discussion_data: DiscussionViewSpecHelper.makeThreadWithProps({
commentable_id: 'test-topic',
title: TEST_THREAD_TITLE
}),
page: 1,
num_pages: 1,
content: {
endorsed_responses: [],
non_endorsed_responses: [],
children: []
}
});
testView.$('.discussion-show').click();
};
setNextAjaxResult = function(test, result) {
test.ajaxSpy.and.callFake(function(params) {
var deferred = $.Deferred();
deferred.resolve();
params.success(result);
return deferred;
});
};
describe('inline discussion', function() {
it('is shown after "Show Discussion" is clicked', function() {
var testView = createTestView(this),
showButton = testView.$('.discussion-show');
showDiscussion(this, testView);
// Verify that the discussion is now shown
expect(showButton).toHaveClass('shown');
expect(showButton.text().trim()).toEqual('Hide Discussion');
expect(testView.$('.inline-discussion:visible')).not.toHaveClass('is-hidden');
});
it('is hidden after "Hide Discussion" is clicked', function() {
var testView = createTestView(this),
showButton = testView.$('.discussion-show');
showDiscussion(this, testView);
// Hide the discussion by clicking the toggle button again
testView.$('.discussion-show').click();
// Verify that the discussion is now hidden
expect(showButton).not.toHaveClass('shown');
expect(showButton.text().trim()).toEqual('Show Discussion');
expect(testView.$('.inline-discussion:visible')).toHaveClass('is-hidden');
});
});
describe('new post form', function() {
it('should not be visible when the discussion is first shown', function() {
var testView = createTestView(this);
showDiscussion(this, testView);
expect(testView.$('.new-post-article')).toHaveClass('is-hidden');
});
it('should be shown when the "Add a Post" button is clicked', function() {
var testView = createTestView(this);
showDiscussion(this, testView);
testView.$('.new-post-btn').click();
expect(testView.$('.new-post-article')).not.toHaveClass('is-hidden');
});
it('should be hidden when the "Cancel" button is clicked', function() {
var testView = createTestView(this);
showDiscussion(this, testView);
testView.$('.new-post-btn').click();
testView.$('.forum-new-post-form .cancel').click();
expect(testView.$('.new-post-article')).toHaveClass('is-hidden');
});
it('should be hidden when the "Close" button is clicked', function() {
var testView = createTestView(this);
showDiscussion(this, testView);
testView.$('.new-post-btn').click();
testView.$('.forum-new-post-form .add-post-cancel').click();
expect(testView.$('.new-post-article')).toHaveClass('is-hidden');
});
it('should return to the thread listing after adding a post', function() {
var testView = createTestView(this);
showDiscussion(this, testView);
// Navigate to an individual thread
testView.$('.forum-nav-thread-link').click();
// Click "Add a Post", fill in the form, and submit it
testView.$('.new-post-btn').click();
testView.$('.js-post-title').text('Test title');
testView.$('.wmd-input').text('Test body');
setNextAjaxResult(this, {
hello: 'world'
});
testView.$('.forum-new-post-form .submit').click();
// Verify that the list of threads is shown
expect(testView.$('.inline-threads')).not.toHaveClass('is-hidden');
// Verify that the individual thread is no longer shown
expect(testView.$('.group-visibility-label').length).toBe(0);
});
});
describe('thread listing', function() {
it('builds a view that lists the threads', function() {
var testView = createTestView(this);
showDiscussion(this, testView);
expect(testView.$('.forum-nav-thread-title').text()).toBe(TEST_THREAD_TITLE);
});
});
describe('thread post drill down', function() {
it('can drill down to a thread', function() {
var testView = createTestView(this);
showDiscussion(this, testView);
testView.$('.forum-nav-thread-link').click();
// Verify that the list of threads is hidden
expect(testView.$('.inline-threads')).toHaveClass('is-hidden');
// Verify that the individual thread is shown
expect(testView.$('.group-visibility-label').text().trim()).toBe('This post is visible to everyone.');
});
it('can go back to the list of threads', function() {
var testView = createTestView(this);
showDiscussion(this, testView);
testView.$('.forum-nav-thread-link').click();
testView.$('.all-posts-btn').click();
// Verify that the list of threads is shown
expect(testView.$('.inline-threads')).not.toHaveClass('is-hidden');
// Verify that the individual thread is no longer shown
expect(testView.$('.group-visibility-label').length).toBe(0);
});
});
});
}());
......@@ -188,12 +188,11 @@
renderSingleThreadWithProps = function(props) {
return makeView(new Discussion([new Thread(DiscussionViewSpecHelper.makeThreadWithProps(props))])).render();
};
makeView = function(discussion, options) {
var opts = options || {};
makeView = function(discussion) {
return new DiscussionThreadListView({
el: $('#fixture-element'),
collection: discussion,
showThreadPreview: opts.showThreadPreview || true,
showThreadPreview: true,
courseSettings: new DiscussionCourseSettings({
is_cohorted: true
})
......@@ -551,7 +550,7 @@
it('does not add a search alert when no alternate term was searched', function() {
testCorrection(this.view, null);
expect(this.view.addSearchAlert.calls.count()).toEqual(1);
return expect(this.view.addSearchAlert.calls.mostRecent().args[0]).toMatch(/no threads matched/i);
return expect(this.view.addSearchAlert.calls.mostRecent().args[0]).toMatch(/no posts matched/i);
});
it('clears search alerts when a new search is performed', function() {
......@@ -676,10 +675,8 @@
it('should not be shown when showThreadPreview is false', function() {
var view,
discussion = new Discussion([]),
options = {
showThreadPreview: false
};
view = makeView(discussion, options);
showThreadPreview = false;
view = makeView(discussion, showThreadPreview);
view.render();
expect(view.$el.find('.thread-preview-body').length).toEqual(0);
});
......
/* global Content, Discussion, DiscussionCourseSettings, DiscussionUtil, DiscussionUser */
/* global _, Content, Discussion, DiscussionCourseSettings, DiscussionUtil, DiscussionUser */
(function() {
'use strict';
this.DiscussionSpecHelper = (function() {
......@@ -51,23 +51,29 @@
return jasmine.createSpyObj('event', ['preventDefault', 'target']);
};
DiscussionSpecHelper.createTestCourseSettings = function() {
return new DiscussionCourseSettings({
category_map: {
children: [['Test Topic', 'entry'], ['Other Topic', 'entry']],
entries: {
'Test Topic': {
is_cohorted: true,
id: 'test_topic'
},
'Other Topic': {
is_cohorted: true,
id: 'other_topic'
DiscussionSpecHelper.createTestCourseSettings = function(options) {
var context = _.extend(
{
category_map: {
children: [['Test Topic', 'entry'], ['Other Topic', 'entry']],
entries: {
'Test Topic': {
is_cohorted: true,
id: 'test_topic'
},
'Other Topic': {
is_cohorted: true,
id: 'other_topic'
}
}
}
},
is_cohorted: true,
allow_anonymous: false,
allow_anonymous_to_peers: false
},
is_cohorted: true
});
options || {}
);
return new DiscussionCourseSettings(context);
};
DiscussionSpecHelper.createTestDiscussion = function(options) {
......
<section class="discussion inline-discussion" data-discussion-id="<%= discussionId %>">
<div class="add_post_btn_container">
<button class="btn-link new-post-btn <%if (read_only) {%>is-hidden<%} %>"><%- gettext("Add a Post") %></button>
<section class="discussion inline-discussion" data-discussion-id="<%- discussionId %>">
<div class="add_post_btn_container <%if (read_only) {%>is-hidden<%} %>">
<button class="btn-link new-post-btn"><%- gettext("Add a Post") %></button>
</div>
<article class="new-post-article"></article>
<article class="new-post-article is-hidden"></article>
<section class="threads">
<% _.each(threads, function(thread) { %>
<article class="discussion-thread" id="thread_<%= thread.id %>">
</article>
<% }); %>
</section>
<div class="inline-discussion-thread-container">
<section class="inline-threads">
</section>
<section class="discussion-pagination">
</section>
<div class="inline-thread">
<div class="forum-nav-bar">
<button class="btn-link all-posts-btn">
<span class="icon fa fa-chevron-prev" aria-hidden="true"></span>
<span aria-hidden="true"><%- gettext('All Posts') %></span>
<span class="sr-only"><%- gettext('Return to all posts') %></span>
</button>
</div>
<div class="forum-content">
</div>
</div>
</div>
</section>
<form class="forum-new-post-form">
<% if (mode === 'inline') { %>
<h3 class="thread-title"><%- gettext("Add a Post") %></h3>
<button class="btn-default add-post-cancel">
<span class="sr"><%- gettext('Cancel') %></span>
<span class="fa fa-close" aria-hidden="true"></span>
</button>
<% } %>
<ul class="post-errors" style="display: none"></ul>
<div class="forum-new-post-form-wrapper"></div>
<% if (cohort_options) { %>
......
<header class="wrapper-response-header">
<header class="response-header">
<div class="response-header-content">
<%= author_display %>
<p class="posted-details">
......
<div class="discussion-post">
<header class="wrapper-post-header">
<header class="post-header">
<% if (!readOnly) { %>
<div class="post-header-actions post-extended-content">
<%=
_.template(
$('#forum-actions').html())(
{
contentId: cid,
contentType: 'post',
primaryActions: ['vote', 'follow'],
secondaryActions: ['pin', 'edit', 'delete', 'report', 'close'],
readOnly: readOnly
}
)
%>
</div>
<% } %>
<div class="post-header-content">
<h1 class="post-title"><%- title %></h1>
<h4 class="post-title"><%- title %></h4>
<p class="posted-details">
<%
var timeAgoHtml = interpolate(
......@@ -32,27 +48,11 @@
</span>
</div>
</div>
<% if (!readOnly) { %>
<div class="post-header-actions post-extended-content">
<%=
_.template(
$('#forum-actions').html())(
{
contentId: cid,
contentType: 'post',
primaryActions: ['vote', 'follow'],
secondaryActions: ['pin', 'edit', 'delete', 'report', 'close'],
readOnly: readOnly
}
)
%>
</div>
<% } %>
</header>
<div class="post-body"><%- body %></div>
<div class="post-context">
<% if (mode == "tab" && obj.courseware_url) { %>
<% if (mode === "tab" && obj.courseware_url) { %>
<%
var courseware_title_linked = interpolate(
'<a href="%(courseware_url)s">%(courseware_title)s</a>',
......
......@@ -9,7 +9,7 @@
<div class="post-extended-content thread-responses-wrapper">
<% if (!readOnly) { %>
<div class="add-response">
<button class="btn-brand btn-small add-response-btn">
<button class="btn btn-small add-response-btn">
<%- gettext("Add a Response") %>
</button>
</div>
......@@ -26,14 +26,10 @@
<ul class="discussion-errors"></ul>
<div class="reply-body" data-id="<%- id %>"></div>
<div class="reply-post-control">
<button class="btn-brand discussion-submit-post control-button"><%- gettext("Submit") %></button>
<button class="btn discussion-submit-post control-button"><%- gettext("Submit") %></button>
</div>
</form>
<% } %>
</div>
</div>
<div class="post-tools">
<button class="btn-link forum-thread-expand"><span class="icon fa fa-plus" aria-hidden="true"/><%- gettext("Expand discussion") %></button>
<button class="btn-link forum-thread-collapse"><span class="icon fa fa-minus" aria-hidden="true"/><%- gettext("Collapse discussion") %></button>
</div>
</article>
......@@ -51,6 +51,7 @@ class Thread(ContentFactory):
group_id = None
pinned = False
read = False
context = "course"
class Comment(ContentFactory):
......
......@@ -12,6 +12,7 @@ from common.test.acceptance.fixtures.discussion import (
Thread,
Response,
ForumsConfigMixin,
MultipleThreadFixture,
)
from common.test.acceptance.pages.lms.discussion import DiscussionTabSingleThreadPage
from common.test.acceptance.tests.helpers import UniqueCourseTest
......@@ -37,6 +38,22 @@ class BaseDiscussionMixin(object):
self.setup_thread_page(thread_id)
return thread_id
def setup_multiple_threads(self, thread_count, **thread_kwargs):
"""
Set up multiple threads on the page by passing 'thread_count'.
"""
self.thread_ids = [] # pylint: disable=attribute-defined-outside-init
threads = [] # pylint: disable=attribute-defined-outside-init
for i in range(thread_count):
thread_id = "test_thread_{}_{}".format(i, uuid4().hex)
thread_body = "Dummy long text body." * 50
threads.append(
Thread(id=thread_id, commentable_id=self.discussion_id, body=thread_body, **thread_kwargs),
)
self.thread_ids.append(thread_id)
thread_fixture = MultipleThreadFixture(threads)
thread_fixture.push()
class CohortTestMixin(object):
"""
......
......@@ -129,8 +129,8 @@ class InlineDiscussionTest(UniqueCourseTest):
discussion_page = InlineDiscussionPage(self.browser, self.discussion_id)
discussion_page.expand_discussion()
self.assertEqual(discussion_page.get_num_displayed_threads(), 1)
self.thread_page = InlineDiscussionThreadPage(self.browser, thread_id) # pylint: disable=attribute-defined-outside-init
self.thread_page.expand()
discussion_page.show_thread(thread_id)
self.thread_page = discussion_page.thread_page # pylint: disable=attribute-defined-outside-init
def refresh_thread_page(self, thread_id):
self.browser.refresh()
......
......@@ -1689,7 +1689,8 @@ class TeamPageTest(TeamsTabBase):
thread = Thread(
id="test_thread_{}".format(uuid4().hex),
commentable_id=self.teams[0]['discussion_topic_id'],
body="Dummy text body."
body="Dummy text body.",
context="standalone",
)
thread_fixture = MultipleThreadFixture([thread])
thread_fixture.push()
......@@ -1718,14 +1719,15 @@ class TeamPageTest(TeamsTabBase):
thread = self.setup_thread()
self.team_page.visit()
self.assertEqual(self.team_page.discussion_id, self.teams[0]['discussion_topic_id'])
discussion = self.team_page.discussion_page
discussion.wait_for_page()
self.assertTrue(discussion.is_discussion_expanded())
self.assertEqual(discussion.get_num_displayed_threads(), 1)
self.assertTrue(discussion.has_thread(thread['id']))
discussion_page = self.team_page.discussion_page
discussion_page.wait_for_page()
self.assertTrue(discussion_page.is_discussion_expanded())
self.assertEqual(discussion_page.get_num_displayed_threads(), 1)
discussion_page.show_thread(thread['id'])
thread_page = discussion_page.thread_page
assertion = self.assertTrue if should_have_permission else self.assertFalse
assertion(discussion.q(css='.post-header-actions').present)
assertion(discussion.q(css='.add-response').present)
assertion(thread_page.q(css='.post-header-actions').present)
assertion(thread_page.q(css='.add-response').present)
def test_discussion_on_my_team_page(self):
"""
......
......@@ -4,17 +4,28 @@
define(
[
'jquery',
'backbone',
'common/js/discussion/content',
'common/js/discussion/discussion',
'common/js/discussion/utils',
'common/js/discussion/models/discussion_user',
'common/js/discussion/models/discussion_course_settings',
'discussion/js/views/discussion_user_profile_view'
],
function($, DiscussionUtil, DiscussionUser, DiscussionUserProfileView) {
function($, Backbone, Content, Discussion, DiscussionUtil, DiscussionUser, DiscussionCourseSettings,
DiscussionUserProfileView) {
return function(options) {
var $element = options.$el,
threads = options.threads,
var threads = options.threads,
contentInfo = options.contentInfo,
userInfo = options.userInfo,
user = new DiscussionUser(userInfo),
page = options.page,
numPages = options.numPages;
numPages = options.numPages,
sortPreference = options.sortPreference,
discussionUserProfileView,
discussion,
courseSettings;
// Roles are not included in user profile page, but they are not used for anything
DiscussionUtil.loadRoles({
Moderator: [],
......@@ -22,16 +33,25 @@
'Community TA': []
});
// TODO: remove global variable usage
DiscussionUtil.loadRoles(options.roles);
window.$$course_id = options.courseId;
window.user = new DiscussionUser(userInfo);
window.courseName = options.course_name;
DiscussionUtil.setUser(user);
window.user = user;
Content.loadContentInfos(contentInfo);
// Create a discussion model
discussion = new Discussion(threads, {pages: numPages, sort: sortPreference});
courseSettings = new DiscussionCourseSettings(options.courseSettings);
new DiscussionUserProfileView({ // eslint-disable-line no-new
el: $element,
collection: threads,
discussionUserProfileView = new DiscussionUserProfileView({
el: $('.discussion-user-threads'),
discussion: discussion,
page: page,
numPages: numPages
numPages: numPages,
courseSettings: courseSettings
});
discussionUserProfileView.render();
};
});
}).call(this, define || RequireJS.define);
/* globals Discussion */
define(
[
'underscore',
......@@ -15,10 +17,12 @@ define(
DiscussionProfilePageFactory(_.extend(
{
courseId: testCourseId,
$el: $('.discussion-user-threads'),
user_info: DiscussionSpecHelper.getTestUserInfo(),
roles: DiscussionSpecHelper.getTestRoleInfo(),
sort_preference: null,
courseSettings: DiscussionSpecHelper.createTestCourseSettings().attributes,
el: $('.discussion-user-threads'),
discussion: new Discussion(),
userInfo: DiscussionSpecHelper.getTestUserInfo(),
sortPreference: null,
threads: [],
page: 1,
numPages: 5
......@@ -34,7 +38,7 @@ define(
it('can render itself', function() {
initializeDiscussionProfilePageFactory();
expect($('.discussion-user-threads').text()).toContain('Active Threads');
expect($('.discussion-user-threads').text()).toContain('Show');
});
});
}
......
......@@ -32,7 +32,6 @@
initialize: function(options) {
this.courseSettings = options.courseSettings;
this.showThreadPreview = true;
this.sidebar_padding = 10;
this.current_search = '';
this.mode = 'all';
......@@ -47,7 +46,7 @@
collection: this.discussion,
el: this.$('.discussion-thread-list-container'),
courseSettings: this.courseSettings,
showThreadPreview: this.showThreadPreview
supportsActiveThread: true
}).render();
this.searchView = new DiscussionSearchView({
el: this.$('.forum-search')
......
/* globals DiscussionThreadView */
(function(define) {
'use strict';
......@@ -13,77 +14,67 @@
'common/js/discussion/utils',
'common/js/discussion/views/discussion_thread_profile_view',
'text!discussion/templates/user-profile.underscore',
'text!common/templates/discussion/pagination.underscore'
'common/js/discussion/views/discussion_thread_list_view'
],
function(_, $, Backbone, gettext, URI, HtmlUtils, ViewUtils, Discussion, DiscussionUtil,
DiscussionThreadProfileView, userProfileTemplate, paginationTemplate) {
DiscussionThreadProfileView, userProfileTemplate, DiscussionThreadListView) {
var DiscussionUserProfileView = Backbone.View.extend({
events: {
'click .discussion-paginator a': 'changePage'
'click .all-posts-btn': 'navigateToAllThreads'
},
initialize: function(options) {
Backbone.View.prototype.initialize.call(this);
this.page = options.page;
this.numPages = options.numPages;
this.discussion = new Discussion();
this.discussion.on('reset', _.bind(this.render, this));
this.discussion.reset(this.collection, {silent: false});
this.courseSettings = options.courseSettings;
this.discussion = options.discussion;
this.mode = 'all';
this.listenTo(this.model, 'change', this.render);
},
render: function() {
var self = this,
baseUri = URI(window.location).removeSearch('page'),
pageUrlFunc,
paginationParams;
HtmlUtils.setHtml(this.$el, HtmlUtils.template(userProfileTemplate)({
threads: self.discussion.models
}));
this.discussion.map(function(thread) {
var view = new DiscussionThreadProfileView({
el: self.$('article#thread_' + thread.id),
model: thread
});
view.render();
return view;
});
pageUrlFunc = function(page) {
return baseUri.clone().addSearch('page', page).toString();
};
paginationParams = DiscussionUtil.getPaginationParams(this.page, this.numPages, pageUrlFunc);
HtmlUtils.setHtml(
this.$el.find('.discussion-pagination'),
HtmlUtils.template(paginationTemplate)(paginationParams)
HtmlUtils.setHtml(this.$el,
HtmlUtils.template(userProfileTemplate)({})
);
this.discussionThreadListView = new DiscussionThreadListView({
collection: this.discussion,
el: this.$('.inline-threads'),
courseSettings: this.courseSettings,
hideRefineBar: true // TODO: re-enable the search/filter bar when it works correctly
}).render();
this.discussionThreadListView.on('thread:selected', _.bind(this.navigateToThread, this));
return this;
},
changePage: function(event) {
var self = this,
url;
event.preventDefault();
url = $(event.target).attr('href');
DiscussionUtil.safeAjax({
$elem: this.$el,
$loading: $(event.target),
takeFocus: true,
url: url,
type: 'GET',
dataType: 'json',
success: function(response) {
self.page = response.page;
self.numPages = response.num_pages;
self.discussion.reset(response.discussion_data, {silent: false});
history.pushState({}, '', url);
ViewUtils.setScrollTop(0);
},
error: function() {
DiscussionUtil.discussionAlert(
gettext('Sorry'),
gettext('We had some trouble loading the page you requested. Please try again.')
);
}
navigateToThread: function(threadId) {
var thread = this.discussion.get(threadId);
this.threadView = new DiscussionThreadView({
el: this.$('.forum-content'),
model: thread,
mode: 'inline',
course_settings: this.courseSettings
});
this.threadView.render();
this.listenTo(this.threadView.showView, 'thread:_delete', this.navigateToAllThreads);
this.discussionThreadListView.$el.addClass('is-hidden');
this.$('.inline-thread').removeClass('is-hidden');
},
navigateToAllThreads: function() {
// Hide the inline thread section
this.$('.inline-thread').addClass('is-hidden');
// Delete the thread view
this.threadView.$el.empty().off();
this.threadView.stopListening();
this.threadView = null;
// Show the thread list view
this.discussionThreadListView.$el.removeClass('is-hidden');
// Set focus to thread list item that was saved as active
this.discussionThreadListView.$('.is-active').focus();
}
});
......
......@@ -6,4 +6,4 @@
id="search"
placeholder="<%- gettext("Search all posts") %>"
/>
<button class="btn-brand btn-small search-btn" type="button"><%- gettext("Search") %></button>
<button class="btn btn-small search-btn" type="button"><%- gettext("Search") %></button>
<h2><%- gettext("Active Threads") %></h2>
<section class="discussion">
<% _.each(threads, function(thread) { %>
<article class="discussion-thread" id="thread_<%- thread.id %>"/>
<% }); %>
</section>
<section class="discussion-pagination"/>
<div class="inline-threads"></div>
<div class="inline-thread is-hidden">
<div class="forum-nav-bar">
<button class="btn-link all-posts-btn">
<span class="icon fa fa-chevron-prev" aria-hidden="true"></span>
<span><%- gettext("All Posts") %></span>
</button>
</div>
<div class="forum-content">
</div>
</div>
......@@ -67,7 +67,7 @@ DiscussionBoardFactory({
## Add Post button
% if has_permission(user, 'create_thread', course.id):
<div class="form-actions">
<button class="btn-brand btn-small new-post-btn">${_("Add a Post")}</button>
<button class="btn btn-small new-post-btn">${_("Add a Post")}</button>
</div>
% endif
## Search box
......@@ -82,7 +82,7 @@ DiscussionBoardFactory({
</aside>
<main id="main" aria-label="Content" tabindex="-1" class="discussion-column layout-col layout-col-b">
<article class="new-post-article" style="display: none" tabindex="-1" aria-label="${_("New topic form")}"></article>
<article class="new-post-article is-hidden" style="display: none" tabindex="-1" aria-label="${_("New topic form")}"></article>
<div class="forum-content"></div>
</main>
</div>
......
......@@ -5,15 +5,18 @@
<%page expression_filter="h"/>
<%inherit file="../main.html" />
<%namespace name='static' file='../static_content.html'/>
<%def name="online_help_token()"><% return "discussions" %></%def>
<%!
from django.utils.translation import ugettext as _
import json
from django.utils.translation import ugettext as _, ungettext
from django.template.defaultfilters import escapejs
from openedx.core.djangolib.js_utils import (
dump_js_escaped_json, js_escaped_string
)
from django.core.urlresolvers import reverse
from django_comment_client.permissions import has_permission
from openedx.core.djangolib.js_utils import dump_js_escaped_json, js_escaped_string
%>
<%block name="bodyclass">discussion-user-profile</%block>
<%block name="bodyclass">discussion discussion-user-profile</%block>
<%block name="pagetitle">${_("Discussion - {course_number}").format(course_number=course.display_number_with_default)}</%block>
<%block name="headextra">
......@@ -22,57 +25,70 @@ from openedx.core.djangolib.js_utils import (
<%block name="js_extra">
<%include file="_js_body_dependencies.html" />
<%static:require_module module_name="discussion/js/discussion_profile_page_factory" class_name="DiscussionProfilePageFactory">
<%
profile_page_context = {
'courseId': unicode(course.id),
'courseName': course.display_name_with_default,
'userInfo': user_info,
'threads': threads,
'page': page,
'numPages': num_pages,
'courseSettings': ${course_settings | n, dump_js_escaped_json},
'courseId': '${unicode(course.id) | n, js_escaped_string}',
'courseName': '${course.display_name_with_default | n, js_escaped_string}',
'contentInfo': ${annotated_content_info | n, dump_js_escaped_json},
'userInfo': ${user_info | n, dump_js_escaped_json},
'roles': ${roles | n, dump_js_escaped_json},
'threads': ${threads | n, dump_js_escaped_json},
'page': ${page | n, dump_js_escaped_json},
'sortPreference': '${sort_preference | n, js_escaped_string}',
'numPages': ${num_pages | n, dump_js_escaped_json}
}
%>
DiscussionProfilePageFactory(_.extend(
{
$el: $('.discussion-user-threads')
el: $('.discussion-user-profile-board')
},
${profile_page_context | n, dump_js_escaped_json}
profile_page_context
));
</%static:require_module>
</%block>
<%include file="../courseware/course_navigation.html" args="active_page='discussion'" />
<section class="container">
<%block name="content">
<section class="discussion inline-discussion discussion-user-profile-board container">
<header class="page-header">
<div class="page-header-main">
<div class="sr-is-focusable" tabindex="-1"></div>
<h2 class="hd hd-2 page-title">${_("Discussion")}</h2>
<div>
<h2 class="discussion-profile-title">${_("Discussion")}</h2>
<%def name="span(num)"><span class="discussion-count">${num}</span></%def>
<span class="user-name"><a href="${learner_profile_page_url}">${django_user.username}</a></span>
<span class="discussion-profile-info">
<span class="user-roles">${", ".join(_(role_name) for role_name in django_user_roles)}</span>
</span>
<div class="discussion-profile-count">
<span class="discussion-profile-info threads-count">${ungettext('%s discussion started', '%s discussions started', profiled_user['threads_count']) % span(profiled_user['threads_count'])}</span>
<span class="discussion-profile-info comments-count">${ungettext('%s comment', '%s comments', profiled_user['comments_count']) % span(profiled_user['comments_count'])}</span>
</div>
</div>
</div>
</header>
<div class="page-content">
<div class="layout layout-1t2t">
<aside class="forum-nav layout-col layout-col-a" role="complementary" aria-label="${_("Discussion thread list")}">
<nav class="user-profile" aria-label="${_('User Profile')}">
<article class="sidebar-module discussion-sidebar">
<%include file="_user_profile.html" />
</article>
</nav>
</aside>
<main id="main" aria-label="Content" tabindex="-1" class="discussion-column layout-col layout-col-b">
<div class="course-content discussion-user-threads" data-course-id="${course.id}"
data-course-name="${course.display_name_with_default}"
data-threads="${threads}" data-user-info="${user_info}"
data-page="${page}" data-num-pages="${num_pages}">
</div>
</main>
</div>
<main id="main" aria-label="Content" tabindex="-1" class="discussion-column">
<div class="course-content discussion-module discussion-user-threads"
data-course-id="${course.id}"
data-course-name="${course.display_name_with_default}"
data-threads="${threads}"
data-user-info="${user_info}"
data-page="${page}"
data-num-pages="${num_pages}"
data-user-create-comment="${json.dumps(can_create_comment)}"
data-user-create-subcomment="${json.dumps(can_create_subcomment)}"
data-read-only="false"
data-sort-preference="${sort_preference}"
data-flag-moderator="${json.dumps(flag_moderator)}"
data-user-cohort-id="${user_cohort}">
</div>
</main>
</div>
</section>
</%block>
<%include file="_underscore_templates.html" />
<%include file="_thread_list_template.html" />
......@@ -1081,9 +1081,7 @@ class InlineDiscussionTestCase(ForumsEnableMixin, ModuleStoreTestCase):
"""Verifies that the response contains the appropriate courseware_url and courseware_title"""
self.assertEqual(response.status_code, 200)
response_data = json.loads(response.content)
expected_courseware_url = '/courses/TestX/101/Test_Course/jump_to/i4x://TestX/101/discussion/Discussion1'
expected_courseware_title = 'Chapter / Discussion1'
self.assertEqual(response_data['discussion_data'][0]['courseware_url'], expected_courseware_url)
self.assertEqual(response_data["discussion_data"][0]["courseware_title"], expected_courseware_title)
def test_courseware_data(self, mock_request):
......@@ -1155,8 +1153,8 @@ class UserProfileTestCase(ForumsEnableMixin, UrlResetMixin, ModuleStoreTestCase)
html = response.content
self.assertRegexpMatches(html, r'data-page="1"')
self.assertRegexpMatches(html, r'data-num-pages="1"')
self.assertRegexpMatches(html, r'<span>1</span> discussion started')
self.assertRegexpMatches(html, r'<span>2</span> comments')
self.assertRegexpMatches(html, r'<span class="discussion-count">1</span> discussion started')
self.assertRegexpMatches(html, r'<span class="discussion-count">2</span> comments')
self.assertRegexpMatches(html, r'&#39;id&#39;: &#39;{}&#39;'.format(self.TEST_THREAD_ID))
self.assertRegexpMatches(html, r'&#39;title&#39;: &#39;{}&#39;'.format(self.TEST_THREAD_TEXT))
self.assertRegexpMatches(html, r'&#39;body&#39;: &#39;{}&#39;'.format(self.TEST_THREAD_TEXT))
......@@ -1181,15 +1179,9 @@ class UserProfileTestCase(ForumsEnableMixin, UrlResetMixin, ModuleStoreTestCase)
def test_html(self, mock_request):
self.check_html(mock_request)
def test_html_p2(self, mock_request):
self.check_html(mock_request, page="2")
def test_ajax(self, mock_request):
self.check_ajax(mock_request)
def test_ajax_p2(self, mock_request):
self.check_ajax(mock_request, page="2")
def test_404_non_enrolled_user(self, __):
"""
Test that when student try to visit un-enrolled students' discussion profile,
......
......@@ -408,8 +408,10 @@ def user_profile(request, course_key, user_id):
nr_transaction = newrelic.agent.current_transaction()
#TODO: Allow sorting?
user = cc.User.from_django_user(request.user)
user_info = user.to_dict()
course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=True)
course_settings = make_course_settings(course, request.user)
try:
# If user is not enrolled in the course, do not proceed.
......@@ -442,6 +444,8 @@ def user_profile(request, course_key, user_id):
is_staff = has_permission(request.user, 'openclose_thread', course.id)
threads = [utils.prepare_content(thread, course_key, is_staff) for thread in threads]
with newrelic.agent.FunctionTrace(nr_transaction, "add_courseware_context"):
add_courseware_context(threads, course, request.user)
if request.is_ajax():
return utils.JsonResponse({
'discussion_data': threads,
......@@ -454,6 +458,9 @@ def user_profile(request, course_key, user_id):
course_id=course.id
).order_by("name").values_list("name", flat=True).distinct()
with newrelic.agent.FunctionTrace(nr_transaction, "get_cohort_info"):
user_cohort_id = get_cohort_id(request.user, course_key)
context = {
'course': course,
'user': request.user,
......@@ -462,9 +469,20 @@ def user_profile(request, course_key, user_id):
'profiled_user': profiled_user.to_dict(),
'threads': threads,
'user_info': user_info,
'roles': utils.get_role_ids(course_key),
'can_create_comment': has_permission(request.user, "create_comment", course.id),
'can_create_subcomment': has_permission(request.user, "create_sub_comment", course.id),
'can_create_thread': has_permission(request.user, "create_thread", course.id),
'flag_moderator': bool(
has_permission(request.user, 'openclose_thread', course.id) or
has_access(request.user, 'staff', course)
),
'user_cohort': user_cohort_id,
'annotated_content_info': annotated_content_info,
'page': query_params['page'],
'num_pages': query_params['num_pages'],
'sort_preference': user.default_sort_key,
'course_settings': course_settings,
'learner_profile_page_url': reverse('learner_profile', kwargs={'username': django_user.username}),
'disable_courseware_js': True,
'uses_pattern_library': True,
......
......@@ -101,7 +101,7 @@ define([
it('can render itself', function() {
var requests = AjaxHelpers.requests(this),
view = createTeamProfileView(requests, {});
expect(view.$('.discussion-thread').length).toEqual(3);
expect(view.$('.forum-nav-thread').length).toEqual(3);
});
it('shows New Post button when user joins a team', function() {
......
......@@ -3,21 +3,21 @@
*/
(function(define) {
'use strict';
define(['backbone', 'underscore', 'gettext', 'common/js/discussion/discussion_module_view'],
function(Backbone, _, gettext, DiscussionModuleView) {
define(['backbone', 'underscore', 'gettext', 'common/js/discussion/views/discussion_inline_view'],
function(Backbone, _, gettext, DiscussionInlineView) {
var TeamDiscussionView = Backbone.View.extend({
initialize: function() {
initialize: function(options) {
window.$$course_id = this.$el.data('course-id');
this.readOnly = options.readOnly;
},
render: function() {
var discussionModuleView = new DiscussionModuleView({
var discussionInlineView = new DiscussionInlineView({
el: this.$el,
readOnly: this.$el.data('read-only') === true,
context: 'standalone'
showByDefault: true,
readOnly: this.readOnly
});
discussionModuleView.render();
discussionModuleView.loadPage(this.$el);
discussionInlineView.render();
return this;
}
});
......
......@@ -56,7 +56,7 @@
);
this.discussionView = new TeamDiscussionView({
el: this.$('.discussion-module'),
readOnly: this.$('.discussion-module').data('read-only') === true
readOnly: !isMember
});
this.discussionView.render();
......
## mako
<%page expression_filter="h"/>
<%! import json %>
<%!
from django.utils.translation import ugettext as _
......@@ -55,3 +58,4 @@ from openedx.core.djangolib.js_utils import (
</%block>
<%include file="../discussion/_underscore_templates.html" />
<%include file="../discussion/_thread_list_template.html" />
......@@ -79,7 +79,7 @@
'logger': 'empty:',
'utility': 'empty:',
'URI': 'empty:',
'common/js/discussion/discussion_module_view': 'empty:',
'common/js/discussion/views/discussion_inline_view': 'empty:',
'modernizr': 'empty',
// Don't bundle UI Toolkit helpers as they are loaded into the "edx" namespace
......
......@@ -642,7 +642,7 @@
],
exports: 'ThreadResponseView'
},
'common/js/discussion/discussion_module_view': {
'common/js/discussion/views/discussion_inline_view': {
deps: [
'jquery',
'underscore',
......@@ -666,7 +666,7 @@
'common/js/discussion/views/thread_response_show_view',
'common/js/discussion/views/thread_response_view'
],
exports: 'DiscussionModuleView'
exports: 'DiscussionInlineView'
},
'common/js/spec_helpers/discussion_spec_helper': {
deps: [
......
......@@ -37,5 +37,6 @@ $static-path: '../..' !default;
@import 'views/create-edit-post';
@import 'views/response';
@import 'views/search';
@import 'views/inline';
@import 'utilities/developer';
@import 'utilities/shame';
......@@ -54,7 +54,6 @@
.discussion-module {
.discussion {
clear: both;
padding-top: ($baseline/2);
}
.btn {
......
......@@ -2,7 +2,7 @@
// ====================
// NOTE: this file is deprecated, and we should not continue to add to this file. Use other partials as appropriate.
body.discussion {
.discussion-body {
.edit-post-form {
@include clearfix();
......@@ -175,18 +175,17 @@ body.discussion {
}
}
.container .discussion-body {
.discussion-body {
@include clearfix();
border: none;
background: transparent;
box-shadow: none;
line-height: 1.4;
.bottom-post-status {
padding: 30px;
font-size: $forum-x-large-font-size;
font-weight: 700;
color: $gray-l3;
color: $forum-color-copy-light;
text-align: center;
}
......@@ -196,18 +195,10 @@ body.discussion {
a {
word-wrap: break-word;
}
p + p {
margin-top: $baseline;
}
}
.responses li header {
margin-bottom: $baseline;
}
blockquote {
background: $gray-l5;
background: $forum-color-background-light;
border-radius: $forum-border-radius;
padding: ($baseline/4) ($baseline/2);
font-size: $forum-base-font-size;
......@@ -252,7 +243,6 @@ body.discussion {
.discussion-reply-new {
@include clearfix();
@include transition(opacity .2s linear 0s);
padding: 0 ($baseline/2);
h4 {
font-size: $forum-large-font-size;
......@@ -296,9 +286,6 @@ body.discussion {
@extend .discussion-body;
display: block;
position: relative;
margin: $baseline 0;
padding: $baseline;
border: 1px solid $forum-color-border !important;
border-radius: $forum-border-radius;
header {
......@@ -319,18 +306,13 @@ body.discussion {
.discussion-module-header {
@include float(left);
width: flex-grid(7);
margin-bottom: ($baseline * 0.75);
}
.add_post_btn_container {
@include text-align(right);
position: relative;
top: -45px;
}
.discussion {
&.inline-discussion {
padding-top: $baseline * 3;
}
width: flex-grid(12);
height: (2 * $baseline);
}
div.add-response.post-extended-content {
......@@ -362,7 +344,6 @@ section.discussion {
}
.new-post-article {
display: none;
.inner-wrapper {
max-width: 1180px;
......@@ -377,6 +358,7 @@ section.discussion {
color: $gray-d3;
font-weight: 700;
}
}
.edit-post-form {
......@@ -405,6 +387,9 @@ section.discussion {
.xblock-student_view-discussion {
@extend %ui-print-excluded;
// Overrides overspecific courseware CSS from:
// https://github.com/edx/edx-platform/blob/master/lms/static/sass/course/courseware/_courseware.scss#L499
padding-top: 15px !important;
}
// ====================
......@@ -441,6 +426,8 @@ section.discussion-pagination {
.response-count {
@include float(right);
color: $forum-color-response-count;
font-size: $forum-base-font-size;
}
.response-pagination {
......
// Layouts for discussion pages
@import '../course/base/extends';
.user-profile {
background-color: $sidebar-color;
.discussion-user-profile-board {
.user-profile {
padding: $baseline;
min-height: 500px;
.discussion-profile-title {
margin-bottom: $baseline / 5;
font-size: $forum-x-large-font-size;
}
.sidebar-username {
font-weight: 700;
font-size: $forum-large-font-size;
.discussion-profile-count {
margin-top: $baseline / 4;
}
.sidebar-user-roles {
margin-top: $baseline/2;
font-style: italic;
font-size: $forum-base-font-size;
.discussion-profile-info {
@include margin-right($baseline);
}
.sidebar-threads-count {
margin-top: $baseline/2;
.user-name {
@include margin-right($baseline);
font-size: $forum-x-large-font-size;
}
.sidebar-threads-count span,
.sidebar-comments-count span {
font-weight: 700;
.user-roles {
font-size: $forum-small-font-size;
font-style: italic;
}
}
......
......@@ -43,16 +43,16 @@
@mixin discussion-wmd-preview-container {
@include border-radius(0, 0, $forum-border-radius, $forum-border-radius);
box-sizing: border-box;
border: 1px solid $gray-l1;
border: 1px solid $forum-color-border;
border-top: none;
width: 100%;
background: $gray-l4;
background: $forum-color-background-light;
box-shadow: 0 1px 3px $shadow-l1 inset;
}
@mixin discussion-new-post-wmd-preview-container {
@include discussion-wmd-preview-container;
border-color: $gray-d3;
border-color: $forum-color-border;
box-shadow: 0 1px 3px $shadow-d1 inset;
}
......@@ -67,7 +67,7 @@
@mixin discussion-wmd-preview {
padding: ($baseline/2) $baseline;
width: auto;
color: $gray-d3;
background-color: $forum-color-background-light;
ol, ul { // Fix up the RTL-only _reset.scss, but only in specific places
@include padding-left($baseline*2);
......
......@@ -51,20 +51,6 @@
width: 100%;
}
.wmd-input {
@include border-radius($forum-border-radius, $forum-border-radius, 0, 0);
width: 100%;
height: 150px;
font-style: normal;
font-size: $forum-base-font-size;
font-family: Monaco, 'Lucida Console', monospace;
line-height: 1.6em;
&::-webkit-input-placeholder {
color: #888;
}
}
.wmd-button-row {
@include transition(all .2s ease-out 0s);
position: relative;
......
// discussion - elements - labels
// ====================
body.discussion, .discussion-module {
.discussion {
.post-label {
@include margin($baseline/4, $baseline/2, 0, 0);
@extend %t-weight4;
@extend %t-light;
font-size: $forum-small-font-size;
display: inline;
white-space: nowrap;
......
......@@ -122,10 +122,10 @@
// -------------------
// Sort and filter bar
// -------------------
.forum-nav-refine-bar {
@include clearfix();
@include border-radius($forum-border-radius, $forum-border-radius, 0, 0);
@include text-align(right);
font-size: $forum-small-font-size;
border-bottom: 1px solid $forum-color-border;
background-color: $gray-l5;
......@@ -134,16 +134,18 @@
}
.forum-nav-filter-main {
@include text-align(left);
@include float(left);
box-sizing: border-box;
display: inline-block;
width: 50%;
@include text-align(left);
}
.forum-nav-filter-cohort, .forum-nav-sort {
@include text-align(right);
@include float(right);
box-sizing: border-box;
display: inline-block;
@include text-align(right);
@media (min-width: $bp-screen-md) {
width: 50%;
......@@ -173,14 +175,19 @@
// Thread list
// -----------
.forum-nav-thread-list {
@include padding-left(0);
padding-left: 0 !important; // should *not* be RTLed, see below for explanation
min-height: 60px; // @TODO: Remove this when we have a real empty state for the discussion thread list
margin: 0;
overflow-y: scroll;
overflow-y: auto;
list-style: none;
border-radius: 0 0 3px 3px;
.forum-nav-thread-labels {
margin: 5px 0 0;
// Overrides overspecific courseware CSS from:
// https://github.com/edx/edx-platform/blob/master/lms/static/sass/course/courseware/_courseware.scss#L470
// note this should *not* be RTLed, as the rule it overrides is not RTLed
padding-left: 0 !important;
}
.thread-preview-body {
......@@ -190,6 +197,22 @@
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
@include rtl {
// This is counterintuitive, but when showing a preview of the first part of RTL text, using direction: rtl
// will actually show the _last_ part of that text.
direction: ltr;
}
}
}
// Overrides underspecific styles from courseware css
.course-wrapper .course-content .forum-nav-thread-list-wrapper .forum-nav-thread-list {
@include padding-left(0);
list-style: none;
.forum-nav-thread {
margin: 0;
}
}
......@@ -223,33 +246,7 @@
}
&.is-active {
color: $forum-color-background;
background-color: $forum-color-reading-thread;
.forum-nav-thread-labels > li {
border-color: $forum-color-background;
color: $forum-color-background;
}
.forum-nav-thread-votes-count {
color: $forum-color-background;
}
.forum-nav-thread-comments-count {
color: $base-font-color;
&:after {
@include border-right-color($forum-color-border);
}
}
span.icon {
color: $forum-color-background;
}
.thread-preview-body {
color: $forum-color-background;
}
}
.forum-nav-thread-unread-comments-count {
......@@ -275,6 +272,7 @@
.forum-nav-thread-wrapper-0 {
@extend %forum-nav-thread-wrapper;
@include margin-right($baseline/5);
align-self: flex-start;
.icon {
font-size: $forum-base-font-size;
......@@ -293,13 +291,16 @@
.forum-nav-thread-wrapper-1 {
@extend %forum-nav-thread-wrapper;
margin: 0 ($baseline / 4);
width: 80%;
// 125 is the width we need to save for the "X new" comments indicator - and we want to clip the preview
// at the same length whether there are unread comments for this story or not.
max-width: calc(100% - 125px);
flex-grow: 1; // This column should consume all the available space
}
.forum-nav-thread-wrapper-2 {
@extend %forum-nav-thread-wrapper;
@include text-align(right);
min-width: 90px;
white-space: nowrap;
}
......@@ -370,7 +371,9 @@
&:hover,
&:focus {
color: $forum-color-active-text;
background-color: $forum-color-active-thread;
// !important overrides the one set here:
// https://github.com/edx/edx-platform/blob/master/lms/static/sass/elements/_controls.scss#L472
background-color: $forum-color-active-thread !important;
}
}
......
......@@ -9,3 +9,4 @@
// app - discussion
@import 'build-discussion';
@import 'views/inline';
......@@ -9,3 +9,4 @@
// app - discussion
@import 'build-discussion';
@import 'views/inline';
......@@ -17,66 +17,64 @@
// provisional styling for "search alerts" (messages boxes that appear in the sidebar below the search
// input field with notices pertaining to the search result).
// --------------------
body.discussion {
.forum-nav {
.forum-nav {
// wrapper for multiple alerts
.search-alerts {
// wrapper for multiple alerts
.search-alerts {
}
// a single alert, which can be independently displayed / dismissed
.search-alert {
@include transition(none);
padding: ($baseline/2) 11px ($baseline/2) 18px;
background-color: $black;
}
}
.search-alert-content, .search-alert-controls {
display: inline-block;
vertical-align: middle;
}
// a single alert, which can be independently displayed / dismissed
.search-alert {
@include transition(none);
padding: ($baseline/2) 11px ($baseline/2) 18px;
background-color: $black;
}
// alert content
.search-alert-content {
width: 70%;
.search-alert-content, .search-alert-controls {
display: inline-block;
vertical-align: middle;
}
// alert copy
.message {
font-size: $forum-small-font-size;
color: $white;
// alert content
.search-alert-content {
width: 70%;
em {
@extend %t-weight5;
font-style: italic;
}
}
// alert copy
.message {
font-size: $forum-small-font-size;
color: $white;
// links to jump to users/content in alerts
.link-jump {
@include transition(none);
em {
@extend %t-weight5;
font-style: italic;
}
}
// alert controls
.search-alert-controls {
@include text-align(right);
width: 28%;
// links to jump to users/content in alerts
.link-jump {
@include transition(none);
@extend %t-weight5;
}
}
.control {
@include transition(none);
@extend %t-weight5;
padding: ($baseline/4) ($baseline/2);
color: $white;
font-size: $forum-base-font-size;
// alert controls
.search-alert-controls {
@include text-align(right);
width: 28%;
.control {
@include transition(none);
@extend %t-weight5;
padding: ($baseline/4) ($baseline/2);
color: $white;
font-size: $forum-base-font-size;
// reseting poorly globally scoped hover/focus state for this control
&:hover, &:focus {
color: $white;
text-decoration: none;
}
// reseting poorly globally scoped hover/focus state for this control
&:hover, &:focus {
color: $white;
text-decoration: none;
}
}
}
......
......@@ -118,7 +118,34 @@ li[class*=forum-nav-thread-label-] {
// -------
.discussion-module {
.wrapper-post-header .post-title {
margin-bottom: 0 !important; // overrides "#seq_content h1" styling
.post-header {
margin-bottom: 0 !important; // overrides default header styling
padding-bottom: 0 !important; // overrides default header styling
.posted-details {
margin: ($baseline/5) 0 !important; // overrides courseware p styling
}
.post-labels {
font-size: $forum-base-font-size; // overrides default heading styling
}
.post-title {
margin-bottom: 0 !important; // overrides "#seq_content h1" styling
}
}
}
// overrides courseware styling to keep views consistent everywhere
.discussion-article {
.response-header {
line-height: 1 !important;
font-size: $forum-base-font-size !important;
margin-bottom: 0 !important;
padding-bottom: 0 !important;
}
p {
margin-bottom: 0 !important;
}
}
// discussion - utilities - variables
// ====================
// color variables
// base color variables
$forum-color-primary: rgb(0, 117, 180) !default;
$forum-color-copy-light: rgb(65, 65, 65) !default;
$forum-color-background-light: rgb(245, 245, 245) !default;
// contextual color variables
$forum-color-background: $white;
$forum-color-active-thread: $blue !default;
$forum-color-hover: $action-primary-bg !default;
$forum-color-active-thread: $forum-color-primary !default;
$forum-color-hover: rgb(6, 86, 131) !default;
$forum-color-active-text: $white !default;
$forum-color-pinned: $pink !default;
$forum-color-reported: $pink !default;
$forum-color-pinned: rgb(152, 44, 98) !default;
$forum-color-reported: rgb(152, 44, 98) !default;
$forum-color-closed: $black !default;
$forum-color-following: $blue !default;
$forum-color-staff: $blue !default;
$forum-color-community-ta: $green-d1 !default;
$forum-color-marked-answer: $green-d1 !default;
$forum-color-border: $gray-l3 !default;
$forum-color-error: $red !default;
$forum-color-hover-thread: #f6f6f6 !default;
$forum-color-reading-thread: $gray-d3 !default;
$forum-color-read-post: $blue !default;
$forum-color-never-read-post: $gray-d3 !default;
$forum-color-editor-preview-label: $gray-d2 !default;
$forum-color-response-count: $gray-d2 !default;
$forum-color-following: $forum-color-primary !default;
$forum-color-staff: $forum-color-primary !default;
$forum-color-community-ta: rgb(0, 129, 0) !default;
$forum-color-marked-answer: rgb(0, 129, 0) !default;
$forum-color-border: rgb(217, 217, 217) !default;
$forum-color-error: rgb(203, 7, 18) !default;
$forum-color-hover-thread: $forum-color-background-light !default;
$forum-color-reading-thread: $forum-color-background-light !default;
$forum-color-read-post: $forum-color-copy-light !default;
$forum-color-never-read-post: $forum-color-primary !default;
$forum-color-editor-preview-label: $forum-color-copy-light !default;
$forum-color-response-count: $forum-color-copy-light !default;
$forum-color-navigation-bar: $forum-color-background-light !default;
$forum-color-count: $forum-color-copy-light !default;
$forum-color-background-label: rgb(65, 65, 65) !default;
// post images
$post-image-dimension: ($baseline*3) !default; // image size + margin
......@@ -37,5 +45,5 @@ $forum-small-font-size: 12px;
$forum-border-radius: 3px;
// btn colors
$uxpl-primary-blue: $blue !default;
$uxpl-primary-blue: rgb(0, 117, 180) !default;
$btn-default-background-color: $white;
// discussion - utilities - variables
// ====================
// color variables
// base color variables
$forum-color-primary: palette(primary, base) !default;
$forum-color-copy-light: palette(grayscale, base) !default;
$forum-color-background-light: palette(grayscale, x-back) !default;
// contextual color variables
$forum-color-background: $lms-container-background-color !default;
$forum-color-active-thread: $lms-active-color !default;
$forum-color-hover: palette(primary, dark) !default;
......@@ -9,18 +14,21 @@ $forum-color-active-text: $lms-container-background-color !default;
$forum-color-pinned: palette(secondary, dark) !default;
$forum-color-reported: palette(secondary, dark) !default;
$forum-color-closed: $black !default;
$forum-color-following: palette(primary, base) !default;
$forum-color-staff: palette(primary, base) !default;
$forum-color-following: $forum-color-primary !default;
$forum-color-staff: $forum-color-primary !default;
$forum-color-community-ta: palette(success, text) !default;
$forum-color-marked-answer: palette(success, text) !default;
$forum-color-border: palette(grayscale, back) !default;
$forum-color-error: palette(error, accent) !default;
$forum-color-hover-thread: palette(grayscale, x-back) !default;
$forum-color-reading-thread: palette(primary, base) !default;
$forum-color-hover-thread: $forum-color-background-light !default;
$forum-color-reading-thread: $forum-color-background-light !default;
$forum-color-read-post: palette(grayscale, base) !default;
$forum-color-never-read-post: palette(primary, base) !default;
$forum-color-never-read-post: $forum-color-primary !default;
$forum-color-editor-preview-label: palette(grayscale, base) !default;
$forum-color-response-count: palette(grayscale, base) !default;
$forum-color-navigation-bar: $forum-color-background-light !default;
$forum-color-count: palette(grayscale, base) !default;
$forum-color-background-label: palette(grayscale, base) !default;
// post images
$post-image-dimension: ($baseline*3) !default; // image size + margin
......
// forums - inline discussion styling
// ====================
.discussion.inline-discussion {
.inline-threads {
border: 1px solid $forum-color-border;
border-radius: $forum-border-radius;
}
.inline-thread {
border: 1px solid $forum-color-border;
border-radius: $forum-border-radius;
.forum-nav-bar {
color: $forum-color-navigation-bar;
padding: ($baseline / 2) $baseline;
position: relative;
.all-posts-btn {
color: $forum-color-primary;
.icon {
@include margin-left(-15px);
}
}
}
.forum-content {
padding: $baseline / 2;
overflow-y: auto;
}
}
.wmd-preview-container {
@include discussion-new-post-wmd-preview-container;
margin-bottom: $baseline;
}
.wmd-preview-label {
@include discussion-wmd-preview-label;
}
.wmd-preview {
@include discussion-wmd-preview;
}
.new-post-article {
position: relative;
border: 1px solid $forum-color-border;
.add-post-cancel {
@include right($baseline / 2);
top: $baseline / 2;
position: absolute;
color: $uxpl-primary-blue;
&:hover,
&:focus {
border-color: transparent;
box-shadow: none;
background-color: transparent;
background-image: none;
}
}
}
}
......@@ -42,8 +42,7 @@
}
// +base - single response element
.container .discussion-response {
.discussion-response {
.response-header-content {
// CASE: larger username for responses
......
......@@ -13,39 +13,34 @@
.discussion-post {
padding: 0 ($baseline/2);
.wrapper-post-header {
padding-bottom: 0;
}
.post-header-content {
display: inline-block;
width: flex-grid(9,12);
}
.post-header-actions {
@include float(right);
}
.post-body {
width: flex-grid(10,12);
}
}
.posted-details {
@extend %t-copy-sub2;
margin: ($baseline/5) 0;
color: $gray-d1;
// post article
.discussion-article {
.posted-details {
@extend %t-copy-sub2;
@extend %t-light;
margin: ($baseline/5) 0;
color: $forum-color-copy-light;
.username {
@extend %t-strong;
display: inline;
}
.username {
@extend %t-strong;
display: inline;
}
.timeago, .top-post-status {
color: inherit;
.timeago, .top-post-status {
color: inherit;
}
}
}
.thread-responses-wrapper {
padding: 0 ($baseline/2);
}
// response layout
.discussion-response {
min-height: ($baseline*5);
......@@ -62,6 +57,11 @@
position: absolute;
top: $baseline;
}
// response body
.response-body {
@extend %t-copy-sub1;
}
}
// comments layout
......@@ -74,7 +74,7 @@
width: flex-grid(10,12);
p + p {
margin-top: 12px;
margin-top: ($baseline/2);
}
}
......@@ -94,58 +94,56 @@
}
// +thread - elements - shared styles
body.discussion {
.discussion-post, .discussion-response, .discussion-comment {
@include clearfix();
.discussion-post, .discussion-response, .discussion-comment {
@include clearfix();
// thread - images
.author-image {
@include margin-right($baseline/2);
display: inline-block;
vertical-align: top;
// thread - images
.author-image {
@include margin-right($baseline/2);
display: inline-block;
vertical-align: top;
// STATE: No profile image
&:empty {
display: none;
}
// STATE: No profile image
&:empty {
display: none;
}
// CASE: post image
&.level-post {
height: $post-image-dimension;
width: $post-image-dimension;
}
// CASE: post image
&.level-post {
height: $post-image-dimension;
width: $post-image-dimension;
}
// CASE: response image
&.level-response {
height: $response-image-dimension;
width: $response-image-dimension;
}
// CASE: response image
&.level-response {
height: $response-image-dimension;
width: $response-image-dimension;
}
// CASE: comment image
&.level-comment {
height: $comment-image-dimension;
width: $comment-image-dimension;
}
// CASE: comment image
&.level-comment {
height: $comment-image-dimension;
width: $comment-image-dimension;
}
img {
border-radius: $forum-border-radius;
}
img {
border-radius: $forum-border-radius;
}
}
}
.discussion-response .response-body {
@include padding-right($baseline); //ensures content doesn't overlap on post or response actions.
}
.discussion-response .response-body {
@include padding(($baseline/2), $baseline, 0, 0); //ensures content doesn't overlap on post or response actions.
margin-bottom: 0.2em;
font-size: $forum-base-font-size;
}
// +post - individual element styling
// NOTE: discussion-article is used for inline discussion modules.
.discussion-post,
.discussion-article {
.discussion-post {
@include clearfix();
.post-header-content {
max-width: calc(100% - 100px);
// post title
.post-title {
......@@ -157,24 +155,19 @@ body.discussion {
// post body
.post-body {
@extend %t-copy-sub1;
// clear: both; //TO-DO: confirm that removing this is ok for all cases of discussion posts.
padding: ($baseline/2) 0;
}
// post context
.post-context {
@extend %t-copy-sub2;
margin-top: $baseline;
color: $gray-d1;
@extend %t-light;
color: $forum-color-copy-light;
// CASE: no courseware context or cohort visibility rules
&:empty {
display: none;
}
// post visibility - cohorts
.group-visibility-label {
margin-top: ($baseline/4);
}
}
}
......@@ -188,10 +181,6 @@ body.discussion {
margin-bottom: 0;
}
.thread-main-wrapper, .thread-responses-wrapper {
padding: $baseline;
}
.discussion-article {
@include transition(all .2s linear 0s);
border: 1px solid $forum-color-border;
......@@ -239,11 +228,6 @@ body.discussion {
font-size: $forum-large-font-size;
}
}
.response-body {
margin-bottom: 0.2em;
font-size: $forum-base-font-size;
}
}
.discussion-reply-new {
......@@ -285,13 +269,6 @@ body.discussion {
}
}
// Custom styling for the list of user threads
.discussion-user-threads {
.discussion-post {
padding: $baseline/2;
}
}
.thread-wrapper,
.forum-new-post-form {
img {
......
<%page expression_filter="h"/>
<%include file="_underscore_templates.html" />
<%include file="_thread_list_template.html" />
<%!
from django.utils.translation import ugettext as _
......@@ -16,18 +17,15 @@ from openedx.core.djangolib.js_utils import js_escaped_string
<h3 class="discussion-module-title">${_(display_name)}</h3>
<div class="inline-discussion-topic"><span class="inline-discussion-topic-title">${_("Topic:")}</span> ${discussion_category} / ${discussion_target}</div>
</div>
<button class="discussion-show btn btn-brand" data-discussion-id="${discussion_id}">
<button class="discussion-show btn" data-discussion-id="${discussion_id}">
<span class="button-text">${_("Show Discussion")}</span>
</button>
</div>
<script type="text/javascript">
/* global DiscussionModuleView */
/* exported DiscussionInlineBlock, $$course_id */
var $$course_id = "${course_id | n, js_escaped_string}";
function DiscussionInlineBlock(runtime, element) {
'use strict';
var el = $(element).find('.discussion-module');
new DiscussionModuleView({ el: el });
new DiscussionInlineView({ el: $(element).find('.discussion-module') });
}
</script>
......@@ -17,7 +17,7 @@ from openedx.core.djangolib.js_utils import js_escaped_string
discussion_classes = [
['Discussion', 'common/js/discussion/discussion'],
['Content', 'common/js/discussion/content'],
['DiscussionModuleView', 'common/js/discussion/discussion_module_view'],
['DiscussionInlineView', 'common/js/discussion/views/discussion_inline_view'],
['DiscussionThreadView', 'common/js/discussion/views/discussion_thread_view'],
['DiscussionThreadListView', 'common/js/discussion/views/discussion_thread_list_view'],
['DiscussionThreadProfileView', 'common/js/discussion/views/discussion_thread_profile_view'],
......
<%page expression_filter="h"/>
<%! from django.utils.translation import ugettext as _ %>
<script type="text/template" id="thread-list-template">
<div class="forum-nav-thread-list-wrapper" id="sort-filter-wrapper" tabindex="-1" style="display:none">
<div class="forum-nav-thread-list-wrapper" id="sort-filter-wrapper" tabindex="-1">
<div class="forum-nav-refine-bar">
<label class="forum-nav-filter-main">
## Translators: This labels a filter menu in forum navigation
......@@ -26,7 +26,8 @@
<span class="sr">${_("Cohort:")}</span>
<select class="forum-nav-filter-cohort-control">
<option value="">${_("in all cohorts")}</option>
%for c in cohorts:
## cohorts is not iterable sometimes because inline discussions xmodule doesn't pass it
%for c in (cohorts or []):
<option value="${c['id']}">${c['name']}</option>
%endfor
</select>
......
......@@ -56,7 +56,7 @@
id="search"
placeholder="Search all the things"
/>
<button class="btn-brand btn-small search-btn" type="button">Search</button>
<button class="btn btn-small search-btn" type="button">Search</button>
</form>
</div>
</div>
......
......@@ -6,7 +6,7 @@
"backbone.paginator": "~2.0.3",
"backbone-validation": "~0.11.5",
"coffee-script": "1.6.1",
"edx-pattern-library": "0.17.1",
"edx-pattern-library": "0.18.0",
"edx-ui-toolkit": "1.5.0",
"jquery": "~2.2.0",
"jquery-migrate": "^1.4.1",
......
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