Commit c3dc40d7 by Greg Price

Add UI for editing forum third-level content

parent 3f48e224
......@@ -10,6 +10,8 @@ describe 'ResponseCommentShowView', ->
<i class="icon"></i><span class="flag-label"></span></div>
<div style="display:none" class="discussion-delete-comment action-delete" data-role="comment-delete" data-tooltip="Delete Comment" role="button" aria-pressed="false" tabindex="0">
<i class="icon icon-remove"></i><span class="sr delete-label">Delete Comment</span></div>
<div style="display:none" class="discussion-edit-comment action-edit" data-tooltip="Edit Comment" role="button" tabindex="0">
<i class="icon icon-pencil"></i><span class="sr">Edit Comment</span></div>
<p class="posted-details">&ndash;posted <span class="timeago" title="<%- created_at %>"><%- created_at %></span> by
<% if (obj.username) { %>
<a href="<%- user_url %>" class="profile-link"><%- username %></a>
......@@ -70,4 +72,14 @@ describe 'ResponseCommentShowView', ->
@view.bind "comment:_delete", triggerTarget
@view.render()
@view.$el.find('.action-delete').click()
describe 'comment edit', ->
it 'triggers comment:edit when the edit button is clicked', ->
DiscussionUtil.loadRoles []
@comment.updateInfo {ability: {'can_edit': true}}
triggerTarget = jasmine.createSpy()
@view.bind "comment:edit", triggerTarget
@view.render()
@view.$el.find(".action-edit").click()
expect(triggerTarget).toHaveBeenCalled()
describe 'ResponseCommentView', ->
beforeEach ->
window.$$course_id = 'edX/999/test'
window.user = new DiscussionUser {id: '567'}
DiscussionUtil.loadRoles []
@comment = new Comment {
id: '01234567',
user_id: '567',
course_id: 'edX/999/test',
user_id: user.id,
course_id: $$course_id,
body: 'this is a response',
created_at: '2013-04-03T20:08:39Z',
abuse_flaggers: ['123']
roles: ['Student']
}
@view = new ResponseCommentView({ model: @comment })
spyOn(@view, "render")
setFixtures """
<script id="response-comment-show-template" type="text/template">
<div id="response-comment-show-div"/>
</script>
<script id="response-comment-edit-template" type="text/template">
<div id="response-comment-edit-div">
<div class="edit-comment-body"><textarea/></div>
<ul class="edit-comment-form-errors"/>
</div>
</script>
<div id="response-comment-fixture"/>
"""
@view = new ResponseCommentView({ model: @comment, el: $("#response-comment-fixture") })
spyOn(ResponseCommentShowView.prototype, "convertMath")
spyOn(DiscussionUtil, "makeWmdEditor")
@view.render()
makeEventSpy = () -> jasmine.createSpyObj('event', ['preventDefault', 'target'])
describe '_delete', ->
beforeEach ->
@comment.updateInfo {ability: {can_delete: true}}
@event = jasmine.createSpyObj('event', ['preventDefault', 'target'])
@event = makeEventSpy()
spyOn(@comment, "remove")
spyOn(@view.$el, "remove")
......@@ -68,3 +86,82 @@ describe 'ResponseCommentView', ->
expect(@comment.remove).not.toHaveBeenCalled()
expect(@view.$el.remove).not.toHaveBeenCalled()
describe 'renderShowView', ->
it 'renders the show view, removes the edit view, and registers event handlers', ->
spyOn(@view, "_delete")
spyOn(@view, "edit")
# Without calling renderEditView first, renderShowView is a no-op
@view.renderEditView()
@view.renderShowView()
@view.showView.trigger "comment:_delete", makeEventSpy()
expect(@view._delete).toHaveBeenCalled()
@view.showView.trigger "comment:edit", makeEventSpy()
expect(@view.edit).toHaveBeenCalled()
expect(@view.$("#response-comment-show-div").length).toEqual(1)
expect(@view.$("#response-comment-edit-div").length).toEqual(0)
describe 'renderEditView', ->
it 'renders the edit view, removes the show view, and registers event handlers', ->
spyOn(@view, "update")
spyOn(@view, "cancelEdit")
@view.renderEditView()
@view.editView.trigger "comment:update", makeEventSpy()
expect(@view.update).toHaveBeenCalled()
@view.editView.trigger "comment:cancel_edit", makeEventSpy()
expect(@view.cancelEdit).toHaveBeenCalled()
expect(@view.$("#response-comment-show-div").length).toEqual(0)
expect(@view.$("#response-comment-edit-div").length).toEqual(1)
describe 'edit', ->
it 'triggers the appropriate event and switches to the edit view', ->
spyOn(@view, 'renderEditView')
editTarget = jasmine.createSpy()
@view.bind "comment:edit", editTarget
@view.edit()
expect(@view.renderEditView).toHaveBeenCalled()
expect(editTarget).toHaveBeenCalled()
describe 'with edit view displayed', ->
beforeEach ->
@view.renderEditView()
describe 'cancelEdit', ->
it 'triggers the appropriate event and switches to the show view', ->
spyOn(@view, 'renderShowView')
cancelEditTarget = jasmine.createSpy()
@view.bind "comment:cancel_edit", cancelEditTarget
@view.cancelEdit()
expect(@view.renderShowView).toHaveBeenCalled()
expect(cancelEditTarget).toHaveBeenCalled()
describe 'update', ->
beforeEach ->
@updatedBody = "updated body"
@view.$el.find(".edit-comment-body textarea").val(@updatedBody)
spyOn(@view, 'cancelEdit')
spyOn($, "ajax").andCallFake(
(params) =>
expect(params.url._parts.path).toEqual("/courses/edX/999/test/discussion/comments/01234567/update")
expect(params.data.body).toEqual(@updatedBody)
if @ajaxSucceed
params.success()
else
params.error({status: 500})
{always: ->}
)
it 'calls the update endpoint correctly and displays the show view on success', ->
@ajaxSucceed = true
@view.update(makeEventSpy())
expect($.ajax).toHaveBeenCalled()
expect(@view.model.get("body")).toEqual(@updatedBody)
expect(@view.cancelEdit).toHaveBeenCalled()
it 'handles AJAX errors', ->
originalBody = @comment.get("body")
@ajaxSucceed = false
@view.update(makeEventSpy())
expect($.ajax).toHaveBeenCalled()
expect(@view.model.get("body")).toEqual(originalBody)
expect(@view.cancelEdit).not.toHaveBeenCalled()
expect(@view.$(".edit-comment-form-errors *").length).toEqual(1)
describe 'ThreadResponseView', ->
beforeEach ->
setFixtures """
<script id="thread-response-template" type="text/template">
<div/>
</script>
<div id="thread-response-fixture"/>
"""
@response = new Comment {
children: [{}, {}]
}
@view = new ThreadResponseView({model: @response, el: $("#thread-response-fixture")})
spyOn(ThreadResponseShowView.prototype, "render")
spyOn(ResponseCommentView.prototype, "render")
describe 'renderComments', ->
it 'populates commentViews and binds events', ->
# Ensure that edit view is set to test invocation of cancelEdit
@view.createEditView()
spyOn(@view, 'cancelEdit')
spyOn(@view, 'cancelCommentEdits')
spyOn(@view, 'hideCommentForm')
spyOn(@view, 'showCommentForm')
@view.renderComments()
expect(@view.commentViews.length).toEqual(2)
@view.commentViews[0].trigger "comment:edit", jasmine.createSpyObj("event", ["preventDefault"])
expect(@view.cancelEdit).toHaveBeenCalled()
expect(@view.cancelCommentEdits).toHaveBeenCalled()
expect(@view.hideCommentForm).toHaveBeenCalled()
@view.commentViews[0].trigger "comment:cancel_edit"
expect(@view.showCommentForm).toHaveBeenCalled()
describe 'cancelCommentEdits', ->
it 'calls cancelEdit on each comment view', ->
@view.renderComments()
expect(@view.commentViews.length).toEqual(2)
_.each(@view.commentViews, (commentView) -> spyOn(commentView, 'cancelEdit'))
@view.cancelCommentEdits()
_.each(@view.commentViews, (commentView) -> expect(commentView.cancelEdit).toHaveBeenCalled())
......@@ -157,11 +157,20 @@ class @DiscussionUtil
@formErrorHandler: (errorsField) ->
(xhr, textStatus, error) ->
response = JSON.parse(xhr.responseText)
if response.errors? and response.errors.length > 0
errorsField.empty()
for error in response.errors
errorsField.append($("<li>").addClass("new-post-form-error").html(error)).show()
makeErrorElem = (message) ->
$("<li>").addClass("new-post-form-error").html(message)
errorsField.empty().show()
if xhr.status == 400
response = JSON.parse(xhr.responseText)
if response.errors? and response.errors.length > 0
for error in response.errors
errorsField.append(makeErrorElem(error))
else
errorsField.append(
makeErrorElem(
gettext("We had some trouble processing your request. Please try again.")
)
)
@clearFormErrors: (errorsField) ->
errorsField.empty()
......
if Backbone?
class @ResponseCommentEditView extends Backbone.View
events:
"click .post-update": "update"
"click .post-cancel": "cancel_edit"
$: (selector) ->
@$el.find(selector)
initialize: ->
super()
render: ->
@template = _.template($("#response-comment-edit-template").html())
@$el.html(@template(@model.toJSON()))
@delegateEvents()
DiscussionUtil.makeWmdEditor @$el, $.proxy(@$, @), "edit-comment-body"
@
update: (event) ->
@trigger "comment:update", event
cancel_edit: (event) ->
@trigger "comment:cancel_edit", event
......@@ -3,6 +3,7 @@ if Backbone?
events:
"click .action-delete": "_delete"
"click .action-edit": "edit"
tagName: "li"
......@@ -14,6 +15,9 @@ if Backbone?
can_delete:
enable: -> @$(".action-delete").show()
disable: -> @$(".action-delete").hide()
editable:
enable: -> @$(".action-edit").show()
disable: -> @$(".action-edit").hide()
render: ->
@template = _.template($("#response-comment-show-template").html())
......@@ -68,4 +72,5 @@ if Backbone?
updateModelDetails: =>
@renderFlagged()
edit: (event) =>
@trigger "comment:edit", event
......@@ -7,29 +7,37 @@ if Backbone?
initialize: ->
super()
@createShowView()
render: ->
@renderShowView()
@
createShowView: () ->
if @editView?
@editView.undelegateEvents()
@editView.$el.empty()
@editView = null
@showView = new ResponseCommentShowView(model: @model)
@showView.bind "comment:_delete", @_delete
renderSubView: (view) ->
view.setElement(@$el)
view.render()
view.delegateEvents()
renderShowView: () ->
@renderSubView(@showView)
if not @showView?
if @editView?
@editView.undelegateEvents()
@editView.$el.empty()
@editView = null
@showView = new ResponseCommentShowView(model: @model)
@showView.bind "comment:_delete", @_delete
@showView.bind "comment:edit", @edit
@renderSubView(@showView)
renderEditView: () ->
if not @editView?
if @showView?
@showView.undelegateEvents()
@showView.$el.empty()
@showView = null
@editView = new ResponseCommentEditView(model: @model)
@editView.bind "comment:update", @update
@editView.bind "comment:cancel_edit", @cancelEdit
@renderSubView(@editView)
_delete: (event) =>
event.preventDefault()
......@@ -51,3 +59,27 @@ if Backbone?
gettext("Sorry"),
gettext("We had some trouble deleting this comment. Please try again.")
)
cancelEdit: (event) =>
@trigger "comment:cancel_edit", event
@renderShowView()
edit: (event) =>
@trigger "comment:edit", event
@renderEditView()
update: (event) =>
newBody = @editView.$(".edit-comment-body textarea").val()
url = DiscussionUtil.urlFor("update_comment", @model.id)
DiscussionUtil.safeAjax
$elem: $(event.target)
$loading: $(event.target)
url: url
type: "POST"
dataType: "json"
data:
body: newBody
error: DiscussionUtil.formErrorHandler(@$(".edit-comment-form-errors"))
success: (response, textStatus) =>
@model.set("body", newBody)
@cancelEdit()
......@@ -53,6 +53,7 @@ if Backbone?
renderComments: ->
comments = new Comments()
@commentViews = []
comments.comparator = (comment) ->
comment.get('created_at')
collectComments = (comment) ->
......@@ -69,6 +70,12 @@ if Backbone?
view = new ResponseCommentView(model: comment)
view.render()
@$el.find(".comments .new-comment").before(view.el)
view.bind "comment:edit", (event) =>
@cancelEdit(event) if @editView?
@cancelCommentEdits()
@hideCommentForm()
view.bind "comment:cancel_edit", () => @showCommentForm()
@commentViews.push(view)
view
submitComment: (event) ->
......@@ -128,6 +135,9 @@ if Backbone?
renderEditView: () ->
@renderSubView(@editView)
cancelCommentEdits: () ->
_.each(@commentViews, (view) -> view.cancelEdit())
hideCommentForm: () ->
@$('.comment-form').closest('li').hide()
......@@ -157,6 +167,7 @@ if Backbone?
edit: (event) =>
@createEditView()
@renderEditView()
@cancelCommentEdits()
@hideCommentForm()
update: (event) =>
......
......@@ -342,6 +342,10 @@ body.discussion {
}
.comments .edit-post-form h1 {
@extend %t-title6;
}
.new-post-form {
width: 100%;
margin-bottom: 20px;
......@@ -539,13 +543,14 @@ body.discussion {
height: 20px;
list-style: none;
cursor: pointer;
background: none;
}
.wmd-button > span {
display: inline-block;
width: 20px;
height: 20px;
background-image: url('/static/images/wmd-buttons.png');
background-image: url('/static/images/wmd-buttons-transparent.png');
background-position: 0px 0px;
background-repeat: no-repeat;
}
......@@ -1631,6 +1636,7 @@ body.discussion {
> li {
background: #f6f6f6;
border-bottom: 1px solid #ddd;
padding: ($baseline/2) $baseline;
}
blockquote {
......@@ -1667,7 +1673,7 @@ body.discussion {
.response-body {
font-size: 13px;
padding: $baseline/2 $baseline;
margin-bottom: $baseline/2;
p + p {
margin-top: 12px;
......@@ -1675,7 +1681,6 @@ body.discussion {
}
.posted-details {
margin: 0 $baseline $baseline/2;
font-size: 11px;
}
......@@ -2529,49 +2534,28 @@ body.discussion {
display:none;
}
.discussion-flag-abuse, .discussion-delete-comment {
.discussion-flag-abuse, .discussion-delete-comment, .discussion-edit-comment {
font-size: 12px;
float:right;
padding-right: 5px;
margin-left: $baseline/2;
font-style: italic;
cursor:pointer;
color: $dark-gray;
opacity: 0.8;
&:hover, &:focus {
@include transition(opacity .2s linear 0s);
opacity: 1.0;
}
}
.notflagged .icon {
display: block;
color: #333;
float: left;
margin: 3px;
width: 10px;
height: 14px;
padding-right: 3px;
}
.flagged .icon
{
display: block;
float: left;
margin: 3px;
width: 10px;
height: 14px;
padding-right: 3px;
color: $pink;
.flag-label {
font-style: italic;
margin-left: $baseline/4;
}
}
.flagged span {
.flagged * {
color: $pink;
font-style: italic;
}
.notflagged span {
color: #333;
font-style: italic;
}
.response-count {
......
......@@ -154,7 +154,7 @@
<h1>${_("Editing response")}</h1>
<ul class="edit-post-form-errors"></ul>
<div class="form-row">
<div class="edit-post-body" name="body">${"<%- body %>"}</div>
<div class="edit-post-body" name="body" data-id="${'<%- id %>'}">${"<%- body %>"}</div>
</div>
<input type="submit" id="edit-response-submit"class="post-update" value="${_("Update response") | h}">
<a href="#" class="post-cancel">${_("Cancel")}</a>
......@@ -168,6 +168,8 @@
<i class="icon icon-flag"></i><span class="sr flag-label">${_("Report Misuse")}</span></div>
<div style="display: none" class="discussion-delete-comment action-delete" data-tooltip="${_('Delete Comment') | h}" role="button" tabindex="0">
<i class="icon icon-remove"></i><span class="sr">${_("Delete Comment")}</span></div>
<div class="discussion-edit-comment action-edit" data-tooltip="${_('Edit') | h}" role="button" tabindex="0">
<i class="icon icon-pencil"></i><span class="sr">${_("Edit")}</span></div>
<%
js_block = u"""
interpolate(
......@@ -190,6 +192,18 @@
</div>
</script>
<script aria-hidden="true" type="text/template" id="response-comment-edit-template">
<div class="edit-post-form">
<h1>${_("Editing comment")}</h1>
<ul class="edit-comment-form-errors"></ul>
<div class="form-row">
<div class="edit-comment-body" name="body" data-id="${'<%- id %>'}">${"<%- body %>"}</div>
</div>
<input type="submit" id="edit-comment-submit" class="post-update" value="${_("Update comment") | h}">
<a href="#" class="post-cancel">${_("Cancel")}</a>
</div>
</script>
<script aria-hidden="true" type="text/template" id="thread-list-item-template">
<a href="${'<%- id %>'}" data-id="${'<%- id %>'}">
<span class="title">${"<%- title %>"}</span>
......
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