Requested UI changes - borderless, all posts is no longer highlighted

Fix read only support for team discussions

Fix error messages to use "posts" not "threads"

More small UI tweaks, padding and alignment

Add a11y tests for all inline discussion states

Improve discussion error messages
This commit is contained in:
Brian Jacobel
2016-11-29 14:39:45 -05:00
parent 95af18430c
commit 187783beaf
19 changed files with 138 additions and 114 deletions

View File

@@ -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);
}

View File

@@ -1,4 +1,4 @@
/* globals DiscussionContentView, DiscussionUtil */
/* globals _, Backbone, DiscussionContentView, DiscussionUtil */
(function() {
'use strict';
var __hasProp = {}.hasOwnProperty,
@@ -148,22 +148,24 @@
return _results;
};
DiscussionContentView.prototype.makeWmdEditor = function(cls_identifier) {
DiscussionContentView.prototype.makeWmdEditor = function(classIdentifier) {
if (!this.$el.find('.wmd-panel').length) {
return DiscussionUtil.makeWmdEditor(this.$el, $.proxy(this.$, this), cls_identifier);
return DiscussionUtil.makeWmdEditor(this.$el, $.proxy(this.$, this), classIdentifier);
} else {
return null;
}
};
DiscussionContentView.prototype.getWmdEditor = function(cls_identifier) {
return DiscussionUtil.getWmdEditor(this.$el, $.proxy(this.$, this), cls_identifier);
DiscussionContentView.prototype.getWmdEditor = function(classIdentifier) {
return DiscussionUtil.getWmdEditor(this.$el, $.proxy(this.$, this), classIdentifier);
};
DiscussionContentView.prototype.getWmdContent = function(cls_identifier) {
return DiscussionUtil.getWmdContent(this.$el, $.proxy(this.$, this), cls_identifier);
DiscussionContentView.prototype.getWmdContent = function(classIdentifier) {
return DiscussionUtil.getWmdContent(this.$el, $.proxy(this.$, this), classIdentifier);
};
DiscussionContentView.prototype.setWmdContent = function(cls_identifier, text) {
return DiscussionUtil.setWmdContent(this.$el, $.proxy(this.$, this), cls_identifier, text);
DiscussionContentView.prototype.setWmdContent = function(classIdentifier, text) {
return DiscussionUtil.setWmdContent(this.$el, $.proxy(this.$, this), classIdentifier, text);
};
DiscussionContentView.prototype.initialize = function() {
@@ -171,7 +173,7 @@
this.model.bind('change', this.renderPartialAttrs, this);
return this.listenTo(this.model, 'change:endorsed', function() {
if (self.model instanceof Comment) {
return self.trigger('comment:endorse');
self.trigger('comment:endorse');
}
});
};
@@ -330,7 +332,7 @@
DiscussionContentShowView.prototype.handleSecondaryActionEscape = function(event) {
if (event.keyCode === 27) {
this.toggleSecondaryActions(event);
return this.$('.action-more').focus();
this.$('.action-more').focus();
}
};
@@ -338,23 +340,23 @@
var self = this;
return setTimeout(function() {
if (self.secondaryActionsExpanded && self.$('.actions-dropdown :focus').length === 0) {
return self.toggleSecondaryActions(event);
self.toggleSecondaryActions(event);
}
}, 10);
};
DiscussionContentShowView.prototype.toggleFollow = function(event) {
var is_subscribing, msg, url;
var isSubscribing, msg, url;
event.preventDefault();
is_subscribing = !this.model.get('subscribed');
url = this.model.urlFor(is_subscribing ? 'follow' : 'unfollow');
if (is_subscribing) {
msg = gettext('We had some trouble subscribing you to this thread. Please try again.');
isSubscribing = !this.model.get('subscribed');
url = this.model.urlFor(isSubscribing ? 'follow' : 'unfollow');
if (isSubscribing) {
msg = gettext('You could not be subscribed to this post. Refresh the page and try again.');
} else {
msg = gettext('We had some trouble unsubscribing you from this thread. Please try again.');
msg = gettext('You could not be unsubscribed from this post. Refresh the page and try again.');
}
return DiscussionUtil.updateWithUndo(this.model, {
'subscribed': is_subscribing
subscribed: isSubscribing
}, {
url: url,
type: 'POST',
@@ -363,30 +365,30 @@
};
DiscussionContentShowView.prototype.toggleEndorse = function(event) {
var beforeFunc, is_endorsing, msg, updates, url,
var isEndorsing, msg, updates, url,
self = this;
event.preventDefault();
is_endorsing = !this.model.get('endorsed');
isEndorsing = !this.model.get('endorsed');
url = this.model.urlFor('endorse');
updates = {
endorsed: is_endorsing,
endorsement: is_endorsing ? {
endorsed: isEndorsing,
endorsement: isEndorsing ? {
username: DiscussionUtil.getUser().get('username'),
user_id: DiscussionUtil.getUser().id,
time: new Date().toISOString()
} : null
};
if (this.model.get('thread').get('thread_type') === 'question') {
if (is_endorsing) {
msg = gettext('We had some trouble marking this response as an answer. Please try again.');
if (isEndorsing) {
msg = gettext('This response could not be marked as an answer. Refresh the page and try again.'); // eslint-disable-line max-len
} else {
msg = gettext('We had some trouble removing this response as an answer. Please try again.');
msg = gettext('This response could not be unmarked as an answer. Refresh the page and try again.'); // eslint-disable-line max-len
}
} else {
if (is_endorsing) {
msg = gettext('We had some trouble marking this response endorsed. Please try again.');
if (isEndorsing) {
msg = gettext('This response could not be marked as endorsed. Refresh the page and try again.');
} else {
msg = gettext('We had some trouble removing this endorsement. Please try again.');
msg = gettext('This response could not be unendorsed. Refresh the page and try again.');
}
}
return DiscussionUtil.updateWithUndo(
@@ -395,7 +397,7 @@
{
url: url,
type: 'POST',
data: {endorsed: is_endorsing},
data: {endorsed: isEndorsing},
$elem: $(event.currentTarget)
},
msg,
@@ -404,22 +406,22 @@
};
DiscussionContentShowView.prototype.toggleVote = function(event) {
var is_voting, updates, url, user,
var isVoting, updates, url, user,
self = this;
event.preventDefault();
user = DiscussionUtil.getUser();
is_voting = !user.voted(this.model);
url = this.model.urlFor(is_voting ? 'upvote' : 'unvote');
isVoting = !user.voted(this.model);
url = this.model.urlFor(isVoting ? 'upvote' : 'unvote');
updates = {
upvoted_ids: (is_voting ? _.union : _.difference)(user.get('upvoted_ids'), [this.model.id])
upvoted_ids: (isVoting ? _.union : _.difference)(user.get('upvoted_ids'), [this.model.id])
};
if (!$(event.target.closest('.actions-item')).hasClass('is-disabled')) {
return DiscussionUtil.updateWithUndo(user, updates, {
url: url,
type: 'POST',
$elem: $(event.currentTarget)
}, gettext('We had some trouble saving your vote. Please try again.')).done(function() {
if (is_voting) {
}, gettext('This vote could not be processed. Refresh the page and try again.')).done(function() {
if (isVoting) {
return self.model.vote();
} else {
return self.model.unvote();
@@ -429,17 +431,17 @@
};
DiscussionContentShowView.prototype.togglePin = function(event) {
var is_pinning, msg, url;
var isPinning, msg, url;
event.preventDefault();
is_pinning = !this.model.get('pinned');
url = this.model.urlFor(is_pinning ? 'pinThread' : 'unPinThread');
if (is_pinning) {
msg = gettext('We had some trouble pinning this thread. Please try again.');
isPinning = !this.model.get('pinned');
url = this.model.urlFor(isPinning ? 'pinThread' : 'unPinThread');
if (isPinning) {
msg = gettext('This post could not be pinned. Refresh the page and try again.');
} else {
msg = gettext('We had some trouble unpinning this thread. Please try again.');
msg = gettext('This post could not be unpinned. Refresh the page and try again.');
}
return DiscussionUtil.updateWithUndo(this.model, {
pinned: is_pinning
pinned: isPinning
}, {
url: url,
type: 'POST',
@@ -448,18 +450,18 @@
};
DiscussionContentShowView.prototype.toggleReport = function(event) {
var is_flagging, msg, updates, url;
var isFlagging, msg, updates, url;
event.preventDefault();
if (this.model.isFlagged()) {
is_flagging = false;
msg = gettext('We had some trouble removing your flag on this post. Please try again.');
isFlagging = false;
msg = gettext('This post could not be flagged for abuse. Refresh the page and try again.');
} else {
is_flagging = true;
msg = gettext('We had some trouble reporting this post. Please try again.');
isFlagging = true;
msg = gettext('This post could not be unflagged for abuse. Refresh the page and try again.');
}
url = this.model.urlFor(is_flagging ? 'flagAbuse' : 'unFlagAbuse');
url = this.model.urlFor(isFlagging ? 'flagAbuse' : 'unFlagAbuse');
updates = {
abuse_flaggers: (is_flagging ? _.union : _.difference)(
abuse_flaggers: (isFlagging ? _.union : _.difference)(
this.model.get('abuse_flaggers'), [DiscussionUtil.getUser().id]
)
};
@@ -471,16 +473,16 @@
};
DiscussionContentShowView.prototype.toggleClose = function(event) {
var is_closing, msg, updates;
var isClosing, msg, updates;
event.preventDefault();
is_closing = !this.model.get('closed');
if (is_closing) {
msg = gettext('We had some trouble closing this thread. Please try again.');
isClosing = !this.model.get('closed');
if (isClosing) {
msg = gettext('This post could not be closed. Refresh the page and try again.');
} else {
msg = gettext('We had some trouble reopening this thread. Please try again.');
msg = gettext('This post could not be reopened. Refresh the page and try again.');
}
updates = {
closed: is_closing
closed: isClosing
};
return DiscussionUtil.updateWithUndo(this.model, updates, {
url: this.model.urlFor('close'),

View File

@@ -26,6 +26,7 @@
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);
@@ -144,6 +145,7 @@
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');
},
@@ -179,8 +181,8 @@
this.loadDiscussions(this.$el, function() {
self.hideDiscussion();
DiscussionUtil.discussionAlert(
gettext('Sorry'),
gettext('We had some trouble loading the discussion. Please try again.')
gettext('Error'),
gettext('This discussion could not be loaded. Refresh the page to try again.')
);
});
}

View File

@@ -309,7 +309,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 to try again.')
);
};
return this.collection.retrieveAnotherPage(this.mode, options, {
@@ -478,7 +479,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 +516,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) {

View File

@@ -261,18 +261,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 to 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 to try again.')
);
}
}

View File

@@ -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 to try again.')
);
}
});

View File

@@ -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,

View File

@@ -550,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() {

View File

@@ -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

View File

@@ -33,7 +33,8 @@ from common.test.acceptance.fixtures.discussion import (
Response,
Comment,
SearchResult,
MultipleThreadFixture)
MultipleThreadFixture,
)
from common.test.acceptance.tests.discussion.helpers import BaseDiscussionMixin
from common.test.acceptance.tests.helpers import skip_if_browser
@@ -416,7 +417,7 @@ class DiscussionTabSingleThreadTest(BaseDiscussionTestCase, DiscussionResponsePa
self.assertFalse(self.thread_page.is_comment_deletable("comment1"))
class DiscussionTabMultipleThreadTest(BaseDiscussionTestCase):
class DiscussionTabMultipleThreadTest(BaseDiscussionTestCase, BaseDiscussionMixin):
"""
Tests for the discussion page with multiple threads
"""
@@ -441,19 +442,6 @@ class DiscussionTabMultipleThreadTest(BaseDiscussionTestCase):
)
self.thread_page_1.visit()
@attr(shard=2)
def setup_multiple_threads(self, thread_count):
threads = []
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),
)
self.thread_ids.append(thread_id)
view = MultipleThreadFixture(threads)
view.push()
@attr('a11y')
def test_page_accessibility(self):
self.thread_page_1.a11y_audit.config.set_rules({
@@ -1039,7 +1027,9 @@ class InlineDiscussionTest(UniqueCourseTest, DiscussionResponsePaginationTestMix
"""
Tests Inline Discussion for accessibility issues.
"""
self.setup_multiple_inline_threads(thread_count=3)
self.setup_multiple_threads(thread_count=3)
# First test the a11y of the expanded list of threads
self.discussion_page.expand_discussion()
self.discussion_page.a11y_audit.config.set_rules({
'ignore': [
@@ -1048,6 +1038,14 @@ class InlineDiscussionTest(UniqueCourseTest, DiscussionResponsePaginationTestMix
})
self.discussion_page.a11y_audit.check_for_accessibility_errors()
# Now show the first thread and test the a11y again
self.discussion_page.show_thread(self.thread_ids[0])
self.discussion_page.a11y_audit.check_for_accessibility_errors()
# Finally show the new post form and test its a11y
self.discussion_page.click_new_post_button()
self.discussion_page.a11y_audit.check_for_accessibility_errors()
def test_add_a_post_is_present_if_can_create_thread_when_expanded(self):
self.discussion_page.expand_discussion()
# Add a Post link is present
@@ -1363,7 +1361,7 @@ class DiscussionSearchAlertTest(UniqueCourseTest):
def test_no_rewrite(self):
self.setup_corrected_text(None)
self.page.perform_search()
self.check_search_alert_messages(["no threads"])
self.check_search_alert_messages(["no posts"])
@attr(shard=2)
def test_rewrite_dismiss(self):
@@ -1385,7 +1383,7 @@ class DiscussionSearchAlertTest(UniqueCourseTest):
self.setup_corrected_text(None)
self.page.perform_search()
self.check_search_alert_messages(["no threads"])
self.check_search_alert_messages(["no posts"])
@attr(shard=2)
def test_rewrite_and_user(self):
@@ -1397,7 +1395,7 @@ class DiscussionSearchAlertTest(UniqueCourseTest):
def test_user_only(self):
self.setup_corrected_text(None)
self.page.perform_search(self.SEARCHED_USERNAME)
self.check_search_alert_messages(["no threads", self.SEARCHED_USERNAME])
self.check_search_alert_messages(["no posts", self.SEARCHED_USERNAME])
# make sure clicking the link leads to the user profile page
UserProfileViewFixture([]).push()
self.page.get_search_alert_links().first.click()

View File

@@ -6,14 +6,16 @@
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 discussionInlineView = new DiscussionInlineView({
el: this.$el,
showByDefault: true
showByDefault: true,
readOnly: this.readOnly
});
discussionInlineView.render();
return this;

View File

@@ -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();

View File

@@ -54,7 +54,6 @@
.discussion-module {
.discussion {
clear: both;
padding-top: ($baseline/2);
}
.btn {

View File

@@ -296,9 +296,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,6 +316,7 @@ body.discussion {
.discussion-module-header {
@include float(left);
width: flex-grid(7);
margin-bottom: ($baseline * 0.75);
}
.add_post_btn_container {
@@ -399,6 +397,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;
}
// ====================

View File

@@ -126,7 +126,6 @@
.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;
@@ -135,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%;
@@ -174,7 +175,7 @@
// Thread list
// -----------
.forum-nav-thread-list {
@include padding-left(0);
padding-left: 0 !important; // should *not* be RTLed, see below for explanation
margin: 0;
overflow-y: scroll;
list-style: none;
@@ -182,6 +183,10 @@
.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 {
@@ -216,6 +221,9 @@
.forum-nav-thread-link {
@include border-left(3px solid transparent);
@include rtl {
flex-direction: row-reverse;
}
display: flex;
padding: $baseline / 2;
transition: none;
@@ -290,6 +298,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;

View File

@@ -24,7 +24,7 @@ $forum-color-read-post: $forum-color-primary !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-navigation-bar: $forum-color-primary !default;
$forum-color-navigation-bar: #f6f6f6 !default;
// post images
$post-image-dimension: ($baseline*3) !default; // image size + margin

View File

@@ -24,7 +24,7 @@ $forum-color-read-post: palette(grayscale, 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-primary !default;
$forum-color-navigation-bar: palette(grayscale, x-back) !default;
// post images
$post-image-dimension: ($baseline*3) !default; // image size + margin

View File

@@ -2,8 +2,6 @@
// ====================
.discussion.inline-discussion {
padding-top: $baseline;
.inline-threads {
border: 1px solid $forum-color-border;
border-radius: $forum-border-radius;
@@ -14,10 +12,18 @@
border-radius: $forum-border-radius;
.forum-nav-bar {
background-color: $forum-color-navigation-bar;
color: $forum-color-navigation-bar;
padding: ($baseline / 2) $baseline;
position: relative;
.btn-link {
color: $forum-color-active-text;
.all-posts-btn {
color: $forum-color-primary;
.icon {
position: absolute;
@include left(7px);
top: 17px;
}
}
}

View File

@@ -27,8 +27,13 @@
}
.post-body {
@include float(left);
width: flex-grid(10,12);
}
.post-context {
@include float(left);
}
}
.posted-details {