Commit 7470e600 by Greg Price

Unify forum thread views (and templates)

parent ab2b7f34
......@@ -150,21 +150,21 @@ describe "DiscussionThreadListView", ->
<div class="forum-nav"></div>
"""
@threads = [
makeThreadWithProps({
DiscussionViewSpecHelper.makeThreadWithProps({
id: "1",
title: "Thread1",
votes: {up_count: '20'},
comments_count: 1,
created_at: '2013-04-03T20:08:39Z',
}),
makeThreadWithProps({
DiscussionViewSpecHelper.makeThreadWithProps({
id: "2",
title: "Thread2",
votes: {up_count: '42'},
comments_count: 2,
created_at: '2013-04-03T20:07:39Z',
}),
makeThreadWithProps({
DiscussionViewSpecHelper.makeThreadWithProps({
id: "3",
title: "Thread3",
votes: {up_count: '12'},
......@@ -179,20 +179,8 @@ describe "DiscussionThreadListView", ->
@view = new DiscussionThreadListView({collection: @discussion, el: $(".forum-nav")})
@view.render()
makeThreadWithProps = (props) ->
# Minimal set of properties necessary for rendering
thread = {
id: "dummy_id",
pinned: false,
endorsed: false,
votes: {up_count: '0'},
unread_comments_count: 0,
comments_count: 0,
}
$.extend(thread, props)
renderSingleThreadWithProps = (props) ->
makeView(new Discussion([new Thread(makeThreadWithProps(props))])).render()
makeView(new Discussion([new Thread(DiscussionViewSpecHelper.makeThreadWithProps(props))])).render()
makeView = (discussion) ->
return new DiscussionThreadListView(
......
describe "DiscussionThreadInlineView", ->
beforeEach ->
setFixtures(
"""
<script type="text/template" id="_inline_thread">
<article class="discussion-article">
<div class="non-cohorted-indicator"/>
<div class="post-body"/>
<div class="post-extended-content">
<div class="response-count"/>
<ol class="responses"/>
<div class="response-pagination"/>
</div>
<div class="post-tools">
<a href="javascript:void(0)" class="expand-post">Expand</a>
<a href="javascript:void(0)" class="collapse-post">Collapse</a>
</div>
</article>
</script>
<script type="text/template" id="_inline_thread_cohorted">
<article class="discussion-article">
<div class="cohorted-indicator"/>
<div class="post-body"/>
<div class="post-extended-content">
<div class="response-count"/>
<ol class="responses"/>
<div class="response-pagination"/>
</div>
<div class="post-tools">
<a href="javascript:void(0)" class="expand-post">Expand</a>
<a href="javascript:void(0)" class="collapse-post">Collapse</a>
</div>
</article>
</script>
<div class="thread-fixture"/>
"""
)
@threadData = {
id: "dummy",
body: "dummy body",
abuse_flaggers: [],
votes: {up_count: "42"}
}
@thread = new Thread(@threadData)
@view = new DiscussionThreadInlineView({ model: @thread })
@view.setElement($(".thread-fixture"))
spyOn($, "ajax")
# Avoid unnecessary boilerplate
spyOn(@view.showView, "render")
spyOn(@view.showView, "convertMath")
spyOn(@view, "makeWmdEditor")
spyOn(DiscussionThreadView.prototype, "renderResponse")
assertContentVisible = (view, selector, visible) ->
content = view.$el.find(selector)
expect(content.length).toEqual(1)
expect(content.is(":visible")).toEqual(visible)
assertExpandedContentVisible = (view, expanded) ->
expect(view.$el.hasClass("expanded")).toEqual(expanded)
assertContentVisible(view, ".post-extended-content", expanded)
assertContentVisible(view, ".expand-post", not expanded)
assertContentVisible(view, ".collapse-post", expanded)
describe "render", ->
it "uses the cohorted template if cohorted", ->
@view.model.set({group_id: 1})
@view.render()
expect(@view.$el.find(".cohorted-indicator").length).toEqual(1)
it "uses the non-cohorted template if not cohorted", ->
@view.render()
expect(@view.$el.find(".non-cohorted-indicator").length).toEqual(1)
it "shows content that should be visible when collapsed", ->
@view.render()
assertExpandedContentVisible(@view, false)
it "does not render any responses by default", ->
@view.render()
expect($.ajax).not.toHaveBeenCalled()
expect(@view.$el.find(".responses li").length).toEqual(0)
describe "expand/collapse", ->
it "shows/hides appropriate content", ->
DiscussionViewSpecHelper.setNextResponseContent({resp_total: 0, children: []})
@view.render()
@view.expandPost()
assertExpandedContentVisible(@view, true)
@view.collapsePost()
assertExpandedContentVisible(@view, false)
it "switches between the abbreviated and full body", ->
DiscussionViewSpecHelper.setNextResponseContent({resp_total: 0, children: []})
@thread.set("body", new Array(100).join("test "))
@view.abbreviateBody()
expect(@thread.get("body")).not.toEqual(@thread.get("abbreviatedBody"))
@view.render()
@view.expandPost()
expect(@view.$el.find(".post-body").text()).toEqual(@thread.get("body"))
expect(@view.showView.convertMath).toHaveBeenCalled()
@view.showView.convertMath.reset()
@view.collapsePost()
expect(@view.$el.find(".post-body").text()).toEqual(@thread.get("abbreviatedBody"))
expect(@view.showView.convertMath).toHaveBeenCalled()
class @DiscussionViewSpecHelper
@makeThreadWithProps = (props) ->
# Minimal set of properties necessary for rendering
thread = {
id: "dummy_id",
pinned: false,
endorsed: false,
votes: {up_count: '0'},
unread_comments_count: 0,
comments_count: 0,
abuse_flaggers: [],
body: ""
}
$.extend(thread, props)
@expectVoteRendered = (view, voted) ->
button = view.$el.find(".vote-btn")
if voted
......
......@@ -101,7 +101,7 @@ if Backbone?
@newPostForm = $('.new-post-article')
@threadviews = @discussion.map (thread) ->
new DiscussionThreadInlineView el: @$("article#thread_#{thread.id}"), model: thread
new DiscussionThreadView el: @$("article#thread_#{thread.id}"), model: thread, mode: "inline"
_.each @threadviews, (dtv) -> dtv.render()
DiscussionUtil.bulkUpdateContentInfo(window.$$annotated_content_info)
@newPostView = new NewPostView(
......@@ -124,7 +124,7 @@ if Backbone?
# TODO: When doing pagination, this will need to repaginate. Perhaps just reload page 1?
article = $("<article class='discussion-thread' id='thread_#{thread.id}'></article>")
@$('section.discussion > .threads').prepend(article)
threadView = new DiscussionThreadInlineView el: article, model: thread
threadView = new DiscussionThreadView el: article, model: thread, mode: "inline"
threadView.render()
@threadviews.unshift threadView
......
......@@ -50,7 +50,7 @@ if Backbone?
if(@newPost.is(":visible"))
@newPost.fadeOut()
@main = new DiscussionThreadView(el: $(".forum-content"), model: @thread)
@main = new DiscussionThreadView(el: $(".forum-content"), model: @thread, mode: "tab")
@main.render()
@main.on "thread:responses:rendered", =>
@nav.updateSidebar()
......
......@@ -23,13 +23,18 @@ if Backbone?
$: (selector) ->
@$el.find(selector)
initialize: ->
initialize: (options) ->
super()
@mode = options.mode or "inline" # allowed values are "tab" or "inline"
if @mode not in ["tab", "inline"]
throw new Error("invalid mode: " + @mode)
@model.on "change", @updateModelDetails
renderTemplate: ->
@template = _.template($("#thread-show-template").html())
@template(@model.toJSON())
context = @model.toJSON()
context.mode = @mode
@template(context)
render: ->
@$el.html(@renderTemplate())
......@@ -170,13 +175,3 @@ if Backbone?
highlight: (el) ->
if el.html()
el.html(el.html().replace(/&lt;mark&gt;/g, "<mark>").replace(/&lt;\/mark&gt;/g, "</mark>"))
class @DiscussionThreadInlineShowView extends DiscussionThreadShowView
renderTemplate: ->
@template = DiscussionUtil.getTemplate('_inline_thread_show')
params = @model.toJSON()
if @model.get('username')?
params = $.extend(params, user:{username: @model.username, user_url: @model.user_url})
Mustache.render(@template, params)
......@@ -7,14 +7,20 @@ if Backbone?
events:
"click .discussion-submit-post": "submitComment"
"click .add-response-btn": "scrollToAddResponse"
"click .forum-thread-expand": "expand"
"click .forum-thread-collapse": "collapse"
$: (selector) ->
@$el.find(selector)
initialize: ->
initialize: (options) ->
super()
@mode = options.mode or "inline" # allowed values are "tab" or "inline"
if @mode not in ["tab", "inline"]
throw new Error("invalid mode: " + @mode)
@createShowView()
@responses = new Comments()
@loadedResponses = false
renderTemplate: ->
@template = _.template($("#thread-template").html())
......@@ -31,10 +37,44 @@ if Backbone?
@makeWmdEditor "reply-body"
@renderAddResponseButton()
@responses.on("add", @renderResponse)
# Without a delay, jQuery doesn't add the loading extension defined in
# utils.coffee before safeAjax is invoked, which results in an error
setTimeout((=> @loadInitialResponses()), 100)
@
if @mode == "tab"
# Without a delay, jQuery doesn't add the loading extension defined in
# utils.coffee before safeAjax is invoked, which results in an error
setTimeout((=> @loadInitialResponses()), 100)
@$(".post-tools").hide()
else # mode == "inline"
@collapse()
expand: (event) ->
if event
event.preventDefault()
@$el.addClass("expanded")
@$el.find(".post-body").html(@model.get("body"))
@showView.convertMath()
@$el.find(".forum-thread-expand").hide()
@$el.find(".forum-thread-collapse").show()
@$el.find(".post-extended-content").show()
if not @loadedResponses
@loadInitialResponses()
collapse: (event) ->
if event
event.preventDefault()
@$el.removeClass("expanded")
@$el.find(".post-body").html(@getAbbreviatedBody())
@showView.convertMath()
@$el.find(".forum-thread-expand").show()
@$el.find(".forum-thread-collapse").hide()
@$el.find(".post-extended-content").hide()
getAbbreviatedBody: ->
cached = @model.get("abbreviatedBody")
if cached
cached
else
abbreviated = DiscussionUtil.abbreviateString @model.get("body"), 140
@model.set("abbreviatedBody", abbreviated)
abbreviated
cleanup: ->
if @responsesRequest?
......@@ -56,6 +96,7 @@ if Backbone?
@responses.add(data['content']['children'])
@renderResponseCountAndPagination(data['content']['resp_total'])
@trigger "thread:responses:rendered"
@loadedResponses = true
error: (xhr) =>
if xhr.status == 404
DiscussionUtil.discussionAlert(
......@@ -208,6 +249,7 @@ if Backbone?
@model.set
title: newTitle
body: newBody
@model.unset("abbreviatedBody")
@createShowView()
@renderShowView()
......@@ -231,9 +273,6 @@ if Backbone?
renderEditView: () ->
@renderSubView(@editView)
getShowViewClass: () ->
return DiscussionThreadShowView
createShowView: () ->
if @editView?
......@@ -241,8 +280,7 @@ if Backbone?
@editView.$el.empty()
@editView = null
showViewClass = @getShowViewClass()
@showView = new showViewClass(model: @model)
@showView = new DiscussionThreadShowView({model: @model, mode: @mode})
@showView.bind "thread:_delete", @_delete
@showView.bind "thread:edit", @edit
......
if Backbone?
class @DiscussionThreadInlineView extends DiscussionThreadView
expanded = false
events:
"click .discussion-submit-post": "submitComment"
"click .expand-post": "expandPost"
"click .collapse-post": "collapsePost"
"click .add-response-btn": "scrollToAddResponse"
initialize: ->
super()
renderTemplate: () ->
if @model.has('group_id')
@template = DiscussionUtil.getTemplate("_inline_thread_cohorted")
else
@template = DiscussionUtil.getTemplate("_inline_thread")
if not @model.has('abbreviatedBody')
@abbreviateBody()
params = @model.toJSON()
Mustache.render(@template, params)
render: () ->
super()
@$el.find('.post-extended-content').hide()
@$el.find('.collapse-post').hide()
getShowViewClass: () ->
return DiscussionThreadInlineShowView
loadInitialResponses: () ->
if @expanded
super()
abbreviateBody: ->
abbreviated = DiscussionUtil.abbreviateString @model.get('body'), 140
@model.set('abbreviatedBody', abbreviated)
expandPost: (event) =>
@$el.addClass('expanded')
@$el.find('.post-body').html(@model.get('body'))
@showView.convertMath()
@$el.find('.expand-post').css('display', 'none')
@$el.find('.collapse-post').css('display', 'block')
@$el.find('.post-extended-content').show()
if not @expanded
@expanded = true
@loadInitialResponses()
collapsePost: (event) ->
curScroll = $(window).scrollTop()
postTop = @$el.offset().top
if postTop < curScroll
$('html, body').animate({scrollTop: postTop})
@$el.removeClass('expanded')
@$el.find('.post-body').html(@model.get('abbreviatedBody'))
@showView.convertMath()
@$el.find('.expand-post').css('display', 'block')
@$el.find('.collapse-post').css('display', 'none')
@$el.find('.post-extended-content').hide()
createEditView: () ->
super()
@editView.bind "thread:update", @abbreviateBody
......@@ -269,7 +269,7 @@ class InlineDiscussionThreadPage(DiscussionThreadPage):
def expand(self):
"""Clicks the link to expand the thread"""
self._find_within(".expand-post").first.click()
self._find_within(".forum-thread-expand").first.click()
EmptyPromise(
lambda: bool(self.get_response_total_text()),
"Thread expanded"
......
......@@ -1007,7 +1007,8 @@ body.discussion {
max-height: 600px;
.group-visibility-label {
margin: $baseline ($baseline*1.5) ($baseline*-0.5);
font-weight: 400;
margin-bottom: ($baseline*0.5);
}
.discussion-post {
......@@ -1043,10 +1044,10 @@ body.discussion {
}
}
h3 {
h1 {
font-size: 19px;
font-weight: 700;
margin-bottom: 0px;
margin-bottom: 0px !important; // Override courseware CSS
}
h4 {
......
......@@ -40,7 +40,7 @@
// CASE: inline styling
// TO-DO: additional styling cleanup here necessary, for now this case was ported over from _discussion.scss
.discussion-module .forum-new-post-form {
.discussion-module {
.wmd-panel {
width: 100%;
......
......@@ -4,29 +4,37 @@
<script aria-hidden="true" type="text/template" id="thread-template">
<article class="discussion-article" data-id="${'<%- id %>'}">
<div class="thread-content-wrapper"></div>
<div class="response-count"/>
<div class="add-response">
<button class="button add-response-btn">
<i class="icon icon-reply"></i>
<span class="add-response-btn-text">${_('Add A Response')}</span>
</button>
<div class="thread-wrapper">
<div class="thread-content-wrapper"></div>
<div class="post-extended-content">
<div class="response-count"/>
<div class="add-response">
<button class="button add-response-btn">
<i class="icon icon-reply"></i>
<span class="add-response-btn-text">${_('Add A Response')}</span>
</button>
</div>
<ol class="responses"/>
<div class="response-pagination"/>
<div class="post-status-closed bottom-post-status" style="display: none">
${_("This thread is closed.")}
</div>
% if course is UNDEFINED or has_permission(user, 'create_comment', course.id):
<form class="discussion-reply-new" data-id="${'<%- id %>'}">
<h4>${_("Post a response:")}</h4>
<ul class="discussion-errors"></ul>
<div class="reply-body" data-id="${'<%- id %>'}"></div>
<div class="reply-post-control">
<a class="discussion-submit-post control-button" href="#">${_("Submit")}</a>
</div>
</form>
% endif
</div>
</div>
<ol class="responses"/>
<div class="response-pagination"/>
<div class="post-status-closed bottom-post-status" style="display: none">
${_("This thread is closed.")}
<div class="post-tools">
<a href="javascript:void(0)" class="forum-thread-expand"><span class="icon icon-plus"/> ${_("Expand discussion")}</a>
<a href="javascript:void(0)" class="forum-thread-collapse"><span class="icon icon-minus"/> ${_("Collapse discussion")}</a>
</div>
% if course is UNDEFINED or has_permission(user, 'create_comment', course.id):
<form class="discussion-reply-new" data-id="${'<%- id %>'}">
<h4>${_("Post a response:")}</h4>
<ul class="discussion-errors"></ul>
<div class="reply-body" data-id="${'<%- id %>'}"></div>
<div class="reply-post-control">
<a class="discussion-submit-post control-button" href="#">${_("Submit")}</a>
</div>
</form>
% endif
</article>
</script>
......@@ -80,15 +88,17 @@
escapejs(_("(this post is about %(courseware_title_linked)s)"))
)
%>
${'<% if (obj.courseware_url) { %>'}
${'<% if (mode == "tab" && obj.courseware_url) { %>'}
<div class="post-context">${'<%'}${js_block}${'%>'}</div>
${'<% } %>'}
<ul class="moderator-actions">
<li style="display: none"><a class="action-edit" href="javascript:void(0)"><span class="edit-icon"></span> ${_("Edit")}</a></li>
<li style="display: none"><a class="action-delete" href="javascript:void(0)"><span class="delete-icon"></span> ${_("Delete")}</a></li>
<li style="display: none"><a class="action-openclose" href="javascript:void(0)"><span class="edit-icon"></span> ${_("Close")}</a></li>
</ul>
<div class="post-extended-content">
<ul class="moderator-actions">
<li style="display: none"><a class="action-edit" href="javascript:void(0)"><span class="edit-icon"></span> ${_("Edit")}</a></li>
<li style="display: none"><a class="action-delete" href="javascript:void(0)"><span class="delete-icon"></span> ${_("Delete")}</a></li>
<li style="display: none"><a class="action-openclose" href="javascript:void(0)"><span class="edit-icon"></span> ${_("Close")}</a></li>
</ul>
</div>
</div>
</script>
......
<%! from django.utils.translation import ugettext as _ %>
<article class="discussion-article" data-id="{{id}}">
<div class="thread-wrapper">
<div class="thread-content-wrapper"></div>
<div class="post-extended-content">
<div class="response-count"/>
<div class="add-response">
<button class="button add-response-btn">
<i class="icon icon-reply"></i>
<span class="add-response-btn-text">${_('Add A Response')}</span>
</button>
</div>
<ol class="responses"/>
<div class="response-pagination"/>
{{#ability.can_reply}}
<form class="discussion-reply-new" data-id="{{id}}">
<h4>${_("Post a response:")}</h4>
<ul class="discussion-errors"></ul>
<div class="reply-body" data-id="{{id}}"></div>
<div class="reply-post-control">
<a class="discussion-submit-post control-button" href="#">${_("Submit")}</a>
</div>
</form>
{{/ability.can_reply}}
</div>
</div>
<div class="post-tools">
<a href="javascript:void(0)" class="expand-post"><span class="icon icon-plus"/> ${_("Expand discussion")}</a>
<a href="javascript:void(0)" class="collapse-post"><span class="icon icon-minus"/> ${_("Collapse discussion")}</a>
</div>
</article>
<%! from django.utils.translation import ugettext as _ %>
<article class="discussion-article" data-id="{{id}}">
<div class="thread-wrapper">
<div class="group-visibility-label">{{group_string}}</div>
<div class="thread-content-wrapper"></div>
<div class="post-extended-content">
<div class="response-count"/>
<div class="add-response">
<button class="button add-response-btn">
<i class="icon icon-reply"></i>
<span class="add-response-btn-text">${_('Add A Response')}</span>
</button>
</div>
<ol class="responses"/>
<div class="response-pagination"/>
<form class="discussion-reply-new" data-id="{{id}}">
<h4>${_("Post a response:")}</h4>
<ul class="discussion-errors"></ul>
<div class="reply-body" data-id="{{id}}"></div>
<div class="reply-post-control">
<a class="discussion-submit-post control-button" href="#">${_("Submit")}</a>
</div>
</form>
</div>
</div>
<div class="post-tools">
<a href="javascript:void(0)" class="expand-post"><span class="icon icon-plus"/> ${_("Expand discussion")}</a>
<a href="javascript:void(0)" class="collapse-post"><span class="icon icon-minus"/> ${_("Collapse discussion")}</a>
</div>
</article>
<%! from django.utils.translation import ugettext as _ %>
<div class="discussion-post">
<div><a href="javascript:void(0)" class="dogear action-follow" data-tooltip="follow"></a></div>
<header>
<a href="#" class="vote-btn" role="button" aria-pressed="false"/>
<h3>{{title}}</h3>
<div class="discussion-flag-abuse notflagged" data-role="thread-flag">
<i class="icon icon-flag"></i><span class="flag-label"/></div>
<div class="discussion-pin-inline pinned pinned-{{pinned}}" data-tooltip="${_("This thread has been pinned by course staff.")}">
<i class="icon icon-pushpin"></i><span class="pin-label">${_("Pinned")}</span></div>
<p class="posted-details">
{{#user}}
<a href="{{user_url}}" class="username">{{username}}</a>
{{/user}}
{{^user}}
${_("anonymous")}
{{/user}}
<span class="timeago" title="{{created_at}}">{{created_at}}</span>
<span class="post-status-closed top-post-status" style="display: none">
&bull; ${_("This thread is closed.")}
</span>
</p>
</header>
<div class="post-body">{{abbreviatedBody}}</div>
<ul class="moderator-actions post-extended-content">
<li style="display: none"><a class="action-edit" href="javascript:void(0)"><span class="edit-icon"></span> ${_("Edit")}</a></li>
<li style="display: none"><a class="action-delete" href="javascript:void(0)"><span class="delete-icon"></span> ${_("Delete")}</a></li>
<li style="display: none"><a class="action-openclose" href="javascript:void(0)"><span class="edit-icon"></span> ${_("Close")}</a></li>
</ul>
</div>
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