Commit 4516f9e9 by Greg Price

Merge pull request #2733 from edx/gprice/forum-pinning-ux

Improve UX of pinning feature
parents 256e13a0 29e77ed2
...@@ -16,7 +16,7 @@ describe "DiscussionContentView", -> ...@@ -16,7 +16,7 @@ describe "DiscussionContentView", ->
<div class="post-body"><p>Post body.</p></div> <div class="post-body"><p>Post body.</p></div>
<div data-tooltip="Report Misuse" data-role="thread-flag" class="discussion-flag-abuse notflagged"> <div data-tooltip="Report Misuse" data-role="thread-flag" class="discussion-flag-abuse notflagged">
<i class="icon"></i><span class="flag-label">Report Misuse</span></div> <i class="icon"></i><span class="flag-label">Report Misuse</span></div>
<div data-tooltip="pin this thread" data-role="thread-pin" class="admin-pin discussion-pin notpinned"> <div data-tooltip="pin this thread" class="admin-pin discussion-pin notpinned">
<i class="icon"></i><span class="pin-label">Pin Thread</span></div> <i class="icon"></i><span class="pin-label">Pin Thread</span></div>
</div> </div>
""" """
......
...@@ -6,14 +6,20 @@ describe "DiscussionThreadShowView", -> ...@@ -6,14 +6,20 @@ describe "DiscussionThreadShowView", ->
<a href="#" class="vote-btn" data-tooltip="vote" role="button" aria-pressed="false"> <a href="#" class="vote-btn" data-tooltip="vote" role="button" aria-pressed="false">
<span class="plus-icon"/><span class="votes-count-number">0</span> <span class="sr">votes (click to vote)</span> <span class="plus-icon"/><span class="votes-count-number">0</span> <span class="sr">votes (click to vote)</span>
</a> </a>
<div class="admin-pin discussion-pin notpinned" role="button" aria-pressed="false" tabindex="0">
<i class="icon icon-pushpin"></i>
<span class="pin-label">Pin Thread</span>
</div>
</div> </div>
""" """
) )
window.$$course_id = "TestOrg/TestCourse/TestRun"
window.user = new DiscussionUser({id: "567", upvoted_ids: []})
@threadData = { @threadData = {
id: "dummy", id: "dummy",
user_id: "567", user_id: user.id,
course_id: "TestOrg/TestCourse/TestRun", course_id: $$course_id,
body: "this is a thread", body: "this is a thread",
created_at: "2013-04-03T20:08:39Z", created_at: "2013-04-03T20:08:39Z",
abuse_flaggers: [], abuse_flaggers: [],
...@@ -22,7 +28,6 @@ describe "DiscussionThreadShowView", -> ...@@ -22,7 +28,6 @@ describe "DiscussionThreadShowView", ->
@thread = new Thread(@threadData) @thread = new Thread(@threadData)
@view = new DiscussionThreadShowView({ model: @thread }) @view = new DiscussionThreadShowView({ model: @thread })
@view.setElement($(".discussion-post")) @view.setElement($(".discussion-post"))
window.user = new DiscussionUser({id: "567", upvoted_ids: []})
it "renders the vote correctly", -> it "renders the vote correctly", ->
DiscussionViewSpecHelper.checkRenderVote(@view, @thread) DiscussionViewSpecHelper.checkRenderVote(@view, @thread)
...@@ -38,3 +43,48 @@ describe "DiscussionThreadShowView", -> ...@@ -38,3 +43,48 @@ describe "DiscussionThreadShowView", ->
it "vote button activates on appropriate events", -> it "vote button activates on appropriate events", ->
DiscussionViewSpecHelper.checkVoteButtonEvents(@view) DiscussionViewSpecHelper.checkVoteButtonEvents(@view)
describe "renderPinned", ->
describe "for an unpinned thread", ->
it "renders correctly when pinning is allowed", ->
@thread.updateInfo({ability: {can_openclose: true}})
@view.renderPinned()
pinElem = @view.$(".discussion-pin")
expect(pinElem.length).toEqual(1)
expect(pinElem).not.toHaveClass("pinned")
expect(pinElem).toHaveClass("notpinned")
expect(pinElem.find(".pin-label")).toHaveHtml("Pin Thread")
expect(pinElem).not.toHaveAttr("data-tooltip")
expect(pinElem).toHaveAttr("aria-pressed", "false")
# If pinning is not allowed, the pinning UI is not present, so no
# test is needed
describe "for a pinned thread", ->
beforeEach ->
@thread.set("pinned", true)
it "renders correctly when unpinning is allowed", ->
@thread.updateInfo({ability: {can_openclose: true}})
@view.renderPinned()
pinElem = @view.$(".discussion-pin")
expect(pinElem.length).toEqual(1)
expect(pinElem).toHaveClass("pinned")
expect(pinElem).not.toHaveClass("notpinned")
expect(pinElem.find(".pin-label")).toHaveHtml("Pinned<span class='sr'>, click to unpin</span>")
expect(pinElem).toHaveAttr("data-tooltip", "Click to unpin")
expect(pinElem).toHaveAttr("aria-pressed", "true")
it "renders correctly when unpinning is not allowed", ->
@view.renderPinned()
pinElem = @view.$(".discussion-pin")
expect(pinElem.length).toEqual(1)
expect(pinElem).toHaveClass("pinned")
expect(pinElem).not.toHaveClass("notpinned")
expect(pinElem.find(".pin-label")).toHaveHtml("Pinned")
expect(pinElem).not.toHaveAttr("data-tooltip")
expect(pinElem).not.toHaveAttr("aria-pressed")
it "pinning button activates on appropriate events", ->
DiscussionViewSpecHelper.checkButtonEvents(@view, "togglePin", ".admin-pin")
...@@ -97,15 +97,19 @@ class @DiscussionViewSpecHelper ...@@ -97,15 +97,19 @@ class @DiscussionViewSpecHelper
expect(view.unvote).toHaveBeenCalled() expect(view.unvote).toHaveBeenCalled()
expect(event.preventDefault.callCount).toEqual(2) expect(event.preventDefault.callCount).toEqual(2)
@checkVoteButtonEvents = (view) -> @checkButtonEvents = (view, viewFunc, buttonSelector) ->
spyOn(view, "toggleVote") spy = spyOn(view, viewFunc)
button = view.$el.find(".vote-btn") button = view.$el.find(buttonSelector)
button.click() button.click()
expect(view.toggleVote).toHaveBeenCalled() expect(spy).toHaveBeenCalled()
view.toggleVote.reset() spy.reset()
button.trigger($.Event("keydown", {which: 13})) button.trigger($.Event("keydown", {which: 13}))
expect(view.toggleVote).not.toHaveBeenCalled() expect(spy).not.toHaveBeenCalled()
view.toggleVote.reset() spy.reset()
button.trigger($.Event("keydown", {which: 32})) button.trigger($.Event("keydown", {which: 32}))
expect(view.toggleVote).toHaveBeenCalled() expect(spy).toHaveBeenCalled()
@checkVoteButtonEvents = (view) ->
@checkButtonEvents(view, "toggleVote", ".vote-btn")
...@@ -9,7 +9,10 @@ if Backbone? ...@@ -9,7 +9,10 @@ if Backbone?
"click .discussion-flag-abuse": "toggleFlagAbuse" "click .discussion-flag-abuse": "toggleFlagAbuse"
"keydown .discussion-flag-abuse": "keydown .discussion-flag-abuse":
(event) -> DiscussionUtil.activateOnSpace(event, @toggleFlagAbuse) (event) -> DiscussionUtil.activateOnSpace(event, @toggleFlagAbuse)
"click .admin-pin": "togglePin" "click .admin-pin":
(event) -> @togglePin(event)
"keydown .admin-pin":
(event) -> DiscussionUtil.activateOnSpace(event, @togglePin)
"click .action-follow": "toggleFollowing" "click .action-follow": "toggleFollowing"
"keydown .action-follow": "keydown .action-follow":
(event) -> DiscussionUtil.activateOnSpace(event, @toggleFollowing) (event) -> DiscussionUtil.activateOnSpace(event, @toggleFollowing)
...@@ -59,15 +62,36 @@ if Backbone? ...@@ -59,15 +62,36 @@ if Backbone?
@$(".discussion-flag-abuse .flag-label").html(gettext("Report Misuse")) @$(".discussion-flag-abuse .flag-label").html(gettext("Report Misuse"))
renderPinned: => renderPinned: =>
pinElem = @$(".discussion-pin")
pinLabelElem = pinElem.find(".pin-label")
if @model.get("pinned") if @model.get("pinned")
@$("[data-role=thread-pin]").addClass("pinned") pinElem.addClass("pinned")
@$("[data-role=thread-pin]").removeClass("notpinned") pinElem.removeClass("notpinned")
@$(".discussion-pin .pin-label").html(gettext("Pinned")) if @model.can("can_openclose")
###
Translators: The text between start_sr_span and end_span is not shown
in most browsers but will be read by screen readers.
###
pinLabelElem.html(
interpolate(
gettext("Pinned%(start_sr_span)s, click to unpin%(end_span)s"),
{"start_sr_span": "<span class='sr'>", "end_span": "</span>"},
true
)
)
pinElem.attr("data-tooltip", gettext("Click to unpin"))
pinElem.attr("aria-pressed", "true")
else
pinLabelElem.html(gettext("Pinned"))
pinElem.removeAttr("data-tooltip")
pinElem.removeAttr("aria-pressed")
else else
@$("[data-role=thread-pin]").removeClass("pinned") # If not pinned and not able to pin, pin is not shown
@$("[data-role=thread-pin]").addClass("notpinned") pinElem.removeClass("pinned")
@$(".discussion-pin .pin-label").html(gettext("Pin Thread")) pinElem.addClass("notpinned")
pinLabelElem.html(gettext("Pin Thread"))
pinElem.removeAttr("data-tooltip")
pinElem.attr("aria-pressed", "false")
updateModelDetails: => updateModelDetails: =>
@renderVote() @renderVote()
...@@ -85,14 +109,14 @@ if Backbone? ...@@ -85,14 +109,14 @@ if Backbone?
_delete: (event) -> _delete: (event) ->
@trigger "thread:_delete", event @trigger "thread:_delete", event
togglePin: (event) -> togglePin: (event) =>
event.preventDefault() event.preventDefault()
if @model.get('pinned') if @model.get('pinned')
@unPin() @unPin()
else else
@pin() @pin()
pin: -> pin: =>
url = @model.urlFor("pinThread") url = @model.urlFor("pinThread")
DiscussionUtil.safeAjax DiscussionUtil.safeAjax
$elem: @$(".discussion-pin") $elem: @$(".discussion-pin")
...@@ -102,9 +126,9 @@ if Backbone? ...@@ -102,9 +126,9 @@ if Backbone?
if textStatus == 'success' if textStatus == 'success'
@model.set('pinned', true) @model.set('pinned', true)
error: => error: =>
$('.admin-pin').text(gettext("Pinning is not currently available")) DiscussionUtil.discussionAlert("Sorry", "We had some trouble pinning this thread. Please try again.")
unPin: -> unPin: =>
url = @model.urlFor("unPinThread") url = @model.urlFor("unPinThread")
DiscussionUtil.safeAjax DiscussionUtil.safeAjax
$elem: @$(".discussion-pin") $elem: @$(".discussion-pin")
...@@ -113,7 +137,8 @@ if Backbone? ...@@ -113,7 +137,8 @@ if Backbone?
success: (response, textStatus) => success: (response, textStatus) =>
if textStatus == 'success' if textStatus == 'success'
@model.set('pinned', false) @model.set('pinned', false)
error: =>
DiscussionUtil.discussionAlert("Sorry", "We had some trouble unpinning this thread. Please try again.")
toggleClosed: (event) -> toggleClosed: (event) ->
$elem = $(event.target) $elem = $(event.target)
......
...@@ -2471,17 +2471,16 @@ body.discussion { ...@@ -2471,17 +2471,16 @@ body.discussion {
float:right; float:right;
padding-right: 5px; padding-right: 5px;
font-style: italic; font-style: italic;
cursor: pointer;
margin-right: $baseline/2; margin-right: $baseline/2;
opacity: 0.8; opacity: 0.8;
span { &.admin-pin {
cursor: pointer; cursor: pointer;
}
&:hover, &:focus { &:hover, &:focus {
@include transition(opacity .2s linear 0s); @include transition(opacity .2s linear 0s);
opacity: 1.0; opacity: 1.0;
}
} }
} }
...@@ -2520,8 +2519,6 @@ body.discussion { ...@@ -2520,8 +2519,6 @@ body.discussion {
.pinned span { .pinned span {
color: $pink; color: $pink;
font-style: italic; font-style: italic;
//cursor change is here since pins are read-only for inline discussions.
cursor: default;
} }
.notpinned span { .notpinned span {
......
...@@ -62,13 +62,13 @@ ...@@ -62,13 +62,13 @@
% if course and has_permission(user, 'openclose_thread', course.id): % if course and has_permission(user, 'openclose_thread', course.id):
<div class="admin-pin discussion-pin notpinned" data-role="thread-pin" data-tooltip="${_('pin this thread') | h}"> <div class="admin-pin discussion-pin notpinned" role="button" aria-pressed="false" tabindex="0">
<i class="icon icon-pushpin"></i><span class="pin-label">${_("Pin Thread")}</span></div> <i class="icon icon-pushpin"></i><span class="pin-label">${_("Pin Thread")}</span></div>
%else: %else:
${"<% if (pinned) { %>"} ${"<% if (pinned) { %>"}
<div class="discussion-pin notpinned" data-role="thread-pin" data-tooltip="${_('pin this thread') | h}"> <div class="discussion-pin notpinned">
<i class="icon icon-pushpin"></i><span class="pin-label">${_("Pin Thread")}</span></div> <i class="icon icon-pushpin"></i><span class="pin-label">${_("Pinned")}</span></div>
${"<% } %>"} ${"<% } %>"}
% endif % endif
......
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